include/boost/burl/serializer.hpp

100.0% Lines (31/31) 100.0% List of functions (22/22) 87.5% Branches (7/8)
serializer.hpp
f(x) Functions (22)
Function Calls Lines Branches Blocks
boost::burl::serializer::is_done() const :254 326x 100.0% – 100.0% boost::burl::serializer::is_header_done() const :265 8x 100.0% 100.0% 100.0% boost::burl::serializer::should_drain() const :278 141x 100.0% – 100.0% boost::burl::serializer::set_trailer(boost::burl::fields_base const*) :340 15x 100.0% 50.0% 75.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::const_buffer [24]>(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::const_buffer const (&) [24], bool) :438 1x 100.0% 100.0% 100.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::const_buffer [2]>(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::const_buffer const (&) [2], bool) :438 2x 100.0% 100.0% 100.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::const_buffer [32]>(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::const_buffer const (&) [32], bool) :438 1x 100.0% 100.0% 100.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::const_buffer>(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::const_buffer const&, bool) :438 984x 100.0% 100.0% 100.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::detail::slice_of<boost::capy::const_buffer> >(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::detail::slice_of<boost::capy::const_buffer> const&, bool) :438 259x 100.0% 100.0% 100.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 24ul> > >(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 24ul> > const&, bool) :438 6x 100.0% 100.0% 100.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 2ul> > >(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 2ul> > const&, bool) :438 2x 100.0% 100.0% 100.0% boost::system::result<std::span<boost::capy::const_buffer const, 18446744073709551615ul>, std::error_code> boost::burl::serializer::frame<boost::capy::detail::slice_of<std::span<boost::capy::const_buffer const, 18446744073709551615ul> > >(std::span<boost::capy::const_buffer, 18446744073709551615ul>, boost::capy::detail::slice_of<std::span<boost::capy::const_buffer const, 18446744073709551615ul> > const&, bool) :438 10x 100.0% 100.0% 100.0% boost::burl::serializer::frame(std::span<boost::capy::const_buffer, 18446744073709551615ul>, bool) :468 11x 100.0% 100.0% 100.0% boost::burl::serializer::source::next() :531 3x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::const_buffer [24]>::source_of(boost::capy::const_buffer const (&) [24]) :541 1x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::const_buffer [2]>::source_of(boost::capy::const_buffer const (&) [2]) :541 2x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::const_buffer [32]>::source_of(boost::capy::const_buffer const (&) [32]) :541 1x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::const_buffer>::source_of(boost::capy::const_buffer const&) :541 984x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::detail::slice_of<boost::capy::const_buffer> >::source_of(boost::capy::detail::slice_of<boost::capy::const_buffer> const&) :541 259x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 24ul> > >::source_of(boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 24ul> > const&) :541 6x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 2ul> > >::source_of(boost::capy::detail::slice_of<std::array<boost::capy::const_buffer, 2ul> > const&) :541 2x 100.0% – 100.0% boost::burl::serializer::source_of<boost::capy::detail::slice_of<std::span<boost::capy::const_buffer const, 18446744073709551615ul> > >::source_of(boost::capy::detail::slice_of<std::span<boost::capy::const_buffer const, 18446744073709551615ul> > const&) :541 10x 100.0% – 100.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 #ifndef BOOST_BURL_SERIALIZER_HPP
11 #define BOOST_BURL_SERIALIZER_HPP
12
13 #include <boost/burl/detail/config.hpp>
14 #include <boost/burl/detail/flat_buffer.hpp>
15 #include <boost/burl/encoder_config.hpp>
16 #include <boost/burl/fields_base.hpp>
17 #include <boost/burl/message_head_base.hpp>
18
19 #include <boost/assert.hpp>
20 #include <boost/capy/buffers.hpp>
21 #include <boost/system/result.hpp>
22
23 #include <cstddef>
24 #include <cstdint>
25 #include <memory>
26 #include <span>
27 #include <system_error>
28 #include <utility>
29
30 namespace boost
31 {
32 namespace burl
33 {
34
35 namespace detail
36 {
37 struct encoder;
38 } // namespace detail
39
40 /** A serializer for HTTP/1.1 messages.
41
42 Objects of this type incrementally produce the
43 wire representation of a message. The serializer
44 performs no I/O; the caller obtains buffers
45 describing the octets to transfer next and
46 reports the amount actually transferred.
47
48 @par The body
49 Body octets may be provided in two ways, which
50 can be mixed:
51
52 @li supplied directly to @ref frame as a buffer
53 sequence, or
54 @li written into the staging buffer obtained
55 from @ref prepare and made part of the body
56 with @ref commit.
57
58 Small amounts of data are accumulated internally
59 and framed together; a call to @ref frame may
60 therefore return no buffers, or reference fewer
61 octets than were supplied. Data supplied to
62 @ref frame whose total size is at least
63 `config::min_direct` is framed by reference,
64 without copying; supplied memory must remain
65 valid and unchanged until consumed.
66
67 @par Framing
68 The framing headers of the message are
69 selected from the payload. This selection
70 occurs on the first call to @ref prepare or
71 @ref frame, so the caller may continue
72 adjusting the start line and header fields
73 until then.
74
75 Until the first octet of the header has been
76 flushed, the serializer may modify
77 framing-related fields of the message. For
78 example, if the total size of the body becomes
79 known before the header is transferred, chunked
80 or stream-delimited framing may be replaced
81 with an explicit Content-Length.
82
83 A stream-delimited body in an HTTP/1.1 message
84 that does not specify a transfer coding is
85 replaced with chunked transfer coding, keeping
86 the connection reusable. HTTP/1.0 messages, and
87 messages that specify their own transfer
88 coding, retain stream-delimited framing.
89
90 Once transfer of the header has begun, the
91 message is no longer modified.
92
93 @par Encoding
94 When @ref config::encoder is set, a body whose
95 message names a `Content-Encoding` of `gzip`,
96 `deflate`, `br`, or `zstd` is encoded as it is
97 serialized, using the encode service installed
98 for that coding in the system context and the
99 settings for that coding; the encoder's output
100 is framed in place of the body. A coding
101 without an installed service, or one the
102 serializer does not know, leaves the body as
103 supplied. When the complete body is smaller
104 than `config::enc_threshold` the serializer may
105 skip encoding, in which case it removes the
106 Content-Encoding field and the body is
107 serialized unencoded.
108
109 @par Errors
110 When a call to @ref frame reports an error, the
111 message is failed and serialization cannot
112 proceed; the only valid operations on the
113 serializer are @ref start and destruction.
114 */
115 class serializer
116 {
117 public:
118 /** Serializer configuration settings. */
119 struct config
120 {
121 /** The size of the staging buffer.
122
123 The staging buffer holds body octets
124 which are ready for transfer: data
125 committed through @ref prepare and
126 @ref commit or copied from small
127 supplied buffers when no encoder is
128 used, or the output of the encoder
129 otherwise.
130 */
131 std::size_t stage_buffer = 64 * 1024;
132
133 /** The minimum capacity before draining.
134
135 When the free capacity of the buffer
136 written by @ref prepare falls below
137 this value, @ref should_drain returns
138 `true` and staged octets become
139 eligible for transfer on the next call
140 to @ref frame.
141 */
142 std::size_t min_prepare = 4 * 1024;
143
144 /** The zero-copy threshold.
145
146 Without an encoder, body data supplied
147 to @ref frame whose total size is at
148 least this value is framed by
149 reference, without copying. Smaller
150 amounts are copied into the staging
151 buffer, to be coalesced with subsequent
152 data.
153 */
154 std::size_t min_direct = 2 * 1024;
155
156 /** The size of the encoder input stage.
157
158 When an encoder is used, this buffer
159 accumulates body data before it is
160 passed to the encoder. It is unused
161 otherwise.
162 */
163 std::size_t enc_buffer = 8 * 1024;
164
165 /** The encoding threshold.
166
167 When an encoder is used, body data is
168 accumulated until at least this many
169 octets are available before the encoder
170 is first invoked. If the complete body
171 is smaller than this value the
172 serializer may skip encoding entirely.
173 */
174 std::size_t enc_threshold = 4 * 1024;
175
176 /** The content encoder settings.
177
178 When set, a body whose message names a
179 `Content-Encoding` with an encode
180 service installed in the system context
181 is encoded as it is serialized, using
182 these settings. When null, no body is
183 encoded.
184 */
185 std::shared_ptr<encoder_config const> encoder = nullptr;
186 };
187
188 /** Constructor.
189
190 The serializer allocates a single internal
191 buffer whose size is derived from `cfg`; no
192 further allocations are performed
193 afterwards, except for the content encoder.
194
195 @param cfg The configuration settings to
196 use.
197
198 @throws std::bad_alloc Allocation of the
199 internal buffer failed.
200 */
201 BOOST_BURL_DECL
202 explicit serializer(config const& cfg);
203
204 /** Constructor.
205
206 The state of `other`, is transferred to the new
207 object. Buffers previously returned by
208 @ref prepare or @ref frame remain valid and
209 refer to the new object. Afterwards, the
210 moved-from serializer may only be destroyed
211 or assigned to.
212
213 @param other The serializer to move from.
214 */
215 BOOST_BURL_DECL
216 serializer(serializer&& other) noexcept;
217
218 /** Assignment.
219
220 The state of `other` is transferred; any
221 message being serialized by `*this` is
222 abandoned. Buffers previously returned by
223 `other` remain valid and refer to `*this`.
224 Afterwards, the moved-from serializer may
225 only be destroyed or assigned to.
226
227 @param other The serializer to move from.
228 */
229 BOOST_BURL_DECL
230 serializer&
231 operator=(serializer&& other) noexcept;
232
233 serializer(serializer const&) = delete;
234
235 serializer&
236 operator=(serializer const&) = delete;
237
238 /** Destructor.
239 */
240 BOOST_BURL_DECL
241 ~serializer();
242
243 /** Return `true` if the message is finished.
244
245 The message is finished when every
246 serialized octet, including any trailer,
247 has been consumed.
248 This function may also return `true` after
249 the message has failed. In either case,
250 the serializer may be reused by calling
251 @ref start.
252 */
253 bool
254 326x is_done() const noexcept
255 {
256 326x return state_ >= state::done;
257 }
258
259 /** Return `true` if the header was transferred.
260
261 Returns `true` when every octet of the
262 serialized header has been consumed.
263 */
264 bool
265 8x is_header_done() const noexcept
266 {
267
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 time.
15x return state_ >= state::streaming &&
268
2/2
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 1 time.
15x msg_->buffer().size() == header_offset_;
269 }
270
271 /** Return `true` if staged octets should drain.
272
273 Returns `true` when the free capacity of
274 the buffer written by @ref prepare is below
275 @ref config::min_prepare.
276 */
277 bool
278 141x should_drain() const noexcept
279 {
280 141x return stage_.capacity() < min_prepare_;
281 }
282
283 /** Start serializing a message.
284
285 Any message currently being serialized is
286 abandoned and the serializer is reset.
287 `msg` is attached but the framing
288 of the body is selected from
289 `msg->payload()` by the first call to
290 @ref prepare or @ref frame. Until then the
291 caller may modify `msg` freely, the start
292 line and the framing-related fields
293 included. From that first call until the
294 message completes the caller must not
295 modify `msg`.
296
297 When `head` is `true`, the message is
298 serialized as the response to a HEAD
299 request: only the header is emitted,
300 exactly as stored in `msg`, and the body
301 is omitted.
302
303 @par Preconditions
304 `msg` is not null.
305
306 @param msg The message to serialize.
307 Ownership is not transferred; the object
308 must remain valid until the message
309 completes or is abandoned by another call
310 to `start` or by destroying the serializer.
311
312 @param head `true` to serialize the
313 response to a HEAD request.
314 */
315 BOOST_BURL_DECL
316 void
317 start(
318 message_head_base* msg,
319 bool head = false);
320
321 /** Set the trailer fields.
322
323 The referenced fields are serialized after
324 the final chunk of a body which uses the
325 chunked transfer coding; with any other
326 framing the trailer is ignored. Passing
327 `nullptr` removes a previously set trailer.
328
329 Ownership is not transferred; the object
330 must remain valid until the message
331 completes or is abandoned.
332
333 @par Preconditions
334 A message is being serialized. The end of
335 the body has not been declared.
336
337 @param t The trailer fields, or `nullptr`.
338 */
339 void
340 15x set_trailer(fields_base const* t) noexcept
341 {
342
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 15 times.
15x BOOST_ASSERT(!sealed_());
343 15x trailer_ = t;
344 15x }
345
346 /** Obtain a buffer for writing body octets.
347
348 The result is empty when the staging buffer
349 is full; in that case, transfer staged octets
350 by calling @ref frame and @ref consume.
351 @ref should_drain indicates when draining
352 is advisable.
353
354 The first call to `prepare` or @ref frame
355 selects the framing and the content encoder
356 from the message; see @ref start.
357
358 @par Preconditions
359 A message is being serialized. The end of
360 the body has not been declared.
361
362 @param dest The span receiving the buffer
363 descriptor.
364
365 @return The prefix of `dest` which was
366 filled.
367
368 @throws std::bad_alloc Allocation of the
369 content encoder failed.
370 */
371 BOOST_BURL_DECL
372 std::span<capy::mutable_buffer>
373 prepare(std::span<capy::mutable_buffer> dest);
374
375 /** Make written octets part of the body.
376
377 @par Preconditions
378 A message is being serialized. The end of
379 the body has not been declared. `n` does
380 not exceed the size of the buffer returned
381 by @ref prepare.
382
383 @param n The number of octets written.
384 */
385 BOOST_BURL_DECL
386 void
387 commit(std::size_t n) noexcept;
388
389 /** Obtain buffers for the next serialized octets.
390
391 This function combines pending header and
392 staged body octets with body octets supplied
393 by the caller, and returns descriptors for
394 the octets to transfer next. The returned
395 buffers may reference the serializer, message,
396 trailer, or supplied buffers; they remain valid
397 until the matching call to @ref consume.
398
399 Passing `more == false` declares the end
400 of the body. Afterwards, only octets which
401 were supplied but not yet consumed may be
402 supplied again.
403
404 The result might be empty, which indicates
405 that either the supplied octets were
406 absorbed for later framing or that nothing
407 is ready for transfer. `@ref consume` must
408 still be called before the next call to query
409 octets of `buffers` that were absorbed.
410
411 The first call to @ref prepare or `frame`
412 selects the framing and the content encoder
413 from the message; see @ref start.
414
415 @par Preconditions
416 A message is being serialized.
417
418 @param dest Storage for the returned
419 descriptors.
420
421 @param buffers The body octets not yet
422 consumed.
423
424 @param more `true` if further body octets
425 will be supplied.
426
427 @return The prefix of `dest` containing
428 the descriptors, which may be empty,
429 otherwise the error.
430
431 @throws std::bad_alloc Allocation of the
432 content encoder failed.
433 */
434 template<capy::ConstBufferSequence CB>
435 system::result<
436 std::span<capy::const_buffer const>,
437 std::error_code>
438 1265x frame(
439 std::span<capy::const_buffer> dest,
440 CB const& buffers,
441 bool more)
442 {
443 1265x source_of<CB> src(buffers);
444
1/1
✓ Branch 1 taken 1265 times.
2530x return frame_(dest, src, more);
445 }
446
447 /** Obtain buffers for the next serialized octets.
448
449 Equivalent to calling @ref frame with an empty
450 buffer sequence.
451
452 @par Preconditions
453 A message is being serialized.
454
455 @param dest Storage for the returned
456 descriptors.
457
458 @param more `true` if body octets will be
459 supplied later.
460
461 @return The prefix of `dest` containing
462 the descriptors, which may be empty,
463 otherwise the error.
464 */
465 system::result<
466 std::span<capy::const_buffer const>,
467 std::error_code>
468 11x frame(
469 std::span<capy::const_buffer> dest,
470 bool more)
471 {
472 11x source src;
473
1/1
✓ Branch 1 taken 11 times.
22x return frame_(dest, src, more);
474 }
475
476 /** Release transferred octets.
477
478 This function informs the serializer that
479 `n` octets from the front of the buffers
480 most recently returned by @ref frame were
481 transferred. Buffers previously returned by
482 @ref frame are invalidated.
483
484 The return value is the number of octets of
485 the caller-supplied body consumed by this
486 call: octets which were transferred, or
487 which were captured by the serializer, by
488 copy or by encoding, in the preceding call
489 to @ref frame. The caller advances its
490 remaining body input by this amount.
491
492 @par Preconditions
493 A message is being serialized. `n` does not
494 exceed the total size of the buffers
495 returned by the preceding call to @ref frame.
496
497 @param n The number of octets transferred.
498
499 @return The number of supplied body octets
500 consumed.
501 */
502 BOOST_BURL_DECL
503 std::size_t
504 consume(std::size_t n) noexcept;
505
506 protected:
507 BOOST_BURL_DECL
508 void
509 set_encoder(
510 std::unique_ptr<detail::encoder> enc);
511
512 private:
513 static constexpr std::size_t margin = 24;
514
515 enum class state : unsigned char
516 {
517 idle,
518 started,
519 streaming,
520 sealed,
521 done,
522 failed
523 };
524
525 struct source
526 {
527 std::size_t remain = 0;
528
529 virtual
530 capy::const_buffer
531 3x next() noexcept
532 {
533 3x return {};
534 }
535 };
536
537 template<capy::ConstBufferSequence CB>
538 struct source_of : source
539 {
540 explicit
541 1265x source_of(CB const& bs) noexcept
542 2253x : it_(capy::begin(bs))
543 1265x , end_(capy::end(bs))
544 {
545 1265x remain = capy::buffer_size(bs);
546 1265x }
547
548 capy::const_buffer
549 962x next() noexcept override
550 {
551 1022x while(it_ != end_)
552 {
553 856x capy::const_buffer const b(*it_++);
554 856x if(b.size() != 0)
555 {
556 796x remain -= b.size();
557 796x return b;
558 }
559 }
560 166x return {};
561 }
562
563 private:
564 decltype(capy::begin(
565 std::declval<CB const&>())) it_;
566 decltype(capy::end(
567 std::declval<CB const&>())) end_;
568 };
569
570 BOOST_BURL_DECL
571 system::result<
572 std::span<capy::const_buffer const>,
573 std::error_code>
574 frame_(
575 std::span<capy::const_buffer> dest,
576 source& src,
577 bool more);
578
579 void
580 select_framing_();
581
582 void
583 revise_framing_(
584 std::uint64_t remaining) noexcept;
585
586 void
587 split_() noexcept;
588
589 bool
590 sealed_() const noexcept;
591
592 bool
593 chunked_() const noexcept;
594
595 bool
596 to_eof_() const noexcept;
597
598 bool
599 should_coalesce_(
600 std::size_t avail) const noexcept;
601
602 detail::flat_buffer&
603 buffered_() noexcept;
604
605 capy::const_buffer
606 epilogue_buf_() const noexcept;
607
608 capy::const_buffer
609 trailer_buf_() const noexcept;
610
611 bool
612 settled_() const noexcept;
613
614 void
615 open_chunk_(std::uint64_t s) noexcept;
616
617 void
618 encode_(
619 source& src,
620 std::error_code& ec);
621
622 system::result<bool, std::error_code>
623 ingest_(
624 source& src,
625 bool more);
626
627 std::span<capy::const_buffer const>
628 gather_(
629 std::span<capy::const_buffer> dest,
630 source& src,
631 bool flush_body,
632 bool flush_header) noexcept;
633
634 std::unique_ptr<char[]> buf_;
635 std::size_t min_prepare_;
636 std::size_t min_direct_;
637 std::size_t enc_threshold_;
638
639 detail::flat_buffer stage_;
640 detail::flat_buffer enc_out_;
641
642 message_head_base* msg_ = nullptr;
643 std::unique_ptr<detail::encoder> enc_;
644 std::shared_ptr<encoder_config const> enc_cfg_;
645 fields_base const* trailer_ = nullptr;
646
647 std::uint32_t header_offset_ = 0;
648 std::uint32_t tail_offset_ = 0;
649 std::uint64_t owed_ = 0;
650 std::size_t input_framed_ = 0;
651 std::size_t input_digested_ = 0;
652 std::uint8_t prefix_rem_ = 0;
653 http::payload payload_ = http::payload::none;
654 state state_ = state::idle;
655 bool head_ = false;
656 bool crlf_owed_ = false;
657 bool enc_started_ = false;
658 };
659
660 } // namespace burl
661 } // namespace boost
662
663 #endif
664