src/detail/encoder.cpp

89.2% Lines (141/158) 100.0% List of functions (16/16) 76.1% Branches (54/71)
encoder.cpp
f(x) Functions (16)
Function Calls Lines Branches Blocks
boost::burl::detail::(anonymous namespace)::zlib_encoder::zlib_encoder(boost::http::zlib::deflate_service&, boost::burl::encoder_config::zlib_settings const&, bool) :49 16x 81.8% 87.5% 52.0% boost::burl::detail::(anonymous namespace)::zlib_encoder::~zlib_encoder() :69 32x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::zlib_encoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool) :75 313x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::zlib_encoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool)::{lambda()#1}::operator()() const :93 313x 81.8% 50.0% 69.0% boost::burl::detail::(anonymous namespace)::zlib_encoder::saturate(unsigned long) :111 1252x 80.0% 50.0% 75.0% boost::burl::detail::(anonymous namespace)::brotli_encoder::brotli_encoder(boost::http::brotli::encode_service&, boost::burl::encoder_config::brotli_settings const&) :128 9x 84.0% 85.7% 58.0% boost::burl::detail::(anonymous namespace)::brotli_encoder::~brotli_encoder() :159 18x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::brotli_encoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool) :165 158x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::brotli_encoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool)::{lambda()#1}::operator()() const :185 158x 81.8% 75.0% 83.0% boost::burl::detail::(anonymous namespace)::brotli_encoder::set(boost::http::brotli::encoder_parameter, int) :206 29x 100.0% 50.0% 75.0% boost::burl::detail::(anonymous namespace)::zstd_encoder::zstd_encoder(boost::http::zstd::compress_service&, boost::burl::encoder_config::zstd_settings const&) :222 9x 91.7% 83.3% 75.0% boost::burl::detail::(anonymous namespace)::zstd_encoder::~zstd_encoder() :240 18x 100.0% – 100.0% boost::burl::detail::(anonymous namespace)::zstd_encoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool) :246 166x 100.0% 100.0% 100.0% boost::burl::detail::(anonymous namespace)::zstd_encoder::process(boost::capy::mutable_buffer, boost::capy::const_buffer, bool)::{lambda()#1}::operator()() const :261 166x 90.9% 75.0% 75.0% boost::burl::detail::(anonymous namespace)::zstd_encoder::set(boost::http::zstd::c_parameter, int) :282 12x 100.0% 50.0% 80.0% boost::burl::detail::make_encoder(boost::http::content_coding, boost::burl::encoder_config const&) :296 37x 86.7% 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 "encoder.hpp"
11
12 #include <boost/assert.hpp>
13 #include <boost/capy/error.hpp>
14 #include <boost/capy/ex/system_context.hpp>
15 #include <boost/http/brotli/encode.hpp>
16 #include <boost/http/zlib/compression_method.hpp>
17 #include <boost/http/zlib/compression_strategy.hpp>
18 #include <boost/http/zlib/deflate.hpp>
19 #include <boost/http/zlib/error.hpp>
20 #include <boost/http/zlib/flush.hpp>
21 #include <boost/http/zlib/stream.hpp>
22 #include <boost/http/zstd/compress.hpp>
23 #include <boost/http/zstd/types.hpp>
24
25 #include <algorithm>
26 #include <cstdint>
27 #include <limits>
28 #include <new>
29 #include <system_error>
30
31 namespace boost
32 {
33 namespace burl
34 {
35 namespace detail
36 {
37
38 namespace
39 {
40
41 class zlib_encoder final
42 : public encoder
43 {
44 http::zlib::deflate_service& svc_;
45 http::zlib::stream strm_ = {};
46
47 public:
48 // gzip selects the gzip wrapper over zlib's.
49 16x zlib_encoder(
50 http::zlib::deflate_service& svc,
51 encoder_config::zlib_settings const& cfg,
52 bool gzip)
53 16x : svc_(svc)
54 {
55 // with the settings clamped, only
56 // allocation can fail
57 80x if(svc_.init2(
58 16x strm_,
59
2/2
✓ Branch 1 taken 16 times.
✓ Branch 4 taken 16 times.
16x std::clamp(cfg.level, 0, 9),
60 http::zlib::deflated,
61 ✗ std::clamp(cfg.window_bits, 9, 15) +
62
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 7 times.
16x (gzip ? 16 : 0),
63
2/2
✓ Branch 1 taken 16 times.
✓ Branch 4 taken 16 times.
16x std::clamp(cfg.mem_level, 1, 9),
64
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16x http::zlib::default_strategy) !=
65 static_cast<int>(http::zlib::error::ok))
66 ✗ throw std::bad_alloc();
67 16x }
68
69 32x ~zlib_encoder() override
70 16x {
71 16x svc_.deflate_end(strm_);
72 32x }
73
74 result
75 313x process(
76 capy::mutable_buffer out,
77 capy::const_buffer in,
78 bool more) override
79 {
80 313x strm_.next_in = static_cast<unsigned char*>(
81 313x const_cast<void*>(in.data()));
82 313x strm_.avail_in = saturate(in.size());
83 313x strm_.next_out =
84 313x static_cast<unsigned char*>(out.data());
85 313x strm_.avail_out = saturate(out.size());
86
87 auto const rs = static_cast<http::zlib::error>(
88
2/2
✓ Branch 0 taken 268 times.
✓ Branch 1 taken 45 times.
313x svc_.deflate(
89
1/1
✓ Branch 1 taken 313 times.
313x strm_,
90 more ? http::zlib::no_flush
91 313x : http::zlib::finish));
92
93 939x auto const ec = [&]() -> std::error_code
94 {
95
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 299 times.
313x if(rs == http::zlib::error::stream_end)
96 14x return capy::error::eof;
97
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 299 times.
299x if(rs != http::zlib::error::ok &&
98 ✗ rs != http::zlib::error::buf_err)
99 ✗ return rs;
100 299x return {};
101 313x }();
102
103 return {
104 313x .consumed = saturate(in.size()) - strm_.avail_in,
105 313x .produced = saturate(out.size()) - strm_.avail_out,
106 626x .ec = ec };
107 }
108
109 static
110 unsigned
111 1252x saturate(std::size_t n) noexcept
112 {
113 1252x constexpr auto max =
114 (std::numeric_limits<unsigned>::max)();
115
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1252 times.
1252x if(n >= max)
116 ✗ return max;
117 1252x return static_cast<unsigned>(n);
118 }
119 };
120
121 class brotli_encoder final
122 : public encoder
123 {
124 http::brotli::encode_service& svc_;
125 http::brotli::encoder_state* state_;
126
127 public:
128 9x brotli_encoder(
129 http::brotli::encode_service& svc,
130 encoder_config::brotli_settings const& cfg)
131 18x : svc_(svc)
132 18x , state_(svc.create_instance(
133 9x nullptr, nullptr, nullptr))
134 {
135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9x if(!state_)
136 ✗ throw std::bad_alloc();
137
138 using http::brotli::encoder_parameter;
139 9x set(encoder_parameter::quality,
140 9x std::clamp<int>(
141 9x cfg.quality,
142 ✗ http::brotli::min_quality,
143
1/1
✓ Branch 1 taken 9 times.
9x http::brotli::max_quality));
144 9x set(encoder_parameter::lgwin,
145 9x std::clamp<int>(
146 9x cfg.lgwin,
147 ✗ http::brotli::min_window_bits,
148
1/1
✓ Branch 1 taken 9 times.
9x http::brotli::max_window_bits));
149
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9x if(cfg.lgblock != 0)
150 2x set(encoder_parameter::lgblock,
151 2x std::clamp<int>(
152 2x cfg.lgblock,
153 ✗ http::brotli::min_input_block_bits,
154
1/1
✓ Branch 1 taken 2 times.
4x http::brotli::max_input_block_bits));
155 9x set(encoder_parameter::mode,
156 9x static_cast<int>(cfg.mode));
157 9x }
158
159 18x ~brotli_encoder() override
160 9x {
161 9x svc_.destroy_instance(state_);
162 18x }
163
164 result
165 158x process(
166 capy::mutable_buffer out,
167 capy::const_buffer in,
168 bool more) override
169 {
170 158x auto* next_in = static_cast<std::uint8_t const*>(in.data());
171 158x auto available_in = in.size();
172 158x auto* next_out = static_cast<std::uint8_t*>(out.data());
173 158x auto available_out = out.size();
174
175
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 17 times.
158x auto const ok = svc_.compress_stream(
176 state_,
177 more ? http::brotli::encoder_operation::process
178 : http::brotli::encoder_operation::finish,
179 &available_in,
180 &next_in,
181 &available_out,
182 &next_out,
183 158x nullptr);
184
185 474x auto const ec = [&]() -> std::error_code
186 {
187 // brotli reports no error code; failure
188 // means its lazily allocated state could
189 // not be obtained
190
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 158 times.
158x if(!ok)
191 ✗ return make_error_code(
192 ✗ std::errc::not_enough_memory);
193
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 150 times.
158x if(svc_.is_finished(state_))
194 8x return capy::error::eof;
195 150x return {};
196 158x }();
197
198 return {
199 158x .consumed = in.size() - available_in,
200 316x .produced = out.size() - available_out,
201 158x .ec = ec };
202 }
203
204 private:
205 void
206 29x set(http::brotli::encoder_parameter p, int v) noexcept
207 {
208 // fails only for an unknown parameter or
209 // once the stream has started
210
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 29 times.
29x BOOST_VERIFY(svc_.set_parameter(
211 state_, p, static_cast<std::uint32_t>(v)));
212 29x }
213 };
214
215 class zstd_encoder final
216 : public encoder
217 {
218 http::zstd::compress_service& svc_;
219 http::zstd::cctx* ctx_;
220
221 public:
222 9x zstd_encoder(
223 http::zstd::compress_service& svc,
224 encoder_config::zstd_settings const& cfg)
225 18x : svc_(svc)
226 9x , ctx_(svc.create_cctx())
227 {
228
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9x if(!ctx_)
229 ✗ throw std::bad_alloc();
230
231 using http::zstd::c_parameter;
232 9x set(c_parameter::compression_level, cfg.level);
233
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 7 times.
9x if(cfg.window_log != 0)
234 2x set(c_parameter::window_log, cfg.window_log);
235
2/2
✓ Branch 1 taken 1 time.
✓ Branch 2 taken 8 times.
9x if(cfg.strategy)
236 1x set(c_parameter::strategy,
237 1x static_cast<int>(*cfg.strategy));
238 9x }
239
240 18x ~zstd_encoder() override
241 9x {
242 9x svc_.free_cctx(ctx_);
243 18x }
244
245 result
246 166x process(
247 capy::mutable_buffer out,
248 capy::const_buffer in,
249 bool more) override
250 {
251 166x http::zstd::in_buffer in_buf{ in.data(), in.size(), 0 };
252 166x http::zstd::out_buffer out_buf{ out.data(), out.size(), 0 };
253
254
2/2
✓ Branch 0 taken 141 times.
✓ Branch 1 taken 25 times.
166x auto const rs = svc_.compress_stream(
255 ctx_,
256 out_buf,
257 in_buf,
258 more ? http::zstd::end_directive::continue_
259 166x : http::zstd::end_directive::end);
260
261 498x auto const ec = [&]() -> std::error_code
262 {
263
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 166 times.
166x if(svc_.is_error(rs))
264 ✗ return svc_.get_error_code(rs);
265 // zero reports the frame complete and
266 // fully flushed
267
4/4
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 141 times.
✓ Branch 2 taken 8 times.
✓ Branch 3 taken 17 times.
166x if(!more && rs == 0 &&
268
1/2
✓ Branch 0 taken 8 times.
✗ Branch 1 not taken.
8x in_buf.pos == in_buf.size)
269 8x return capy::error::eof;
270 158x return {};
271 166x }();
272
273 return {
274 166x .consumed = in_buf.pos,
275 166x .produced = out_buf.pos,
276 166x .ec = ec };
277 }
278
279 private:
280 // clamps to the bounds the library reports
281 void
282 12x set(http::zstd::c_parameter p, int v) noexcept
283 {
284 12x auto const b = svc_.param_bounds(p);
285
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 12 times.
12x BOOST_ASSERT(!svc_.is_error(b.error));
286
1/2
✗ Branch 3 not taken.
✓ Branch 4 taken 12 times.
12x BOOST_VERIFY(!svc_.is_error(svc_.set_parameter(
287 ctx_,
288 p,
289 std::clamp(v, b.lower_bound, b.upper_bound))));
290 12x }
291 };
292
293 } // namespace
294
295 std::unique_ptr<encoder>
296 37x make_encoder(
297 http::content_coding coding,
298 encoder_config const& cfg)
299 {
300 37x auto const& ctx = capy::get_system_context();
301
5/5
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 9 times.
✓ Branch 2 taken 9 times.
✓ Branch 3 taken 9 times.
✓ Branch 4 taken 3 times.
37x switch(coding)
302 {
303 7x case http::content_coding::deflate:
304
1/2
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
7x if(auto* svc = ctx.find_service<
305 7x http::zlib::deflate_service>())
306 7x return std::make_unique<zlib_encoder>(
307
1/1
✓ Branch 1 taken 7 times.
14x *svc, cfg.zlib, false);
308 ✗ break;
309 9x case http::content_coding::gzip:
310
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9x if(auto* svc = ctx.find_service<
311 9x http::zlib::deflate_service>())
312 9x return std::make_unique<zlib_encoder>(
313
1/1
✓ Branch 1 taken 9 times.
18x *svc, cfg.zlib, true);
314 ✗ break;
315 9x case http::content_coding::br:
316
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9x if(auto* svc = ctx.find_service<
317 9x http::brotli::encode_service>())
318 9x return std::make_unique<brotli_encoder>(
319
1/1
✓ Branch 1 taken 9 times.
9x *svc, cfg.brotli);
320 ✗ break;
321 9x case http::content_coding::zstd:
322
1/2
✓ Branch 0 taken 9 times.
✗ Branch 1 not taken.
9x if(auto* svc = ctx.find_service<
323 9x http::zstd::compress_service>())
324 9x return std::make_unique<zstd_encoder>(
325
1/1
✓ Branch 1 taken 9 times.
9x *svc, cfg.zstd);
326 ✗ break;
327 3x default:
328 3x break;
329 }
330 3x return nullptr;
331 }
332
333 } // namespace detail
334 } // namespace burl
335 } // namespace boost
336