libs/http/src/serializer.cpp

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