GCC Code Coverage Report


Directory: ./
File: libs/http_proto/src/serializer.cpp
Date: 2025-12-02 19:05:14
Exec Total Coverage
Lines: 420 478 87.9%
Functions: 44 47 93.6%
Branches: 233 287 81.2%

Line Branch Exec Source
1 //
2 // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2024 Christian Mazakas
4 // Copyright (c) 2024 Mohammad Nejati
5 //
6 // Distributed under the Boost Software License, Version 1.0. (See accompanying
7 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
8 //
9 // Official repository: https://github.com/cppalliance/http_proto
10 //
11
12 #include <boost/http_proto/detail/except.hpp>
13 #include <boost/http_proto/detail/header.hpp>
14 #include <boost/http_proto/message_base.hpp>
15 #include <boost/http_proto/serializer.hpp>
16
17 #include "src/detail/array_of_const_buffers.hpp"
18 #include "src/detail/brotli_filter_base.hpp"
19 #include "src/detail/buffer_utils.hpp"
20 #include "src/detail/zlib_filter_base.hpp"
21
22 #include <boost/buffers/circular_buffer.hpp>
23 #include <boost/buffers/copy.hpp>
24 #include <boost/core/bit.hpp>
25 #include <boost/core/ignore_unused.hpp>
26 #include <boost/capy/brotli/encode.hpp>
27 #include <boost/capy/polystore.hpp>
28 #include <boost/capy/zlib/compression_method.hpp>
29 #include <boost/capy/zlib/compression_strategy.hpp>
30 #include <boost/capy/zlib/deflate.hpp>
31 #include <boost/capy/zlib/error.hpp>
32 #include <boost/capy/zlib/flush.hpp>
33
34 #include <stddef.h>
35
36 namespace boost {
37 namespace http_proto {
38
39 namespace {
40
41 const
42 buffers::const_buffer
43 crlf_and_final_chunk = {"\r\n0\r\n\r\n", 7};
44
45 const
46 buffers::const_buffer
47 crlf = {"\r\n", 2};
48
49 const
50 buffers::const_buffer
51 final_chunk = {"0\r\n\r\n", 5};
52
53 constexpr
54 std::uint8_t
55 76 chunk_header_len(
56 std::size_t max_chunk_size) noexcept
57 {
58 return
59 static_cast<uint8_t>(
60 76 (core::bit_width(max_chunk_size) + 3) / 4 +
61 76 2); // crlf
62 };
63
64 void
65 1146 write_chunk_header(
66 const buffers::mutable_buffer_pair& mbs,
67 std::size_t size) noexcept
68 {
69 static constexpr char hexdig[] =
70 "0123456789ABCDEF";
71 char buf[18];
72 1146 auto p = buf + 16;
73 1146 auto const n = buffers::size(mbs);
74
2/2
✓ Branch 0 taken 4583 times.
✓ Branch 1 taken 1146 times.
5729 for(std::size_t i = n - 2; i--;)
75 {
76 4583 *--p = hexdig[size & 0xf];
77 4583 size >>= 4;
78 }
79 1146 buf[16] = '\r';
80 1146 buf[17] = '\n';
81 1146 auto copied = buffers::copy(
82 mbs,
83 2292 buffers::const_buffer(p, n));
84 ignore_unused(copied);
85
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1146 times.
1146 BOOST_ASSERT(copied == n);
86 1146 }
87
88 //------------------------------------------------
89
90 class zlib_filter
91 : public detail::zlib_filter_base
92 {
93 capy::zlib::deflate_service& svc_;
94
95 public:
96 52 zlib_filter(
97 const capy::polystore& ctx,
98 http_proto::detail::workspace& ws,
99 int comp_level,
100 int window_bits,
101 int mem_level)
102 52 : zlib_filter_base(ws)
103 52 , svc_(ctx.get<capy::zlib::deflate_service>())
104 {
105 104 system::error_code ec = static_cast<capy::zlib::error>(svc_.init2(
106
1/1
✓ Branch 1 taken 52 times.
52 strm_,
107 comp_level,
108 capy::zlib::deflated,
109 window_bits,
110 mem_level,
111 52 capy::zlib::default_strategy));
112
1/2
✗ Branch 2 not taken.
✓ Branch 3 taken 52 times.
52 if(ec != capy::zlib::error::ok)
113 detail::throw_system_error(ec);
114 52 }
115
116 private:
117 virtual
118 std::size_t
119 86 min_out_buffer() const noexcept override
120 {
121 // Prevents deflate from producing
122 // zero output due to small buffer
123 86 return 8;
124 }
125
126 virtual
127 results
128 4922 do_process(
129 buffers::mutable_buffer out,
130 buffers::const_buffer in,
131 bool more) noexcept override
132 {
133 4922 strm_.next_out = static_cast<unsigned char*>(out.data());
134 4922 strm_.avail_out = saturate_cast(out.size());
135 4922 strm_.next_in = static_cast<unsigned char*>(const_cast<void *>(in.data()));
136 4922 strm_.avail_in = saturate_cast(in.size());
137
138 auto rs = static_cast<capy::zlib::error>(
139
2/2
✓ Branch 0 taken 4836 times.
✓ Branch 1 taken 86 times.
4922 svc_.deflate(
140 4922 strm_,
141 more ? capy::zlib::no_flush : capy::zlib::finish));
142
143 4922 results rv;
144 4922 rv.out_bytes = saturate_cast(out.size()) - strm_.avail_out;
145 4922 rv.in_bytes = saturate_cast(in.size()) - strm_.avail_in;
146 4922 rv.finished = (rs == capy::zlib::error::stream_end);
147
148
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 4922 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
4922 if(rs < capy::zlib::error::ok && rs != capy::zlib::error::buf_err)
149 rv.ec = rs;
150
151 4922 return rv;
152 }
153 };
154
155 class brotli_filter
156 : public detail::brotli_filter_base
157 {
158 capy::brotli::encode_service& svc_;
159 capy::brotli::encoder_state* state_;
160
161 public:
162 brotli_filter(
163 const capy::polystore& ctx,
164 http_proto::detail::workspace&,
165 std::uint32_t comp_quality,
166 std::uint32_t comp_window)
167 : svc_(ctx.get<capy::brotli::encode_service>())
168 {
169 // TODO: use custom allocator
170 state_ = svc_.create_instance(nullptr, nullptr, nullptr);
171 if(!state_)
172 detail::throw_bad_alloc();
173 using encoder_parameter = capy::brotli::encoder_parameter;
174 svc_.set_parameter(state_, encoder_parameter::quality, comp_quality);
175 svc_.set_parameter(state_, encoder_parameter::lgwin, comp_window);
176 }
177
178 ~brotli_filter()
179 {
180 svc_.destroy_instance(state_);
181 }
182
183 private:
184 virtual
185 results
186 do_process(
187 buffers::mutable_buffer out,
188 buffers::const_buffer in,
189 bool more) noexcept override
190 {
191 auto* next_in = reinterpret_cast<const std::uint8_t*>(in.data());
192 auto available_in = in.size();
193 auto* next_out = reinterpret_cast<std::uint8_t*>(out.data());
194 auto available_out = out.size();
195
196 using encoder_operation =
197 capy::brotli::encoder_operation;
198
199 bool rs = svc_.compress_stream(
200 state_,
201 more ? encoder_operation::process : encoder_operation::finish,
202 &available_in,
203 &next_in,
204 &available_out,
205 &next_out,
206 nullptr);
207
208 results rv;
209 rv.in_bytes = in.size() - available_in;
210 rv.out_bytes = out.size() - available_out;
211 rv.finished = svc_.is_finished(state_);
212
213 // TODO: use proper error code
214 if(rs == false)
215 rv.ec = error::bad_payload;
216
217 return rv;
218 }
219 };
220
221 template<class UInt>
222 std::size_t
223 8 clamp(
224 UInt x,
225 std::size_t limit = (std::numeric_limits<
226 std::size_t>::max)()) noexcept
227 {
228
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if(x >= limit)
229 2 return limit;
230 6 return static_cast<std::size_t>(x);
231 }
232
233 class serializer_service
234 {
235 public:
236 serializer::config cfg;
237 std::size_t space_needed = 0;
238
239 24 serializer_service(
240 serializer::config const& cfg_)
241 24 : cfg(cfg_)
242 {
243 24 space_needed += cfg.payload_buffer;
244 24 space_needed += cfg.max_type_erase;
245
246
3/4
✓ Branch 0 taken 23 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 23 times.
24 if(cfg.apply_deflate_encoder || cfg.apply_gzip_encoder)
247 {
248 // TODO: Account for the number of allocations and
249 // their overhead in the workspace.
250
251 // https://www.zlib.net/zlib_tech.html
252 1 space_needed +=
253 1 (1 << (cfg.zlib_window_bits + 2)) +
254 1 (1 << (cfg.zlib_mem_level + 9)) +
255 1 (6 * 1024) +
256 #ifdef __s390x__
257 5768 +
258 #endif
259 1 detail::workspace::space_needed<zlib_filter>();
260 }
261 24 }
262 };
263
264 } // namespace
265
266 //------------------------------------------------
267
268 void
269 24 install_serializer_service(
270 capy::polystore& ctx,
271 serializer::config const& cfg)
272 {
273 24 ctx.emplace<serializer_service>(cfg);
274 24 }
275
276 //------------------------------------------------
277
278 class serializer::impl
279 {
280 friend stream;
281
282 enum class state
283 {
284 reset,
285 start,
286 header,
287 body
288 };
289
290 enum class style
291 {
292 empty,
293 buffers,
294 source,
295 stream
296 };
297
298 const capy::polystore& ctx_;
299 serializer_service& svc_;
300 detail::workspace ws_;
301
302 detail::filter* filter_ = nullptr;
303 cbs_gen* cbs_gen_ = nullptr;
304 source* source_ = nullptr;
305
306 buffers::circular_buffer out_;
307 buffers::circular_buffer in_;
308 detail::array_of_const_buffers prepped_;
309 buffers::const_buffer tmp_;
310
311 state state_ = state::start;
312 style style_ = style::empty;
313 uint8_t chunk_header_len_ = 0;
314 bool more_input_ = false;
315 bool is_chunked_ = false;
316 bool needs_exp100_continue_ = false;
317 bool filter_done_ = false;
318
319 public:
320 25 impl(const capy::polystore& ctx)
321 25 : ctx_(ctx)
322 25 , svc_(ctx_.get<serializer_service>())
323 25 , ws_(svc_.space_needed)
324 {
325 25 }
326
327 void
328 82 reset() noexcept
329 {
330 82 ws_.clear();
331 82 state_ = state::start;
332 82 }
333
334 auto
335 2430 prepare() ->
336 system::result<const_buffers_type>
337 {
338 // Precondition violation
339
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2428 times.
2430 if(state_ < state::header)
340 2 detail::throw_logic_error();
341
342 // Expect: 100-continue
343
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 2424 times.
2428 if(needs_exp100_continue_)
344 {
345
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 if(!is_header_done())
346 4 return const_buffers_type(
347 prepped_.begin(),
348 2 1); // limit to header
349
350 2 needs_exp100_continue_ = false;
351
352 2 BOOST_HTTP_PROTO_RETURN_EC(
353 error::expect_100_continue);
354 }
355
356
2/2
✓ Branch 0 taken 62 times.
✓ Branch 1 taken 2362 times.
2424 if(!filter_)
357 {
358
4/5
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 23 times.
✓ Branch 3 taken 13 times.
✗ Branch 4 not taken.
62 switch(style_)
359 {
360 6 case style::empty:
361 6 break;
362
363 20 case style::buffers:
364 {
365 // add more buffers if prepped_ is half empty.
366
6/6
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 10 times.
✓ Branch 2 taken 4 times.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 16 times.
30 if(more_input_ &&
367 10 prepped_.capacity() >= prepped_.size())
368 {
369 4 prepped_.slide_to_front();
370
2/2
✓ Branch 1 taken 48 times.
✓ Branch 2 taken 2 times.
50 while(prepped_.capacity() != 0)
371 {
372
1/1
✓ Branch 1 taken 48 times.
48 auto buf = cbs_gen_->next();
373
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 46 times.
48 if(buf.size() == 0)
374 2 break;
375 46 prepped_.append(buf);
376 }
377
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 if(cbs_gen_->is_empty())
378 {
379
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if(is_chunked_)
380 {
381
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 if(prepped_.capacity() != 0)
382 {
383 1 prepped_.append(
384 crlf_and_final_chunk);
385 1 more_input_ = false;
386 }
387 }
388 else
389 {
390 1 more_input_ = false;
391 }
392 }
393 }
394
1/1
✓ Branch 1 taken 20 times.
20 return detail::make_span(prepped_);
395 }
396
397 23 case style::source:
398 {
399
5/6
✓ Branch 1 taken 23 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 16 times.
✓ Branch 4 taken 7 times.
✓ Branch 5 taken 16 times.
✓ Branch 6 taken 7 times.
23 if(out_capacity() == 0 || !more_input_)
400 22 break;
401
402
1/1
✓ Branch 1 taken 7 times.
7 const auto rs = source_->read(
403 7 out_prepare());
404
405 7 out_commit(rs.bytes);
406
407
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 6 times.
7 if(rs.ec.failed())
408 {
409 1 ws_.clear();
410 1 state_ = state::reset;
411 1 return rs.ec;
412 }
413
414
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if(rs.finished)
415 {
416 6 more_input_ = false;
417 6 out_finish();
418 }
419
420 6 break;
421 }
422
423 13 case style::stream:
424
8/8
✓ Branch 1 taken 7 times.
✓ Branch 2 taken 6 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 3 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 1 times.
✓ Branch 8 taken 3 times.
✓ Branch 9 taken 10 times.
13 if(out_.size() == 0 && is_header_done() && more_input_)
425 3 BOOST_HTTP_PROTO_RETURN_EC(
426 error::need_data);
427 10 break;
428 }
429 }
430 else // filter
431 {
432
4/5
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 376 times.
✓ Branch 2 taken 734 times.
✓ Branch 3 taken 1248 times.
✗ Branch 4 not taken.
2362 switch(style_)
433 {
434 4 case style::empty:
435 {
436
3/6
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
4 if(out_capacity() == 0 || filter_done_)
437 4 break;
438
439
2/2
✓ Branch 1 taken 4 times.
✓ Branch 4 taken 4 times.
4 const auto rs = filter_->process(
440
1/1
✓ Branch 2 taken 4 times.
4 detail::make_span(out_prepare()),
441 {}, // empty input
442 false);
443
444
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
4 if(rs.ec.failed())
445 {
446 ws_.clear();
447 state_ = state::reset;
448 return rs.ec;
449 }
450
451 4 out_commit(rs.out_bytes);
452
453
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if(rs.finished)
454 {
455 4 filter_done_ = true;
456 4 out_finish();
457 }
458
459 4 break;
460 }
461
462 376 case style::buffers:
463 {
464
6/6
✓ Branch 1 taken 2212 times.
✓ Branch 2 taken 360 times.
✓ Branch 3 taken 2196 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 2196 times.
✓ Branch 6 taken 376 times.
2572 while(out_capacity() != 0 && !filter_done_)
465 {
466
6/6
✓ Branch 0 taken 2188 times.
✓ Branch 1 taken 8 times.
✓ Branch 3 taken 2008 times.
✓ Branch 4 taken 180 times.
✓ Branch 5 taken 2008 times.
✓ Branch 6 taken 188 times.
2196 if(more_input_ && tmp_.size() == 0)
467 {
468
1/1
✓ Branch 1 taken 2008 times.
2008 tmp_ = cbs_gen_->next();
469
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 1992 times.
2008 if(tmp_.size() == 0) // cbs_gen_ is empty
470 16 more_input_ = false;
471 }
472
473
2/2
✓ Branch 1 taken 2196 times.
✓ Branch 4 taken 2196 times.
2196 const auto rs = filter_->process(
474
1/1
✓ Branch 1 taken 2196 times.
2196 detail::make_span(out_prepare()),
475 {{ {tmp_}, {} }},
476 2196 more_input_);
477
478
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2196 times.
2196 if(rs.ec.failed())
479 {
480 ws_.clear();
481 state_ = state::reset;
482 return rs.ec;
483 }
484
485 2196 buffers::remove_prefix(tmp_, rs.in_bytes);
486 2196 out_commit(rs.out_bytes);
487
488
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2196 times.
2196 if(rs.out_short)
489 break;
490
491
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 2180 times.
2196 if(rs.finished)
492 {
493 16 filter_done_ = true;
494 16 out_finish();
495 }
496 }
497 376 break;
498 }
499
500 734 case style::source:
501 {
502
6/6
✓ Branch 1 taken 1462 times.
✓ Branch 2 taken 718 times.
✓ Branch 3 taken 1446 times.
✓ Branch 4 taken 16 times.
✓ Branch 5 taken 1446 times.
✓ Branch 6 taken 734 times.
2180 while(out_capacity() != 0 && !filter_done_)
503 {
504
6/6
✓ Branch 0 taken 1432 times.
✓ Branch 1 taken 14 times.
✓ Branch 3 taken 984 times.
✓ Branch 4 taken 448 times.
✓ Branch 5 taken 984 times.
✓ Branch 6 taken 462 times.
1446 if(more_input_ && in_.capacity() != 0)
505 {
506
1/1
✓ Branch 1 taken 984 times.
984 const auto rs = source_->read(
507
1/1
✓ Branch 2 taken 984 times.
984 in_.prepare(in_.capacity()));
508
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 984 times.
984 if(rs.ec.failed())
509 {
510 ws_.clear();
511 state_ = state::reset;
512 return rs.ec;
513 }
514
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 968 times.
984 if(rs.finished)
515 16 more_input_ = false;
516 984 in_.commit(rs.bytes);
517 }
518
519
2/2
✓ Branch 1 taken 1446 times.
✓ Branch 4 taken 1446 times.
1446 const auto rs = filter_->process(
520
1/1
✓ Branch 2 taken 1446 times.
1446 detail::make_span(out_prepare()),
521 in_.data(),
522 1446 more_input_);
523
524
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1446 times.
1446 if(rs.ec.failed())
525 {
526 ws_.clear();
527 state_ = state::reset;
528 return rs.ec;
529 }
530
531 1446 in_.consume(rs.in_bytes);
532 1446 out_commit(rs.out_bytes);
533
534
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1446 times.
1446 if(rs.out_short)
535 break;
536
537
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1430 times.
1446 if(rs.finished)
538 {
539 16 filter_done_ = true;
540 16 out_finish();
541 }
542 }
543 734 break;
544 }
545
546 1248 case style::stream:
547 {
548
3/6
✓ Branch 1 taken 1248 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 1248 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1248 times.
1248 if(out_capacity() == 0 || filter_done_)
549 804 break;
550
551
2/2
✓ Branch 1 taken 1248 times.
✓ Branch 4 taken 1248 times.
1248 const auto rs = filter_->process(
552
1/1
✓ Branch 2 taken 1248 times.
1248 detail::make_span(out_prepare()),
553 in_.data(),
554 1248 more_input_);
555
556
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1248 times.
1248 if(rs.ec.failed())
557 {
558 ws_.clear();
559 state_ = state::reset;
560 444 return rs.ec;
561 }
562
563 1248 in_.consume(rs.in_bytes);
564 1248 out_commit(rs.out_bytes);
565
566
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 1232 times.
1248 if(rs.finished)
567 {
568 16 filter_done_ = true;
569 16 out_finish();
570 }
571
572
6/8
✓ Branch 1 taken 444 times.
✓ Branch 2 taken 804 times.
✓ Branch 4 taken 444 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 444 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 444 times.
✓ Branch 9 taken 804 times.
1248 if(out_.size() == 0 && is_header_done() && more_input_)
573 444 BOOST_HTTP_PROTO_RETURN_EC(
574 error::need_data);
575 804 break;
576 }
577 }
578 }
579
580 1956 prepped_.reset(!is_header_done());
581
2/2
✓ Branch 1 taken 3912 times.
✓ Branch 2 taken 1956 times.
5868 for(auto const& cb : out_.data())
582 {
583
2/2
✓ Branch 1 taken 1948 times.
✓ Branch 2 taken 1964 times.
3912 if(cb.size() != 0)
584 1948 prepped_.append(cb);
585 }
586
1/1
✓ Branch 1 taken 1956 times.
1956 return detail::make_span(prepped_);
587 }
588
589 void
590 3717 consume(
591 std::size_t n)
592 {
593 // Precondition violation
594
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 3716 times.
3717 if(state_ < state::header)
595 1 detail::throw_logic_error();
596
597
2/2
✓ Branch 1 taken 85 times.
✓ Branch 2 taken 3631 times.
3716 if(!is_header_done())
598 {
599 const auto header_remain =
600 85 prepped_[0].size();
601
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 73 times.
85 if(n < header_remain)
602 {
603 12 prepped_.consume(n);
604 12 return;
605 }
606 73 n -= header_remain;
607 73 prepped_.consume(header_remain);
608 73 state_ = state::body;
609 }
610
611 3704 prepped_.consume(n);
612
613 // no-op when out_ is not in use
614 3704 out_.consume(n);
615
616
2/2
✓ Branch 1 taken 1759 times.
✓ Branch 2 taken 1945 times.
3704 if(!prepped_.empty())
617 1759 return;
618
619
2/2
✓ Branch 0 taken 1837 times.
✓ Branch 1 taken 108 times.
1945 if(more_input_)
620 1837 return;
621
622
4/4
✓ Branch 0 taken 86 times.
✓ Branch 1 taken 22 times.
✓ Branch 2 taken 34 times.
✓ Branch 3 taken 52 times.
108 if(filter_ && !filter_done_)
623 34 return;
624
625
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 73 times.
74 if(needs_exp100_continue_)
626 1 return;
627
628 // ready for next message
629 73 reset();
630 }
631
632 void
633 84 start_init(
634 message_base const& m)
635 {
636 // Precondition violation
637
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 83 times.
84 if(state_ != state::start)
638 1 detail::throw_logic_error();
639
640 // TODO: To uphold the strong exception guarantee,
641 // `state_` must be reset to `state::start` if an
642 // exception is thrown during the start operation.
643 83 state_ = state::header;
644
645 // VFALCO what do we do with
646 // metadata error code failures?
647 // m.h_.md.maybe_throw();
648
649 83 auto const& md = m.metadata();
650 83 needs_exp100_continue_ = md.expect.is_100_continue;
651
652 // Transfer-Encoding
653 83 is_chunked_ = md.transfer_encoding.is_chunked;
654
655 // Content-Encoding
656
3/4
✓ Branch 0 taken 26 times.
✓ Branch 1 taken 26 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 31 times.
83 switch (md.content_encoding.coding)
657 {
658 26 case content_coding::deflate:
659
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if(!svc_.cfg.apply_deflate_encoder)
660 goto no_filter;
661 52 filter_ = &ws_.emplace<zlib_filter>(
662 ctx_,
663 26 ws_,
664 26 svc_.cfg.zlib_comp_level,
665 26 svc_.cfg.zlib_window_bits,
666 26 svc_.cfg.zlib_mem_level);
667 26 filter_done_ = false;
668 26 break;
669
670 26 case content_coding::gzip:
671
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 26 times.
26 if(!svc_.cfg.apply_gzip_encoder)
672 goto no_filter;
673 52 filter_ = &ws_.emplace<zlib_filter>(
674 ctx_,
675 26 ws_,
676 26 svc_.cfg.zlib_comp_level,
677 52 svc_.cfg.zlib_window_bits + 16,
678
1/1
✓ Branch 1 taken 26 times.
26 svc_.cfg.zlib_mem_level);
679 26 filter_done_ = false;
680 26 break;
681
682 case content_coding::br:
683 if(!svc_.cfg.apply_brotli_encoder)
684 goto no_filter;
685 filter_ = &ws_.emplace<brotli_filter>(
686 ctx_,
687 ws_,
688 svc_.cfg.brotli_comp_quality,
689 svc_.cfg.brotli_comp_window);
690 filter_done_ = false;
691 break;
692
693 no_filter:
694 31 default:
695 31 filter_ = nullptr;
696 31 break;
697 }
698 83 }
699
700 void
701 12 start_empty(
702 message_base const& m)
703 {
704 12 start_init(m);
705 11 style_ = style::empty;
706
707
1/1
✓ Branch 1 taken 11 times.
11 prepped_ = make_array(
708 1 + // header
709 2); // out buffer pairs
710
711 11 out_init();
712
713
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 4 times.
11 if(!filter_)
714 7 out_finish();
715
716 11 prepped_.append({ m.h_.cbuf, m.h_.size });
717 11 more_input_ = false;
718 11 }
719
720 void
721 24 start_buffers(
722 message_base const& m,
723 cbs_gen& cbs_gen)
724 {
725 // start_init() already called
726 24 style_ = style::buffers;
727 24 cbs_gen_ = &cbs_gen;
728
729
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 16 times.
24 if(!filter_)
730 {
731
1/1
✓ Branch 1 taken 8 times.
8 auto stats = cbs_gen_->stats();
732 8 auto batch_size = clamp(stats.count, 16);
733
734 prepped_ = make_array(
735 1 + // header
736
1/1
✓ Branch 1 taken 8 times.
8 batch_size + // buffers
737
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 (is_chunked_ ? 2 : 0)); // chunk header + final chunk
738
739 8 prepped_.append({ m.h_.cbuf, m.h_.size });
740 8 more_input_ = (batch_size != 0);
741
742
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if(is_chunked_)
743 {
744
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if(!more_input_)
745 {
746 1 prepped_.append(final_chunk);
747 }
748 else
749 {
750 1 auto h_len = chunk_header_len(stats.size);
751 buffers::mutable_buffer mb(
752
1/1
✓ Branch 1 taken 1 times.
1 ws_.reserve_front(h_len), h_len);
753 1 write_chunk_header({{ {mb}, {} }}, stats.size);
754 1 prepped_.append(mb);
755 }
756 }
757 8 return;
758 }
759
760 // filter
761
762
1/1
✓ Branch 1 taken 16 times.
16 prepped_ = make_array(
763 1 + // header
764 2); // out buffer pairs
765
766 16 out_init();
767
768 16 prepped_.append({ m.h_.cbuf, m.h_.size });
769 16 tmp_ = {};
770 16 more_input_ = true;
771 }
772
773 void
774 25 start_source(
775 message_base const& m,
776 source& source)
777 {
778 // start_init() already called
779 25 style_ = style::source;
780 25 source_ = &source;
781
782
1/1
✓ Branch 1 taken 25 times.
25 prepped_ = make_array(
783 1 + // header
784 2); // out buffer pairs
785
786
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 9 times.
25 if(filter_)
787 {
788 // TODO: smarter buffer distribution
789 16 auto const n = (ws_.size() - 1) / 2;
790
1/1
✓ Branch 1 taken 16 times.
16 in_ = { ws_.reserve_front(n), n };
791 }
792
793 25 out_init();
794
795 25 prepped_.append({ m.h_.cbuf, m.h_.size });
796 25 more_input_ = true;
797 25 }
798
799 stream
800 23 start_stream(message_base const& m)
801 {
802 23 start_init(m);
803 23 style_ = style::stream;
804
805
1/1
✓ Branch 1 taken 23 times.
23 prepped_ = make_array(
806 1 + // header
807 2); // out buffer pairs
808
809
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 7 times.
23 if(filter_)
810 {
811 // TODO: smarter buffer distribution
812 16 auto const n = (ws_.size() - 1) / 2;
813
1/1
✓ Branch 1 taken 16 times.
16 in_ = { ws_.reserve_front(n), n };
814 }
815
816 23 out_init();
817
818 23 prepped_.append({ m.h_.cbuf, m.h_.size });
819 23 more_input_ = true;
820 23 return stream{ this };
821 }
822
823 bool
824 2483 is_done() const noexcept
825 {
826 2483 return state_ == state::start;
827 }
828
829 detail::workspace&
830 49 ws() noexcept
831 {
832 49 return ws_;
833 }
834
835 private:
836 bool
837 6127 is_header_done() const noexcept
838 {
839 6127 return state_ == state::body;
840 }
841
842 detail::array_of_const_buffers
843 83 make_array(std::size_t n)
844 {
845
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 83 times.
83 BOOST_ASSERT(n <= std::uint16_t(-1));
846
847 return {
848 83 ws_.push_array(n,
849 buffers::const_buffer{}),
850
1/1
✓ Branch 2 taken 83 times.
83 static_cast<std::uint16_t>(n) };
851 }
852
853 void
854 75 out_init()
855 {
856 // use all the remaining buffer
857 75 auto const n = ws_.size() - 1;
858
1/1
✓ Branch 1 taken 75 times.
75 out_ = { ws_.reserve_front(n), n };
859 75 chunk_header_len_ =
860 75 chunk_header_len(out_.capacity());
861
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 75 times.
75 if(out_capacity() == 0)
862 detail::throw_length_error();
863 75 }
864
865 buffers::mutable_buffer_pair
866 4907 out_prepare() noexcept
867 {
868 4907 auto mbp = out_.prepare(out_.capacity());
869
2/2
✓ Branch 0 taken 2453 times.
✓ Branch 1 taken 2454 times.
4907 if(is_chunked_)
870 {
871 2453 buffers::remove_prefix(
872 2453 mbp, chunk_header_len_);
873 2453 buffers::remove_suffix(
874 mbp, crlf_and_final_chunk.size());
875 }
876 4907 return mbp;
877 }
878
879 void
880 4907 out_commit(
881 std::size_t n) noexcept
882 {
883
2/2
✓ Branch 0 taken 2453 times.
✓ Branch 1 taken 2454 times.
4907 if(is_chunked_)
884 {
885
2/2
✓ Branch 0 taken 1308 times.
✓ Branch 1 taken 1145 times.
2453 if(n == 0)
886 1308 return;
887
888 1145 write_chunk_header(out_.prepare(chunk_header_len_), n);
889 1145 out_.commit(chunk_header_len_);
890
891 1145 out_.prepare(n);
892 1145 out_.commit(n);
893
894 1145 buffers::copy(out_.prepare(crlf.size()), crlf);
895 1145 out_.commit(crlf.size());
896 }
897 else
898 {
899 2454 out_.commit(n);
900 }
901 }
902
903 std::size_t
904 6126 out_capacity() const noexcept
905 {
906
2/2
✓ Branch 0 taken 3060 times.
✓ Branch 1 taken 3066 times.
6126 if(is_chunked_)
907 {
908 3060 auto const overhead = chunk_header_len_ +
909 3060 crlf_and_final_chunk.size();
910
2/2
✓ Branch 1 taken 541 times.
✓ Branch 2 taken 2519 times.
3060 if(out_.capacity() < overhead)
911 541 return 0;
912 2519 return out_.capacity() - overhead;
913 }
914 3066 return out_.capacity();
915 }
916
917 void
918 72 out_finish() noexcept
919 {
920
2/2
✓ Branch 0 taken 33 times.
✓ Branch 1 taken 39 times.
72 if(is_chunked_)
921 {
922 33 buffers::copy(
923 33 out_.prepare(final_chunk.size()), final_chunk);
924 33 out_.commit(final_chunk.size());
925 }
926 72 }
927 };
928
929 //------------------------------------------------
930
931 31 serializer::
932 ~serializer()
933 {
934
2/2
✓ Branch 0 taken 25 times.
✓ Branch 1 taken 6 times.
31 delete impl_;
935 31 }
936
937 1 serializer::
938 1 serializer(serializer&& other) noexcept
939 1 : impl_(other.impl_)
940 {
941 1 other.impl_ = nullptr;
942 1 }
943
944 serializer&
945 2 serializer::
946 operator=(serializer&& other) noexcept
947 {
948
1/2
✓ Branch 0 taken 2 times.
✗ Branch 1 not taken.
2 if(this != &other)
949 {
950
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 delete impl_;
951 2 impl_ = other.impl_;
952 2 other.impl_ = nullptr;
953 }
954 2 return *this;
955 }
956
957 25 serializer::
958 25 serializer(capy::polystore& ctx)
959
1/3
✓ Branch 2 taken 25 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
25 : impl_(new impl(ctx))
960 {
961 // TODO: use a single allocation for
962 // impl and workspace buffer.
963 25 }
964
965 void
966 9 serializer::
967 reset() noexcept
968 {
969
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9 times.
9 BOOST_ASSERT(impl_);
970 9 impl_->reset();
971 9 }
972
973 void
974 12 serializer::
975 start(message_base const& m)
976 {
977
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 12 times.
12 BOOST_ASSERT(impl_);
978 12 impl_->start_empty(m);
979 11 }
980
981 auto
982 23 serializer::
983 start_stream(
984 message_base const& m) -> stream
985 {
986
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 23 times.
23 BOOST_ASSERT(impl_);
987 23 return impl_->start_stream(m);
988 }
989
990 auto
991 2430 serializer::
992 prepare() ->
993 system::result<const_buffers_type>
994 {
995
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2430 times.
2430 BOOST_ASSERT(impl_);
996 2430 return impl_->prepare();
997 }
998
999 void
1000 3717 serializer::
1001 consume(std::size_t n)
1002 {
1003
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3717 times.
3717 BOOST_ASSERT(impl_);
1004 3717 impl_->consume(n);
1005 3716 }
1006
1007 bool
1008 2483 serializer::
1009 is_done() const noexcept
1010 {
1011
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2483 times.
2483 BOOST_ASSERT(impl_);
1012 2483 return impl_->is_done();
1013 }
1014
1015 //------------------------------------------------
1016
1017 detail::workspace&
1018 49 serializer::
1019 ws()
1020 {
1021
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 BOOST_ASSERT(impl_);
1022 49 return impl_->ws();
1023 }
1024
1025 void
1026 49 serializer::
1027 start_init(message_base const& m)
1028 {
1029
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 49 times.
49 BOOST_ASSERT(impl_);
1030 49 impl_->start_init(m);
1031 49 }
1032
1033 void
1034 24 serializer::
1035 start_buffers(
1036 message_base const& m,
1037 cbs_gen& cbs_gen)
1038 {
1039
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 24 times.
24 BOOST_ASSERT(impl_);
1040 24 impl_->start_buffers(m, cbs_gen);
1041 24 }
1042
1043 void
1044 25 serializer::
1045 start_source(
1046 message_base const& m,
1047 source& source)
1048 {
1049
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25 times.
25 BOOST_ASSERT(impl_);
1050 25 impl_->start_source(m, source);
1051 25 }
1052
1053 //------------------------------------------------
1054
1055 std::size_t
1056 1261 serializer::
1057 stream::
1058 capacity() const
1059 {
1060 // Precondition violation
1061
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1260 times.
1261 if(!is_open())
1062 1 detail::throw_logic_error();
1063
1064
2/2
✓ Branch 0 taken 1236 times.
✓ Branch 1 taken 24 times.
1260 if(impl_->filter_)
1065 1236 return impl_->in_.capacity();
1066
1067 24 return impl_->out_capacity();
1068 }
1069
1070 auto
1071 1243 serializer::
1072 stream::
1073 prepare() ->
1074 mutable_buffers_type
1075 {
1076 // Precondition violation
1077
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1242 times.
1243 if(!is_open())
1078 1 detail::throw_logic_error();
1079
1080
2/2
✓ Branch 0 taken 1236 times.
✓ Branch 1 taken 6 times.
1242 if(impl_->filter_)
1081 1236 return impl_->in_.prepare(
1082 2472 impl_->in_.capacity());
1083
1084 6 return impl_->out_prepare();
1085 }
1086
1087 void
1088 1244 serializer::
1089 stream::
1090 commit(std::size_t n)
1091 {
1092 // Precondition violation
1093
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1243 times.
1244 if(!is_open())
1094 1 detail::throw_logic_error();
1095
1096 // Precondition violation
1097
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1242 times.
1243 if(n > capacity())
1098 1 detail::throw_invalid_argument();
1099
1100
2/2
✓ Branch 0 taken 1236 times.
✓ Branch 1 taken 6 times.
1242 if(impl_->filter_)
1101 1236 return impl_->in_.commit(n);
1102
1103 6 impl_->out_commit(n);
1104 }
1105
1106 void
1107 47 serializer::
1108 stream::
1109 close() noexcept
1110 {
1111
2/2
✓ Branch 1 taken 24 times.
✓ Branch 2 taken 23 times.
47 if(!is_open())
1112 24 return; // no-op;
1113
1114
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 16 times.
23 if(!impl_->filter_)
1115 7 impl_->out_finish();
1116
1117 23 impl_->more_input_ = false;
1118 23 impl_ = nullptr;
1119 }
1120
1121 } // http_proto
1122 } // boost
1123