src/detail/decoder.cpp

93.2% Lines (109/117) 100.0% List of functions (14/14) 84.3% Branches (43/51)
decoder.cpp
f(x) Functions (14)
Function Calls Lines Branches Blocks
boost::burl::detail::(anonymous namespace)::zlib_decoder::zlib_decoder(boost::http::zlib::inflate_service&, int) :45 28x 80.0% 66.7% 56.0% boost::burl::detail::(anonymous namespace)::zlib_decoder::~zlib_decoder() :55 56x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::zlib_decoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool) :61 436x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::zlib_decoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool)::{lambda()#1}::operator()() const :79 436x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::zlib_decoder::saturate(unsigned long) :97 1744x 80.0% 50.0% 75.0% boost::burl::detail::(anonymous namespace)::brotli_decoder::brotli_decoder(boost::http::brotli::decode_service&) :115 15x 85.7% 50.0% 56.0% boost::burl::detail::(anonymous namespace)::brotli_decoder::~brotli_decoder() :124 30x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::brotli_decoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool) :130 234x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::brotli_decoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool)::{lambda()#1}::operator()() const :148 234x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::zstd_decoder::zstd_decoder(boost::http::zstd::decompress_service&) :175 25x 83.3% 50.0% 56.0% boost::burl::detail::(anonymous namespace)::zstd_decoder::~zstd_decoder() :183 50x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::zstd_decoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool) :189 475x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::zstd_decoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool)::{lambda()#1}::operator()() const :200 475x 100.0% 100.0% 100.0% boost::burl::detail::make_decoder(boost::http::content_coding) :226 331x 84.6% 76.5% 77.0%
Line Branch TLA Hits Source Code
1 //
2 // Copyright (c) 2026 Mohammad Nejati
3 //
4 // Distributed under the Boost Software License, Version 1.0. (See accompanying
5 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
6 //
7 // Official repository: https://github.com/cppalliance/burl
8 //
9
10 #include "decoder.hpp"
11
12 #include <boost/capy/error.hpp>
13 #include <boost/capy/ex/system_context.hpp>
14 #include <boost/http/brotli/decode.hpp>
15 #include <boost/http/zlib/error.hpp>
16 #include <boost/http/zlib/flush.hpp>
17 #include <boost/http/zlib/inflate.hpp>
18 #include <boost/http/zlib/stream.hpp>
19 #include <boost/http/zstd/decompress.hpp>
20 #include <boost/http/zstd/types.hpp>
21
22 #include <cstdint>
23 #include <limits>
24 #include <new>
25 #include <system_error>
26
27 namespace boost
28 {
29 namespace burl
30 {
31 namespace detail
32 {
33
34 namespace
35 {
36
37 class zlib_decoder final
38 : public decoder
39 {
40 http::zlib::inflate_service& svc_;
41 http::zlib::stream strm_ = {};
42
43 public:
44 // window_bits: 15 for zlib/deflate, 15 + 16 for gzip.
45 28x zlib_decoder(
46 http::zlib::inflate_service& svc,
47 int window_bits)
48 28x : svc_(svc)
49 {
50
2/3
✓ Branch 1 taken 28 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 28 times.
28x if(svc_.init2(strm_, window_bits) !=
51 static_cast<int>(http::zlib::error::ok))
52 ✗ throw std::bad_alloc();
53 28x }
54
55 56x ~zlib_decoder() override
56 28x {
57 28x svc_.inflate_end(strm_);
58 56x }
59
60 result
61 436x process(
62 capy::mutable_buffer out,
63 capy::const_buffer in,
64 bool more) override
65 {
66 436x strm_.next_in = static_cast<unsigned char*>(
67 436x const_cast<void*>(in.data()));
68 436x strm_.avail_in = saturate(in.size());
69 436x strm_.next_out =
70 436x static_cast<unsigned char*>(out.data());
71 436x strm_.avail_out = saturate(out.size());
72
73 auto const rs = static_cast<http::zlib::error>(
74
2/2
✓ Branch 0 taken 356 times.
✓ Branch 1 taken 80 times.
436x svc_.inflate(
75
1/1
✓ Branch 1 taken 436 times.
436x strm_,
76 more ? http::zlib::no_flush
77 436x : http::zlib::finish));
78
79 1308x auto const ec = [&]() -> std::error_code
80 {
81
2/2
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 410 times.
436x if(rs == http::zlib::error::stream_end)
82 26x return capy::error::eof;
83
2/2
✓ Branch 0 taken 57 times.
✓ Branch 1 taken 353 times.
410x if(rs != http::zlib::error::ok &&
84
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 56 times.
57x rs != http::zlib::error::buf_err)
85 1x return rs;
86 409x return {};
87 436x }();
88
89 return {
90 436x .consumed = saturate(in.size()) - strm_.avail_in,
91 436x .produced = saturate(out.size()) - strm_.avail_out,
92 872x .ec = ec };
93 }
94
95 static
96 unsigned
97 1744x saturate(std::size_t n) noexcept
98 {
99 1744x constexpr auto max =
100 (std::numeric_limits<unsigned>::max)();
101
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1744 times.
1744x if(n >= max)
102 ✗ return max;
103 1744x return static_cast<unsigned>(n);
104 }
105 };
106
107 class brotli_decoder final
108 : public decoder
109 {
110 http::brotli::decode_service& svc_;
111 http::brotli::decoder_state* state_;
112
113 public:
114 explicit
115 15x brotli_decoder(http::brotli::decode_service& svc)
116 30x : svc_(svc)
117 30x , state_(svc.create_instance(
118 15x nullptr, nullptr, nullptr))
119 {
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 15 times.
15x if(!state_)
121 ✗ throw std::bad_alloc();
122 15x }
123
124 30x ~brotli_decoder() override
125 15x {
126 15x svc_.destroy_instance(state_);
127 30x }
128
129 result
130 234x process(
131 capy::mutable_buffer out,
132 capy::const_buffer in,
133 bool) override
134 {
135 234x auto* next_in = static_cast<std::uint8_t const*>(in.data());
136 234x auto available_in = in.size();
137 234x auto* next_out = static_cast<std::uint8_t*>(out.data());
138 234x auto available_out = out.size();
139
140 234x auto const rs = svc_.decompress_stream(
141 state_,
142 &available_in,
143 &next_in,
144 &available_out,
145 &next_out,
146 234x nullptr);
147
148 702x auto const ec = [&]() -> std::error_code
149 {
150
2/2
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 220 times.
234x if(svc_.is_finished(state_))
151 14x return capy::error::eof;
152
2/2
✓ Branch 0 taken 1 time.
✓ Branch 1 taken 219 times.
220x if(rs == http::brotli::decoder_result::error)
153 1x return svc_.get_error_code(state_);
154 219x return {};
155 234x }();
156
157 return {
158 234x .consumed = in.size() - available_in,
159 468x .produced = out.size() - available_out,
160 234x .ec = ec };
161 }
162 };
163
164 class zstd_decoder final
165 : public decoder
166 {
167 http::zstd::decompress_service& svc_;
168 http::zstd::dctx* ctx_;
169 // A payload is a sequence of frames (RFC 8878
170 // Section 3.1.1); it ends at a frame boundary.
171 bool at_boundary_ = true;
172
173 public:
174 explicit
175 25x zstd_decoder(http::zstd::decompress_service& svc)
176 50x : svc_(svc)
177 25x , ctx_(svc.create_dctx())
178 {
179
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25x if(!ctx_)
180 ✗ throw std::bad_alloc();
181 25x }
182
183 50x ~zstd_decoder() override
184 25x {
185 25x svc_.free_dctx(ctx_);
186 50x }
187
188 result
189 475x process(
190 capy::mutable_buffer out,
191 capy::const_buffer in,
192 bool more) override
193 {
194 475x http::zstd::in_buffer in_buf{ in.data(), in.size(), 0 };
195 475x http::zstd::out_buffer out_buf{ out.data(), out.size(), 0 };
196
197 auto const rs =
198 475x svc_.decompress_stream(ctx_, out_buf, in_buf);
199
200 1425x auto const ec = [&]() -> std::error_code
201 {
202
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 473 times.
475x if(svc_.is_error(rs))
203 2x return svc_.get_error_code(rs);
204 // zero marks the end of a frame; decoding
205 // resumes on the next one, skippable
206 // frames included
207
2/2
✓ Branch 0 taken 28 times.
✓ Branch 1 taken 445 times.
473x if(rs == 0)
208 28x at_boundary_ = true;
209
2/2
✓ Branch 0 taken 181 times.
✓ Branch 1 taken 264 times.
445x else if(in_buf.pos != 0)
210 181x at_boundary_ = false;
211
6/6
✓ Branch 0 taken 221 times.
✓ Branch 1 taken 252 times.
✓ Branch 2 taken 26 times.
✓ Branch 3 taken 195 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 8 times.
473x if(!more && in_buf.pos == in_buf.size && at_boundary_)
212 18x return capy::error::eof;
213 455x return {};
214 475x }();
215
216 return {
217 475x .consumed = in_buf.pos,
218 475x .produced = out_buf.pos,
219 475x .ec = ec };
220 }
221 };
222
223 } // namespace
224
225 std::unique_ptr<decoder>
226 331x make_decoder(http::content_coding coding)
227 {
228 331x auto const& ctx = capy::get_system_context();
229
5/5
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 15 times.
✓ Branch 3 taken 25 times.
✓ Branch 4 taken 263 times.
331x switch(coding)
230 {
231 9x case http::content_coding::deflate:
232
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9x if(auto* svc = ctx.find_service<
233 9x http::zlib::inflate_service>())
234
1/1
✓ Branch 1 taken 9 times.
9x return std::make_unique<zlib_decoder>(*svc, 15);
235 ✗ break;
236 19x case http::content_coding::gzip:
237
1/2
✓ Branch 0 taken 19 times.
✗ Branch 1 not taken.
19x if(auto* svc = ctx.find_service<
238 19x http::zlib::inflate_service>())
239
1/1
✓ Branch 1 taken 19 times.
19x return std::make_unique<zlib_decoder>(*svc, 15 + 16);
240 ✗ break;
241 15x case http::content_coding::br:
242
1/2
✓ Branch 0 taken 15 times.
✗ Branch 1 not taken.
15x if(auto* svc = ctx.find_service<
243 15x http::brotli::decode_service>())
244
1/1
✓ Branch 1 taken 15 times.
15x return std::make_unique<brotli_decoder>(*svc);
245 ✗ break;
246 25x case http::content_coding::zstd:
247
1/2
✓ Branch 0 taken 25 times.
✗ Branch 1 not taken.
25x if(auto* svc = ctx.find_service<
248 25x http::zstd::decompress_service>())
249
1/1
✓ Branch 1 taken 25 times.
25x return std::make_unique<zstd_decoder>(*svc);
250 ✗ break;
251 263x default:
252 263x break;
253 }
254 263x return nullptr;
255 }
256
257 } // namespace detail
258 } // namespace burl
259 } // namespace boost
260