GCC Code Coverage Report


Directory: libs/http_proto/
File: src/serializer.cpp
Date: 2025-09-21 18:08:15
Exec Total Coverage
Lines: 415 473 87.7%
Functions: 43 46 93.5%
Branches: 229 310 73.9%

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