src/detail/serializer.cpp
100.0% Lines (116/116)
100.0% List of functions (20/20)
96.4% Branches (54/56)
Functions (20)
Function
Calls
Lines
Branches
Blocks
boost::burl::detail::serializer::serializer(boost::burl::detail::serializer::config const&, boost::capy::any_write_stream*)
:27
102x
100.0%
–
100.0%
boost::burl::detail::serializer::serializer(boost::burl::detail::serializer&&)
:41
4x
100.0%
–
100.0%
boost::burl::detail::serializer::operator=(boost::burl::detail::serializer&&)
:65
3x
100.0%
100.0%
100.0%
boost::burl::detail::serializer::~serializer()
:74
106x
100.0%
75.0%
100.0%
boost::burl::detail::serializer::reset(boost::capy::any_write_stream*, boost::http::message_base*, boost::burl::detail::serializer::encoder*, bool)
:82
113x
100.0%
100.0%
100.0%
boost::burl::detail::serializer::write_eof()
:111
70x
100.0%
–
100.0%
boost::burl::detail::serializer::prepare(std::span<boost::capy::mutable_buffer, 18446744073709551615ul>)
:118
25x
100.0%
100.0%
100.0%
boost::burl::detail::serializer::commit(unsigned long)
:128
14x
100.0%
100.0%
44.0%
boost::burl::detail::serializer::commit_eof(unsigned long)
:140
75x
100.0%
100.0%
44.0%
boost::burl::detail::serializer::capacity() const
:151
198x
100.0%
–
100.0%
boost::burl::detail::serializer::do_prepare()
:158
46x
100.0%
–
100.0%
boost::burl::detail::serializer::do_commit(unsigned long)
:165
113x
100.0%
–
100.0%
boost::burl::detail::serializer::should_coalesce(unsigned long) const
:172
71x
100.0%
100.0%
100.0%
boost::burl::detail::serializer::finalize(unsigned long)
:183
96x
100.0%
100.0%
100.0%
boost::burl::detail::serializer::decide_framing(unsigned long)
:204
87x
100.0%
90.0%
93.0%
boost::burl::detail::serializer::process(std::span<boost::capy::const_buffer const, 18446744073709551615ul>, bool)
:216
172x
100.0%
100.0%
100.0%
boost::burl::detail::serializer::encode(std::span<boost::capy::const_buffer const, 18446744073709551615ul>, bool)
:227
71x
100.0%
100.0%
44.0%
boost::burl::detail::serializer::flush(std::span<boost::capy::const_buffer const, 18446744073709551615ul>, bool)
:284
141x
100.0%
100.0%
44.0%
boost::burl::detail::serializer::flush(std::span<boost::capy::const_buffer const, 18446744073709551615ul>, bool)::{lambda(boost::capy::const_buffer)#1}::operator()(boost::capy::const_buffer) const
:299
237x
100.0%
–
100.0%
boost::burl::detail::serializer::flush(std::span<boost::capy::const_buffer const, 18446744073709551615ul>, bool)::{lambda()#1}::operator()() const
:334
88x
100.0%
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 | #include "serializer.hpp" | |||
| 11 | ||||
| 12 | #include <boost/assert.hpp> | |||
| 13 | #include <boost/capy/buffers/make_buffer.hpp> | |||
| 14 | #include <boost/capy/cond.hpp> | |||
| 15 | #include <boost/capy/write_at_least.hpp> | |||
| 16 | ||||
| 17 | #include <cstring> | |||
| 18 | #include <utility> | |||
| 19 | ||||
| 20 | namespace boost | |||
| 21 | { | |||
| 22 | namespace burl | |||
| 23 | { | |||
| 24 | namespace detail | |||
| 25 | { | |||
| 26 | ||||
| 27 | 102x | serializer:: | ||
| 28 | 102x | serializer(config const& cfg, capy::any_write_stream* stream) | ||
| 29 | 102x | : stream_(stream) | ||
| 30 | 102x | , min_prepare_(cfg.min_prepare) | ||
| 31 | 102x | , direct_thr_(cfg.direct_thr) | ||
| 32 | 102x | , enc_thr_(cfg.enc_thr) | ||
| 33 | 102x | , out_(new unsigned char[ | ||
| 34 | 204x | cfg.out_buffer + cfg.enc_buffer + margin] + margin) | ||
| 35 | 102x | , out_cap_(cfg.enc_buffer) | ||
| 36 | 102x | , in_(out_) | ||
| 37 | 102x | , in_cap_(cfg.out_buffer) | ||
| 38 | { | |||
| 39 | 102x | } | ||
| 40 | ||||
| 41 | 4x | serializer:: | ||
| 42 | 4x | serializer(serializer&& other) noexcept | ||
| 43 | 4x | : stream_(other.stream_) | ||
| 44 | 4x | , msg_(other.msg_) | ||
| 45 | 4x | , enc_(other.enc_) | ||
| 46 | 4x | , min_prepare_(other.min_prepare_) | ||
| 47 | 4x | , direct_thr_(other.direct_thr_) | ||
| 48 | 4x | , enc_thr_(other.enc_thr_) | ||
| 49 | 4x | , out_(other.out_) | ||
| 50 | 4x | , out_cap_(other.out_cap_) | ||
| 51 | 4x | , out_len_(other.out_len_) | ||
| 52 | 4x | , in_(other.in_) | ||
| 53 | 4x | , in_cap_(other.in_cap_) | ||
| 54 | 4x | , in_len_(other.in_len_) | ||
| 55 | 4x | , total_body_(other.total_body_) | ||
| 56 | 4x | , head_(other.head_) | ||
| 57 | 4x | , enc_started_(other.enc_started_) | ||
| 58 | 4x | , hdr_sent_(other.hdr_sent_) | ||
| 59 | 4x | , done_(other.done_) | ||
| 60 | { | |||
| 61 | 4x | other.out_ = nullptr; | ||
| 62 | 4x | } | ||
| 63 | ||||
| 64 | serializer& | |||
| 65 | 3x | serializer:: | ||
| 66 | operator=(serializer&& other) noexcept | |||
| 67 | { | |||
| 68 |
2/2✓ Branch 0 taken 1 time.
✓ Branch 1 taken 2 times.
|
3x | if(this == &other) | |
| 69 | 1x | return *this; | ||
| 70 | 2x | this->~serializer(); | ||
| 71 | 2x | return *new(this) serializer(std::move(other)); | ||
| 72 | } | |||
| 73 | ||||
| 74 | 106x | serializer:: | ||
| 75 | ~serializer() | |||
| 76 | { | |||
| 77 |
2/2✓ Branch 0 taken 102 times.
✓ Branch 1 taken 4 times.
|
106x | if(out_) | |
| 78 |
1/2✓ Branch 0 taken 102 times.
✗ Branch 1 not taken.
|
102x | delete[](out_ - margin); | |
| 79 | 106x | } | ||
| 80 | ||||
| 81 | void | |||
| 82 | 113x | serializer:: | ||
| 83 | reset( | |||
| 84 | capy::any_write_stream* stream, | |||
| 85 | http::message_base* msg, | |||
| 86 | encoder* enc, | |||
| 87 | bool head) noexcept | |||
| 88 | { | |||
| 89 |
2/2✓ Branch 0 taken 9 times.
✓ Branch 1 taken 104 times.
|
113x | if(head) | |
| 90 | 9x | enc = nullptr; | ||
| 91 | ||||
| 92 |
2/2✓ Branch 0 taken 32 times.
✓ Branch 1 taken 81 times.
|
113x | if(!enc == (in_ != out_)) | |
| 93 | { | |||
| 94 | 32x | std::swap(in_cap_, out_cap_); | ||
| 95 |
2/2✓ Branch 0 taken 31 times.
✓ Branch 1 taken 1 time.
|
32x | in_ = enc ? out_ + out_cap_ : out_; | |
| 96 | } | |||
| 97 | ||||
| 98 | 113x | stream_ = stream; | ||
| 99 | 113x | msg_ = msg; | ||
| 100 | 113x | enc_ = enc; | ||
| 101 | 113x | in_len_ = 0; | ||
| 102 | 113x | out_len_ = 0; | ||
| 103 | 113x | total_body_ = 0; | ||
| 104 | 113x | head_ = head; | ||
| 105 | 113x | enc_started_ = false; | ||
| 106 | 113x | hdr_sent_ = false; | ||
| 107 | 113x | done_ = false; | ||
| 108 | 113x | } | ||
| 109 | ||||
| 110 | capy::io_task<> | |||
| 111 | 70x | serializer:: | ||
| 112 | write_eof() | |||
| 113 | { | |||
| 114 | 70x | return commit_eof(0); | ||
| 115 | } | |||
| 116 | ||||
| 117 | std::span<capy::mutable_buffer> | |||
| 118 | 25x | serializer:: | ||
| 119 | prepare(std::span<capy::mutable_buffer> dest) | |||
| 120 | { | |||
| 121 |
6/6✓ Branch 1 taken 24 times.
✓ Branch 2 taken 1 time.
✓ Branch 4 taken 2 times.
✓ Branch 5 taken 22 times.
✓ Branch 6 taken 3 times.
✓ Branch 7 taken 22 times.
|
25x | if(dest.empty() || capacity() == 0) | |
| 122 | 3x | return dest.first(0); | ||
| 123 | 22x | dest[0] = do_prepare(); | ||
| 124 | 22x | return dest.first(1); | ||
| 125 | } | |||
| 126 | ||||
| 127 | capy::io_task<> | |||
| 128 |
1/1✓ Branch 1 taken 14 times.
|
14x | serializer:: | |
| 129 | commit(std::size_t n) | |||
| 130 | { | |||
| 131 | BOOST_ASSERT(n <= capacity()); | |||
| 132 | do_commit(n); | |||
| 133 | if(capacity() >= min_prepare_) | |||
| 134 | co_return {}; | |||
| 135 | auto [ec, _] = co_await process({}, false); | |||
| 136 | co_return { ec }; | |||
| 137 | 28x | } | ||
| 138 | ||||
| 139 | capy::io_task<> | |||
| 140 |
1/1✓ Branch 1 taken 75 times.
|
75x | serializer:: | |
| 141 | commit_eof(std::size_t n) | |||
| 142 | { | |||
| 143 | BOOST_ASSERT(n <= capacity()); | |||
| 144 | do_commit(n); | |||
| 145 | finalize(0); | |||
| 146 | auto [ec, _] = co_await process({}, true); | |||
| 147 | co_return { ec }; | |||
| 148 | 150x | } | ||
| 149 | ||||
| 150 | std::size_t | |||
| 151 | 198x | serializer:: | ||
| 152 | capacity() const noexcept | |||
| 153 | { | |||
| 154 | 198x | return in_cap_ - in_len_; | ||
| 155 | } | |||
| 156 | ||||
| 157 | capy::mutable_buffer | |||
| 158 | 46x | serializer:: | ||
| 159 | do_prepare() noexcept | |||
| 160 | { | |||
| 161 | 46x | return { in_ + in_len_, in_cap_ - in_len_ }; | ||
| 162 | } | |||
| 163 | ||||
| 164 | void | |||
| 165 | 113x | serializer:: | ||
| 166 | do_commit(std::size_t n) noexcept | |||
| 167 | { | |||
| 168 | 113x | in_len_ += n; | ||
| 169 | 113x | } | ||
| 170 | ||||
| 171 | bool | |||
| 172 | 71x | serializer:: | ||
| 173 | should_coalesce(std::size_t avail) const noexcept | |||
| 174 | { | |||
| 175 |
2/2✓ Branch 1 taken 26 times.
✓ Branch 2 taken 45 times.
|
71x | if(avail > capacity()) | |
| 176 | 26x | return false; | ||
| 177 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 35 times.
|
45x | if(enc_) | |
| 178 |
4/4✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 5 times.
✓ Branch 3 taken 1 time.
|
10x | return !enc_started_ && avail + in_len_ < enc_thr_; | |
| 179 | 35x | return avail < direct_thr_; | ||
| 180 | } | |||
| 181 | ||||
| 182 | void | |||
| 183 | 96x | serializer:: | ||
| 184 | finalize(std::size_t remaining) noexcept | |||
| 185 | { | |||
| 186 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 88 times.
|
96x | if(head_) | |
| 187 | 8x | return; | ||
| 188 | ||||
| 189 |
2/2✓ Branch 0 taken 27 times.
✓ Branch 1 taken 61 times.
|
88x | if(enc_) | |
| 190 | { | |||
| 191 |
2/2✓ Branch 0 taken 10 times.
✓ Branch 1 taken 17 times.
|
27x | if(enc_started_) | |
| 192 | 10x | return; | ||
| 193 | ||||
| 194 |
2/2✓ Branch 0 taken 11 times.
✓ Branch 1 taken 6 times.
|
17x | if(remaining + in_len_ >= enc_thr_) | |
| 195 | 11x | return; | ||
| 196 | ||||
| 197 | 6x | msg_->erase(http::field::content_encoding); | ||
| 198 | 6x | enc_ = nullptr; | ||
| 199 | } | |||
| 200 | 67x | decide_framing(remaining); | ||
| 201 | } | |||
| 202 | ||||
| 203 | void | |||
| 204 | 87x | serializer:: | ||
| 205 | decide_framing(std::size_t remaining) noexcept | |||
| 206 | { | |||
| 207 |
6/6✓ Branch 1 taken 37 times.
✓ Branch 2 taken 50 times.
✓ Branch 3 taken 19 times.
✓ Branch 4 taken 18 times.
✓ Branch 5 taken 69 times.
✓ Branch 6 taken 18 times.
|
87x | if(!msg_->chunked() || hdr_sent_) | |
| 208 | 69x | return; | ||
| 209 | ||||
| 210 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
|
18x | BOOST_ASSERT(total_body_ == 0); | |
| 211 | 18x | msg_->erase(http::field::transfer_encoding); | ||
| 212 |
2/2✓ Branch 0 taken 8 times.
✓ Branch 1 taken 10 times.
|
18x | msg_->set_content_length((enc_ ? out_len_ : in_len_) + remaining); | |
| 213 | } | |||
| 214 | ||||
| 215 | capy::io_task<std::size_t> | |||
| 216 | 172x | serializer:: | ||
| 217 | process( | |||
| 218 | std::span<capy::const_buffer const> tail, | |||
| 219 | bool eof) | |||
| 220 | { | |||
| 221 |
2/2✓ Branch 0 taken 71 times.
✓ Branch 1 taken 101 times.
|
172x | if(enc_) | |
| 222 | 71x | return encode(tail, eof); | ||
| 223 | 101x | return flush(tail, eof); | ||
| 224 | } | |||
| 225 | ||||
| 226 | capy::io_task<std::size_t> | |||
| 227 |
1/1✓ Branch 1 taken 71 times.
|
71x | serializer:: | |
| 228 | encode( | |||
| 229 | std::span<capy::const_buffer const> tail, | |||
| 230 | bool eof) | |||
| 231 | { | |||
| 232 | std::size_t n = 0; | |||
| 233 | std::size_t consumed = 0; | |||
| 234 | capy::const_buffer in = { in_, in_len_ }; | |||
| 235 | for(;;) | |||
| 236 | { | |||
| 237 | if(in.size() == 0) | |||
| 238 | { | |||
| 239 | if(n != tail.size()) | |||
| 240 | in = tail[n++]; | |||
| 241 | else if(!eof) | |||
| 242 | co_return { {}, consumed }; | |||
| 243 | } | |||
| 244 | ||||
| 245 | if(out_len_ == out_cap_) | |||
| 246 | { | |||
| 247 | if(auto [ec, _] = co_await flush({}, false); ec) | |||
| 248 | co_return { ec, consumed }; | |||
| 249 | } | |||
| 250 | ||||
| 251 | auto r = enc_->process( | |||
| 252 | { out_ + out_len_, out_cap_ - out_len_ }, | |||
| 253 | in, | |||
| 254 | eof && n == tail.size()); | |||
| 255 | ||||
| 256 | enc_started_ = true; | |||
| 257 | out_len_ += r.produced; | |||
| 258 | in += r.consumed; | |||
| 259 | if(in_len_ != 0) | |||
| 260 | in_len_ -= r.consumed; | |||
| 261 | else | |||
| 262 | consumed += r.consumed; | |||
| 263 | ||||
| 264 | if(r.ec) | |||
| 265 | { | |||
| 266 | if(r.ec == capy::cond::eof) | |||
| 267 | { | |||
| 268 | decide_framing(0); | |||
| 269 | auto [ec, _] = co_await flush({}, true); | |||
| 270 | co_return { ec, consumed }; | |||
| 271 | } | |||
| 272 | else | |||
| 273 | { | |||
| 274 | co_return { r.ec, consumed }; | |||
| 275 | } | |||
| 276 | } | |||
| 277 | ||||
| 278 | if(!eof && consumed != 0) | |||
| 279 | co_return { {}, consumed }; | |||
| 280 | } | |||
| 281 | 142x | } | ||
| 282 | ||||
| 283 | capy::io_task<std::size_t> | |||
| 284 |
1/1✓ Branch 1 taken 141 times.
|
141x | serializer:: | |
| 285 | flush( | |||
| 286 | std::span<capy::const_buffer const> tail, | |||
| 287 | bool eof) | |||
| 288 | { | |||
| 289 | auto const buf = enc_ ? out_ : in_; | |||
| 290 | auto& len = enc_ ? out_len_ : in_len_; | |||
| 291 | auto const tail_len = capy::buffer_size(tail); | |||
| 292 | auto const chunked = msg_->chunked() && !head_; | |||
| 293 | BOOST_ASSERT(eof || len + tail_len != 0); | |||
| 294 | BOOST_ASSERT(tail.size() <= capy::detail::max_iovec_); | |||
| 295 | ||||
| 296 | capy::const_buffer vec[capy::detail::max_iovec_ + 3]; | |||
| 297 | std::size_t n = 0; | |||
| 298 | std::size_t sum = 0; | |||
| 299 | 237x | auto const append = [&](capy::const_buffer b) | ||
| 300 | { | |||
| 301 | 237x | vec[n++] = b; | ||
| 302 | 237x | sum += b.size(); | ||
| 303 | 237x | }; | ||
| 304 | ||||
| 305 | if(!hdr_sent_) | |||
| 306 | append(capy::make_buffer(msg_->buffer())); | |||
| 307 | ||||
| 308 | if(chunked) | |||
| 309 | { | |||
| 310 | static constexpr char hex[] = "0123456789abcdef"; | |||
| 311 | auto s = len + tail_len; | |||
| 312 | auto p = buf; | |||
| 313 | ||||
| 314 | *--p = '\n'; | |||
| 315 | *--p = '\r'; | |||
| 316 | ||||
| 317 | do | |||
| 318 | { | |||
| 319 | *--p = hex[s & 0xF]; | |||
| 320 | s >>= 4; | |||
| 321 | } while(s != 0); | |||
| 322 | ||||
| 323 | // prev chunk's CRLF | |||
| 324 | if(total_body_) | |||
| 325 | { | |||
| 326 | *--p = '\n'; | |||
| 327 | *--p = '\r'; | |||
| 328 | } | |||
| 329 | ||||
| 330 | append({ p, static_cast<std::size_t>(buf - p) + len }); | |||
| 331 | } | |||
| 332 | else | |||
| 333 | { | |||
| 334 | 88x | auto const decl = [&]()-> std::uint64_t | ||
| 335 | { | |||
| 336 |
6/6✓ Branch 0 taken 79 times.
✓ Branch 1 taken 9 times.
✓ Branch 3 taken 34 times.
✓ Branch 4 taken 45 times.
✓ Branch 5 taken 43 times.
✓ Branch 6 taken 45 times.
|
88x | if(head_ || msg_->payload() != http::payload::size) | |
| 337 | 43x | return 0; | ||
| 338 | 45x | return msg_->payload_size(); | ||
| 339 | }(); | |||
| 340 | auto const prod = total_body_ + len + tail_len; | |||
| 341 | ||||
| 342 | if(prod > decl || (eof && prod != decl)) | |||
| 343 | co_return { error::body_size_mismatch, 0 }; | |||
| 344 | ||||
| 345 | if(len != 0) | |||
| 346 | append({ buf, len }); | |||
| 347 | } | |||
| 348 | ||||
| 349 | auto const owned = sum; | |||
| 350 | ||||
| 351 | for(auto& b : tail) | |||
| 352 | append(b); | |||
| 353 | ||||
| 354 | if(chunked && eof) | |||
| 355 | append({ "\r\n0\r\n\r\n", len || tail_len ? 7u : 2u }); | |||
| 356 | ||||
| 357 | auto const need = chunked || eof ? sum : owned + !!tail_len; | |||
| 358 | auto [ec, written] = co_await capy::write_at_least( | |||
| 359 | *stream_, std::span{ vec, n }, need); | |||
| 360 | if(ec) | |||
| 361 | co_return { ec, 0 }; | |||
| 362 | ||||
| 363 | auto const consumed = (std::min)(written - owned, tail_len); | |||
| 364 | total_body_ += len + consumed; | |||
| 365 | len = 0; | |||
| 366 | hdr_sent_ = true; | |||
| 367 | done_ = eof; | |||
| 368 | co_return { {}, consumed }; | |||
| 369 | 282x | } | ||
| 370 | ||||
| 371 | } // namespace detail | |||
| 372 | } // namespace burl | |||
| 373 | } // namespace boost | |||
| 374 |