src/detail/serializer.cpp

100.0% Lines (44/44) 100.0% List of functions (10/10) 89.3% Branches (25/28)
serializer.cpp
f(x) Functions (10)
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 #include "serializer.hpp"
11
12 #include <boost/burl/error.hpp>
13
14 #include <boost/capy/buffers/make_buffer.hpp>
15 #include <boost/assert.hpp>
16
17 namespace boost
18 {
19 namespace burl
20 {
21 namespace detail
22 {
23
24 37x serializer::serializer(
25 capy::any_write_stream& stream,
26 http::request_base& req,
27 37x config cfg)
28 37x : stream_(stream)
29 37x , req_(req)
30 37x , cfg_(cfg)
31 37x , storage_(new unsigned char[cfg_.buffer_size + margin] + margin)
32 {
33
3/4
✓ Branch 1 taken 19 times.
✓ Branch 2 taken 18 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 19 times.
37x BOOST_ASSERT(
34 req_.chunked() || req_.payload() == http::payload::size);
35 37x }
36
37 37x serializer::~serializer()
38 {
39
1/2
✓ Branch 0 taken 37 times.
✗ Branch 1 not taken.
37x delete[](storage_ - margin);
40 37x }
41
42 capy::io_task<>
43 18x serializer::write_eof()
44 {
45 18x return commit_eof(0);
46 }
47
48 std::span<capy::mutable_buffer>
49 18x serializer::prepare(std::span<capy::mutable_buffer> dest)
50 {
51
6/6
✓ Branch 1 taken 17 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 15 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 15 times.
18x if(dest.empty() || capacity() == 0)
52 3x return dest.first(0);
53 15x dest[0] = writable();
54 15x return dest.first(1);
55 }
56
57 capy::io_task<>
58
1/1
✓ Branch 1 taken 11 times.
11x serializer::commit(std::size_t n)
59 {
60 BOOST_ASSERT(n <= capacity());
61 avail_ += n;
62 if(capacity() >= cfg_.min_prepare)
63 co_return {};
64 auto [ec, _] = co_await drain({}, false);
65 co_return { ec };
66 22x }
67
68 capy::io_task<>
69
1/1
✓ Branch 1 taken 19 times.
19x serializer::commit_eof(std::size_t n)
70 {
71 BOOST_ASSERT(n <= capacity());
72 avail_ += n;
73 decide_framing(0);
74 auto [ec, _] = co_await drain({}, true);
75 co_return { ec };
76 38x }
77
78 void
79 26x serializer::decide_framing(std::size_t remaining) noexcept
80 {
81
6/6
✓ Branch 1 taken 14 times.
✓ Branch 2 taken 12 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 4 times.
✓ Branch 5 taken 22 times.
✓ Branch 6 taken 4 times.
26x if(!req_.chunked() || hdr_sent_)
82 22x return;
83
84
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4x BOOST_ASSERT(total_body_ == 0);
85 4x req_.erase(http::field::transfer_encoding);
86 4x req_.set_content_length(avail_ + remaining);
87 }
88
89 capy::io_task<std::size_t>
90
1/1
✓ Branch 1 taken 51 times.
51x serializer::drain(
91 std::span<capy::const_buffer const> tail,
92 bool eof)
93 {
94 auto const tail_size = capy::buffer_size(tail);
95 auto const chunked = req_.chunked();
96
97 BOOST_ASSERT(eof || avail_ + tail_size != 0);
98
99 capy::const_buffer vec[capy::detail::max_iovec_ + 3];
100 std::size_t n = 0;
101 std::size_t sum = 0;
102
103 if(!hdr_sent_)
104 {
105 vec[n++] = capy::make_buffer(req_.buffer());
106 sum += req_.buffer().size();
107 }
108
109 if(chunked)
110 {
111 auto const buf = chunk_frame(tail_size);
112 vec[n++] = buf;
113 sum += buf.size();
114 }
115 else
116 {
117 auto const declared =
118 req_.payload() == http::payload::size
119 ? req_.payload_size() : 0;
120 auto const produced = total_body_ + avail_ + tail_size;
121
122 if(produced > declared || (eof && produced != declared))
123 co_return { error::body_size_mismatch, 0 };
124
125 if(avail_ != 0)
126 {
127 vec[n++] = { storage_, avail_ };
128 sum += avail_;
129 }
130 }
131
132 auto const owned = sum;
133
134 sum += tail_size;
135 for(auto const& b : tail)
136 vec[n++] = b;
137
138 if(chunked && eof)
139 {
140 std::size_t const term = avail_ || tail_size ? 7 : 2;
141 vec[n++] = { "\r\n0\r\n\r\n", term };
142 sum += term;
143 }
144
145 auto const need = chunked || eof ? sum : owned + bool(tail_size);
146 auto [ec, written] = co_await write_at_least({ vec, n }, need);
147 if(ec)
148 co_return { ec, 0 };
149
150 auto const consumed = (std::min)(written - owned, tail_size);
151 total_body_ += avail_ + consumed;
152 avail_ = 0;
153 hdr_sent_ = true;
154 done_ = eof;
155 co_return { {}, consumed };
156 102x }
157
158 capy::io_task<std::size_t>
159
1/1
✓ Branch 1 taken 49 times.
49x serializer::write_at_least(
160 std::span<capy::const_buffer> buffers,
161 std::size_t bytes)
162 {
163 BOOST_ASSERT(bytes <= capy::buffer_size(buffers));
164 auto slice = capy::buffer_slice(buffers);
165 std::size_t written = 0;
166 while(written < bytes)
167 {
168 auto [ec, n] = co_await stream_.write_some(slice.data());
169 written += n;
170 if(ec && written < bytes)
171 co_return { ec, written };
172 slice.remove_prefix(n);
173 }
174 co_return { {}, written };
175 98x }
176
177 capy::const_buffer
178 24x serializer::chunk_frame(std::size_t tail_size) noexcept
179 {
180 static constexpr char hex[] = "0123456789ABCDEF";
181
182 24x auto size = avail_ + tail_size;
183 24x auto* p = storage_;
184
185 24x *--p = '\n';
186 24x *--p = '\r';
187
188 do
189 {
190 38x *--p = hex[size & 0xF];
191 38x size >>= 4;
192
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 24 times.
38x } while(size != 0);
193
194 // previous chunk's CRLF
195
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 14 times.
24x if(total_body_ != 0)
196 {
197 10x *--p = '\n';
198 10x *--p = '\r';
199 }
200
201 24x return { p, static_cast<std::size_t>(storage_ - p) + avail_ };
202 }
203
204 } // namespace detail
205 } // namespace burl
206 } // namespace boost
207