GCC Code Coverage Report


Directory: libs/http_proto/
File: libs/http_proto/src/serializer.cpp
Date: 2024-05-09 08:15:19
Exec Total Coverage
Lines: 227 246 92.3%
Functions: 21 23 91.3%
Branches: 99 129 76.7%

Line Branch Exec Source
1 //
2 // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
3 // Copyright (c) 2024 Christian Mazakas
4 //
5 // Distributed under the Boost Software License, Version 1.0. (See accompanying
6 // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7 //
8 // Official repository: https://github.com/cppalliance/http_proto
9 //
10
11 #include <boost/http_proto/serializer.hpp>
12 #include <boost/http_proto/message_view_base.hpp>
13 #include <boost/http_proto/detail/except.hpp>
14 #include <boost/buffers/algorithm.hpp>
15 #include <boost/buffers/buffer_copy.hpp>
16 #include <boost/buffers/buffer_size.hpp>
17 #include <boost/core/ignore_unused.hpp>
18 #include <stddef.h>
19
20 namespace boost {
21 namespace http_proto {
22
23 //------------------------------------------------
24
25 void
26 consume_buffers(
27 buffers::const_buffer*& p,
28 std::size_t& n,
29 std::size_t bytes)
30 {
31 while(n > 0)
32 {
33 if(bytes < p->size())
34 {
35 *p += bytes;
36 return;
37 }
38 bytes -= p->size();
39 ++p;
40 --n;
41 }
42
43 // Precondition violation
44 if(bytes > 0)
45 detail::throw_invalid_argument();
46 }
47
48 template<class MutableBuffers>
49 void
50 54 write_chunk_header(
51 MutableBuffers const& dest0,
52 std::size_t size) noexcept
53 {
54 static constexpr char hexdig[] =
55 "0123456789ABCDEF";
56 char buf[18];
57 54 auto p = buf + 16;
58
2/2
✓ Branch 0 taken 432 times.
✓ Branch 1 taken 27 times.
918 for(std::size_t i = 16; i--;)
59 {
60 864 *--p = hexdig[size & 0xf];
61 864 size >>= 4;
62 }
63 54 buf[16] = '\r';
64 54 buf[17] = '\n';
65 54 auto n = buffers::buffer_copy(
66 dest0,
67 buffers::const_buffer(
68 buf, sizeof(buf)));
69 ignore_unused(n);
70
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 27 times.
54 BOOST_ASSERT(n == 18);
71
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 27 times.
54 BOOST_ASSERT(
72 buffers::buffer_size(dest0) == n);
73 54 }
74
75 template<class DynamicBuffer>
76 void
77 19 write_chunk_close(DynamicBuffer& db)
78 {
79 38 db.commit(
80
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 buffers::buffer_copy(
81 db.prepare(2),
82 buffers::const_buffer("\r\n", 2)));
83 19 }
84
85 template<class DynamicBuffer>
86 void
87 3 write_last_chunk(DynamicBuffer& db)
88 {
89 6 db.commit(
90
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 buffers::buffer_copy(
91 db.prepare(5),
92 buffers::const_buffer("0\r\n\r\n", 5)));
93 3 }
94
95 //------------------------------------------------
96
97 20 serializer::
98 20 ~serializer()
99 {
100 20 }
101
102 9 serializer::
103 9 serializer()
104 9 : serializer(65536)
105 {
106 9 }
107
108 serializer::
109 serializer(
110 serializer&&) noexcept = default;
111
112 20 serializer::
113 serializer(
114 20 std::size_t buffer_size)
115 20 : ws_(buffer_size)
116 {
117 20 }
118
119 void
120 serializer::
121 reset() noexcept
122 {
123 }
124
125 //------------------------------------------------
126
127 auto
128 67 serializer::
129 prepare() ->
130 system::result<
131 const_buffers_type>
132 {
133 // Precondition violation
134
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 66 times.
67 if(is_done_)
135 1 detail::throw_logic_error();
136
137 // Expect: 100-continue
138
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 62 times.
66 if(is_expect_continue_)
139 {
140
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 2 times.
4 if(out_.data() == hp_)
141 2 return const_buffers_type(hp_, 1);
142 2 is_expect_continue_ = false;
143 2 BOOST_HTTP_PROTO_RETURN_EC(
144 error::expect_100_continue);
145 }
146
147
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 59 times.
62 if(st_ == style::empty)
148 {
149 9 return const_buffers_type(
150 3 out_.data(),
151 3 out_.size());
152 }
153
154
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 56 times.
59 if(st_ == style::buffers)
155 {
156 9 return const_buffers_type(
157 3 out_.data(),
158 3 out_.size());
159 }
160
161
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 34 times.
56 if(st_ == style::source)
162 {
163
2/2
✓ Branch 0 taken 17 times.
✓ Branch 1 taken 5 times.
22 if(more_)
164 {
165
2/2
✓ Branch 0 taken 9 times.
✓ Branch 1 taken 8 times.
17 if(! is_chunked_)
166 {
167 9 auto rv = src_->read(
168
2/4
✓ Branch 2 taken 9 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 9 times.
✗ Branch 6 not taken.
9 tmp0_.prepare(tmp0_.capacity()));
169 9 tmp0_.commit(rv.bytes);
170
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 9 times.
9 if(rv.ec.failed())
171 return rv.ec;
172 9 more_ = ! rv.finished;
173 }
174 else
175 {
176
1/2
✓ Branch 1 taken 8 times.
✗ Branch 2 not taken.
8 if(tmp0_.capacity() > chunked_overhead_)
177 {
178 auto dest = tmp0_.prepare(
179 8 tmp0_.capacity() -
180 2 - // CRLF
181
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 5); // final chunk
182
183 8 auto rv = src_->read(
184
1/2
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
8 buffers::sans_prefix(dest, 18));
185
186
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if(rv.ec.failed())
187 return rv.ec;
188
189
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 1 times.
8 if(rv.bytes != 0)
190 {
191 7 write_chunk_header(
192 7 buffers::prefix(dest, 18), rv.bytes);
193 7 tmp0_.commit(rv.bytes + 18);
194 // terminate chunk
195 7 tmp0_.commit(
196 buffers::buffer_copy(
197
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 tmp0_.prepare(2),
198 14 buffers::const_buffer(
199 "\r\n", 2)));
200 }
201
202
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 6 times.
8 if(rv.finished)
203 {
204 2 tmp0_.commit(
205 buffers::buffer_copy(
206
1/2
✓ Branch 1 taken 2 times.
✗ Branch 2 not taken.
2 tmp0_.prepare(5),
207 2 buffers::const_buffer(
208 "0\r\n\r\n", 5)));
209 2 more_ = false;
210 }
211 }
212 }
213 }
214
215 22 std::size_t n = 0;
216
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 17 times.
22 if(out_.data() == hp_)
217 5 ++n;
218
2/2
✓ Branch 3 taken 44 times.
✓ Branch 4 taken 22 times.
66 for(buffers::const_buffer const& b : tmp0_.data())
219 44 out_[n++] = b;
220
221 66 return const_buffers_type(
222 22 out_.data(),
223 22 out_.size());
224 }
225
226
1/2
✓ Branch 0 taken 34 times.
✗ Branch 1 not taken.
34 if(st_ == style::stream)
227 {
228 34 std::size_t n = 0;
229
2/2
✓ Branch 1 taken 6 times.
✓ Branch 2 taken 28 times.
34 if(out_.data() == hp_)
230 6 ++n;
231
6/6
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 32 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 1 times.
✓ Branch 6 taken 33 times.
34 if(tmp0_.size() == 0 && more_)
232 {
233 1 BOOST_HTTP_PROTO_RETURN_EC(
234 error::need_data);
235 }
236
2/2
✓ Branch 3 taken 66 times.
✓ Branch 4 taken 33 times.
99 for(buffers::const_buffer const& b : tmp0_.data())
237 66 out_[n++] = b;
238
239 99 return const_buffers_type(
240 33 out_.data(),
241 33 out_.size());
242 }
243
244 // should never get here
245 detail::throw_logic_error();
246 }
247
248 void
249 1745 serializer::
250 consume(
251 std::size_t n)
252 {
253 // Precondition violation
254
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1744 times.
1745 if(is_done_)
255 1 detail::throw_logic_error();
256
257
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1741 times.
1744 if(is_expect_continue_)
258 {
259 // Cannot consume more than
260 // the header on 100-continue
261
2/2
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 2 times.
3 if(n > hp_->size())
262 1 detail::throw_invalid_argument();
263
264 2 out_.consume(n);
265 2 return;
266 }
267
2/2
✓ Branch 1 taken 26 times.
✓ Branch 2 taken 1715 times.
1741 else if(out_.data() == hp_)
268 {
269 // consume header
270
2/2
✓ Branch 1 taken 11 times.
✓ Branch 2 taken 15 times.
26 if(n < hp_->size())
271 {
272 11 out_.consume(n);
273 11 return;
274 }
275 15 n -= hp_->size();
276 15 out_.consume(hp_->size());
277 }
278
279
3/3
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 1724 times.
1730 switch(st_)
280 {
281 3 default:
282 case style::empty:
283 3 out_.consume(n);
284
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if(out_.empty())
285 3 is_done_ = true;
286 3 return;
287
288 3 case style::buffers:
289 3 out_.consume(n);
290
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 if(out_.empty())
291 3 is_done_ = true;
292 3 return;
293
294 1724 case style::source:
295 case style::stream:
296 1724 tmp0_.consume(n);
297
4/4
✓ Branch 1 taken 39 times.
✓ Branch 2 taken 1685 times.
✓ Branch 3 taken 11 times.
✓ Branch 4 taken 1713 times.
1763 if( tmp0_.size() == 0 &&
298
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 28 times.
39 ! more_)
299 11 is_done_ = true;
300 1724 return;
301 }
302 }
303
304 //------------------------------------------------
305
306 void
307 14 serializer::
308 copy(
309 buffers::const_buffer* dest,
310 buffers::const_buffer const* src,
311 std::size_t n) noexcept
312 {
313
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 7 times.
14 while(n--)
314 7 *dest++ = *src++;
315 7 }
316
317 void
318 26 serializer::
319 start_init(
320 message_view_base const& m)
321 {
322 26 ws_.clear();
323
324 // VFALCO what do we do with
325 // metadata error code failures?
326 // m.ph_->md.maybe_throw();
327
328 26 is_done_ = false;
329
330 26 is_expect_continue_ =
331 26 m.ph_->md.expect.is_100_continue;
332
333 // Transfer-Encoding
334 {
335 26 auto const& te =
336 26 m.ph_->md.transfer_encoding;
337 26 is_chunked_ = te.is_chunked;
338 }
339 26 }
340
341 void
342 4 serializer::
343 start_empty(
344 message_view_base const& m)
345 {
346 4 start_init(m);
347
348 4 st_ = style::empty;
349
350
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 1 times.
4 if(! is_chunked_)
351 {
352 out_ = make_array(
353 3 1); // header
354 }
355 else
356 {
357 out_ = make_array(
358 1 + // header
359
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 1); // final chunk
360
361 // Buffer is too small
362
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if(ws_.size() < 5)
363 detail::throw_length_error();
364
365 buffers::mutable_buffer dest(
366 1 ws_.data(), 5);
367 1 buffers::buffer_copy(
368 dest,
369 1 buffers::const_buffer(
370 "0\r\n\r\n", 5));
371 1 out_[1] = dest;
372 }
373
374 4 hp_ = &out_[0];
375 4 *hp_ = { m.ph_->cbuf, m.ph_->size };
376 4 }
377
378 void
379 7 serializer::
380 start_buffers(
381 message_view_base const& m)
382 {
383 7 st_ = style::buffers;
384
385
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 1 times.
7 if(! is_chunked_)
386 {
387 //if(! cod_)
388 {
389 out_ = make_array(
390 1 + // header
391 6 buf_.size()); // body
392 12 copy(&out_[1],
393 6 buf_.data(), buf_.size());
394 }
395 #if 0
396 else
397 {
398 out_ = make_array(
399 1 + // header
400 2); // tmp1
401 }
402 #endif
403 }
404 else
405 {
406 //if(! cod_)
407 {
408 out_ = make_array(
409 1 + // header
410 1 + // chunk size
411 1 buf_.size() + // body
412
1/2
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
1 1); // final chunk
413 2 copy(&out_[2],
414 1 buf_.data(), buf_.size());
415
416 // Buffer is too small
417
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if(ws_.size() < 18 + 7)
418 detail::throw_length_error();
419 1 buffers::mutable_buffer s1(ws_.data(), 18);
420 1 buffers::mutable_buffer s2(ws_.data(), 18 + 7);
421 1 s2 += 18; // VFALCO HACK
422 1 write_chunk_header(
423 s1,
424 1 buffers::buffer_size(buf_));
425 1 buffers::buffer_copy(s2, buffers::const_buffer(
426 "\r\n"
427 "0\r\n"
428 "\r\n", 7));
429 1 out_[1] = s1;
430 1 out_[out_.size() - 1] = s2;
431 }
432 #if 0
433 else
434 {
435 out_ = make_array(
436 1 + // header
437 2); // tmp1
438 }
439 #endif
440 }
441
442 7 hp_ = &out_[0];
443 7 *hp_ = { m.ph_->cbuf, m.ph_->size };
444 7 }
445
446 void
447 8 serializer::
448 start_source(
449 message_view_base const& m,
450 source* src)
451 {
452 8 st_ = style::source;
453 8 src_ = src;
454 out_ = make_array(
455 1 + // header
456 8 2); // tmp
457 //if(! cod_)
458 {
459 8 tmp0_ = { ws_.data(), ws_.size() };
460
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if(tmp0_.capacity() <
461 18 + // chunk size
462 1 + // body (1 byte)
463 2 + // CRLF
464 5) // final chunk
465 detail::throw_length_error();
466 }
467 #if 0
468 else
469 {
470 buffers::buffered_base::allocator a(
471 ws_.data(), ws_.size()/3, false);
472 src->init(a);
473 ws_.reserve(a.size_used());
474
475 auto const n = ws_.size() / 2;
476
477 tmp0_ = { ws_.data(), ws_.size() / 2 };
478 ws_.reserve(n);
479
480 // Buffer is too small
481 if(ws_.size() < 1)
482 detail::throw_length_error();
483
484 tmp1_ = { ws_.data(), ws_.size() };
485 }
486 #endif
487
488 8 hp_ = &out_[0];
489 8 *hp_ = { m.ph_->cbuf, m.ph_->size };
490 8 more_ = true;
491 8 }
492
493 auto
494 7 serializer::
495 start_stream(
496 message_view_base const& m) ->
497 stream
498 {
499 7 start_init(m);
500
501 7 st_ = style::stream;
502 out_ = make_array(
503 1 + // header
504 7 2); // tmp
505 //if(! cod_)
506 {
507 7 tmp0_ = { ws_.data(), ws_.size() };
508
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
7 if(tmp0_.capacity() <
509 18 + // chunk size
510 1 + // body (1 byte)
511 2 + // CRLF
512 5) // final chunk
513 detail::throw_length_error();
514 }
515 #if 0
516 else
517 {
518 auto const n = ws_.size() / 2;
519 tmp0_ = { ws_.data(), n };
520 ws_.reserve(n);
521
522 // Buffer is too small
523 if(ws_.size() < 1)
524 detail::throw_length_error();
525
526 tmp1_ = { ws_.data(), ws_.size() };
527 }
528 #endif
529
530 7 hp_ = &out_[0];
531 7 *hp_ = { m.ph_->cbuf, m.ph_->size };
532
533 7 more_ = true;
534
535 7 return stream{*this};
536 }
537
538 //------------------------------------------------
539
540 std::size_t
541 140 serializer::
542 stream::
543 capacity() const noexcept
544 {
545 140 return sr_->tmp0_.capacity();
546 }
547
548 std::size_t
549 132 serializer::
550 stream::
551 size() const noexcept
552 {
553 132 return sr_->tmp0_.size();
554 }
555
556 bool
557 66 serializer::
558 stream::
559 is_full() const noexcept
560 {
561
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 30 times.
66 if( sr_->is_chunked_ )
562 36 return capacity() < chunked_overhead_ + 1;
563
564 30 return capacity() == 0;
565 }
566
567 auto
568 36 serializer::
569 stream::
570 prepare() const ->
571 buffers_type
572 {
573 36 auto n = sr_->tmp0_.capacity();
574
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 15 times.
36 if( sr_->is_chunked_ )
575 {
576 // for chunked encoding, we want to unconditionally
577 // reserve space for the complete chunk and the
578 // last-chunk
579 // this enables users to call:
580 //
581 // stream.commit(n); stream.close();
582 //
583 // without needing to worry about draining the
584 // serializer via `consume()` calls
585
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 20 times.
21 if( n < chunked_overhead_ + 1 )
586 1 detail::throw_length_error();
587
588 20 n -= chunked_overhead_;
589 return buffers::sans_prefix(
590 40 sr_->tmp0_.prepare(chunk_header_len_ + n),
591 20 chunk_header_len_);
592 }
593
594 15 return sr_->tmp0_.prepare(n);
595 }
596
597 void
598 35 serializer::
599 stream::
600 commit(std::size_t n) const
601 {
602
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 20 times.
35 if(! sr_->is_chunked_ )
603 {
604 15 sr_->tmp0_.commit(n);
605 }
606 else
607 {
608 // Zero sized chunks are not valid. Call close()
609 // if the intent is to signal the end of the body.
610
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 19 times.
20 if( n == 0 )
611 1 detail::throw_logic_error();
612
613 19 auto m = n + chunk_header_len_;
614
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 auto dest = sr_->tmp0_.prepare(m);
615 19 write_chunk_header(
616 19 buffers::prefix(dest, chunk_header_len_), n);
617 19 sr_->tmp0_.commit(m);
618
1/2
✓ Branch 1 taken 19 times.
✗ Branch 2 not taken.
19 write_chunk_close(sr_->tmp0_);
619 }
620 34 }
621
622 void
623 9 serializer::
624 stream::
625 close() const
626 {
627 // Precondition violation
628
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 5 times.
9 if(! sr_->more_ )
629 4 detail::throw_logic_error();
630
631
2/2
✓ Branch 0 taken 3 times.
✓ Branch 1 taken 2 times.
5 if( sr_->is_chunked_ )
632 3 write_last_chunk(sr_->tmp0_);
633
634 5 sr_->more_ = false;
635 5 }
636
637 //------------------------------------------------
638
639 } // http_proto
640 } // boost
641