LCOV - code coverage report
Current view: top level - libs/http_proto/src - parser.cpp (source / functions) Coverage Total Hit
Test: coverage_filtered.info Lines: 85.4 % 886 757
Test Date: 2025-12-18 06:34:36 Functions: 85.7 % 98 84

            Line data    Source code
       1              : //
       2              : // Copyright (c) 2019 Vinnie Falco (vinnie.falco@gmail.com)
       3              : // Copyright (c) 2024 Mohammad Nejati
       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/detail/except.hpp>
      12              : #include <boost/http_proto/error.hpp>
      13              : #include <boost/http_proto/parser.hpp>
      14              : 
      15              : #include <boost/assert.hpp>
      16              : #include <boost/buffers/circular_buffer.hpp>
      17              : #include <boost/buffers/copy.hpp>
      18              : #include <boost/buffers/flat_buffer.hpp>
      19              : #include <boost/buffers/front.hpp>
      20              : #include <boost/buffers/slice.hpp>
      21              : #include <boost/capy/brotli/decode.hpp>
      22              : #include <boost/capy/polystore.hpp>
      23              : #include <boost/capy/zlib/error.hpp>
      24              : #include <boost/capy/zlib/inflate.hpp>
      25              : #include <boost/url/grammar/ci_string.hpp>
      26              : #include <boost/url/grammar/error.hpp>
      27              : #include <boost/url/grammar/hexdig_chars.hpp>
      28              : 
      29              : #include "src/detail/brotli_filter_base.hpp"
      30              : #include "src/detail/buffer_utils.hpp"
      31              : #include "src/detail/zlib_filter_base.hpp"
      32              : 
      33              : namespace boost {
      34              : namespace http_proto {
      35              : 
      36              : /*
      37              :     Principles for fixed-size buffer design
      38              : 
      39              :     axiom 1:
      40              :         To read data you must have a buffer.
      41              : 
      42              :     axiom 2:
      43              :         The size of the HTTP header is not
      44              :         known in advance.
      45              : 
      46              :     conclusion 3:
      47              :         A single I/O can produce a complete
      48              :         HTTP header and additional payload
      49              :         data.
      50              : 
      51              :     conclusion 4:
      52              :         A single I/O can produce multiple
      53              :         complete HTTP headers, complete
      54              :         payloads, and a partial header or
      55              :         payload.
      56              : 
      57              :     axiom 5:
      58              :         A process is in one of two states:
      59              :             1. at or below capacity
      60              :             2. above capacity
      61              : 
      62              :     axiom 6:
      63              :         A program which can allocate an
      64              :         unbounded number of resources can
      65              :         go above capacity.
      66              : 
      67              :     conclusion 7:
      68              :         A program can guarantee never going
      69              :         above capacity if all resources are
      70              :         provisioned at program startup.
      71              : 
      72              :     corollary 8:
      73              :         `parser` and `serializer` should each
      74              :         allocate a single buffer of calculated
      75              :         size, and never resize it.
      76              : 
      77              :     axiom #:
      78              :         A parser and a serializer are always
      79              :         used in pairs.
      80              : 
      81              : Buffer Usage
      82              : 
      83              : |                                               | begin
      84              : | H |   p   |                               | f | read headers
      85              : | H |   p   |                           | T | f | set T body
      86              : | H |   p   |                       | C | T | f | make codec C
      87              : | H |   p           |       b       | C | T | f | decode p into b
      88              : | H |       p       |       b       | C | T | f | read/parse loop
      89              : | H |                                   | T | f | destroy codec
      90              : | H |                                   | T | f | finished
      91              : 
      92              :     H   headers
      93              :     C   codec
      94              :     T   body
      95              :     f   table
      96              :     p   partial payload
      97              :     b   body data
      98              : 
      99              :     "payload" is the bytes coming in from
     100              :         the stream.
     101              : 
     102              :     "body" is the logical body, after transfer
     103              :         encoding is removed. This can be the
     104              :         same as the payload.
     105              : 
     106              :     A "plain payload" is when the payload and
     107              :         body are identical (no transfer encodings).
     108              : 
     109              :     A "buffered payload" is any payload which is
     110              :         not plain. A second buffer is required
     111              :         for reading.
     112              : 
     113              :     "overread" is additional data received past
     114              :     the end of the headers when reading headers,
     115              :     or additional data received past the end of
     116              :     the message payload.
     117              : */
     118              : 
     119              : namespace {
     120              : 
     121              : class chained_sequence
     122              : {
     123              :     char const* pos_;
     124              :     char const* end_;
     125              :     char const* begin_b_;
     126              :     char const* end_b_;
     127              : 
     128              : public:
     129       120197 :     chained_sequence(buffers::const_buffer_pair const& cbp)
     130       120197 :         : pos_(static_cast<char const*>(cbp[0].data()))
     131       120197 :         , end_(pos_ + cbp[0].size())
     132       120197 :         , begin_b_(static_cast<char const*>(cbp[1].data()))
     133       120197 :         , end_b_(begin_b_ + cbp[1].size())
     134              :     {
     135       120197 :     }
     136              : 
     137              :     char const*
     138       618747 :     next() noexcept
     139              :     {
     140       618747 :         ++pos_;
     141              :         // most frequently taken branch
     142       618747 :         if(pos_ < end_)
     143       597426 :             return pos_;
     144              : 
     145              :         // bring the second range
     146        21321 :         if(begin_b_ != end_b_)
     147              :         {
     148           38 :             pos_ = begin_b_;
     149           38 :             end_ = end_b_;
     150           38 :             begin_b_ = end_b_;
     151           38 :             return pos_;
     152              :         }
     153              : 
     154              :         // undo the increament
     155        21283 :         pos_ = end_;
     156        21283 :         return nullptr;
     157              :     }
     158              : 
     159              :     bool
     160       410999 :     is_empty() const noexcept
     161              :     {
     162       410999 :         return pos_ == end_;
     163              :     }
     164              : 
     165              :     char
     166       604240 :     value() const noexcept
     167              :     {
     168       604240 :         return *pos_;
     169              :     }
     170              : 
     171              :     std::size_t
     172       424974 :     size() const noexcept
     173              :     {
     174       424974 :         return (end_ - pos_) + (end_b_ - begin_b_);
     175              :     }
     176              : };
     177              : 
     178              : std::uint64_t
     179       115957 : parse_hex(
     180              :     chained_sequence& cs,
     181              :     system::error_code& ec) noexcept
     182              : {
     183       115957 :     std::uint64_t v   = 0;
     184       115957 :     std::size_t init_size = cs.size();
     185       302778 :     while(!cs.is_empty())
     186              :     {
     187       283496 :         auto n = grammar::hexdig_value(cs.value());
     188       283496 :         if(n < 0)
     189              :         {
     190        96674 :             if(init_size == cs.size())
     191              :             {
     192            2 :                 ec = BOOST_HTTP_PROTO_ERR(
     193              :                     error::bad_payload);
     194            1 :                 return 0;
     195              :             }
     196        96673 :             return v;
     197              :         }
     198              : 
     199              :         // at least 4 significant bits are free
     200       186822 :         if(v > (std::numeric_limits<std::uint64_t>::max)() >> 4)
     201              :         {
     202            2 :             ec = BOOST_HTTP_PROTO_ERR(
     203              :                 error::bad_payload);
     204            1 :             return 0;
     205              :         }
     206              : 
     207       186821 :         v = (v << 4) | static_cast<std::uint64_t>(n);
     208       186821 :         cs.next();
     209              :     }
     210        38564 :     ec = BOOST_HTTP_PROTO_ERR(
     211              :         error::need_data);
     212        19282 :     return 0;
     213              : }
     214              : 
     215              : void
     216        97025 : find_eol(
     217              :     chained_sequence& cs,
     218              :     system::error_code& ec) noexcept
     219              : {
     220       103714 :     while(!cs.is_empty())
     221              :     {
     222       103626 :         if(cs.value() == '\r')
     223              :         {
     224        96937 :             if(!cs.next())
     225           10 :                 break;
     226        96927 :             if(cs.value() != '\n')
     227              :             {
     228            4 :                 ec = BOOST_HTTP_PROTO_ERR(
     229              :                     error::bad_payload);
     230            2 :                 return;
     231              :             }
     232        96925 :             cs.next();
     233        96925 :             return;
     234              :         }
     235         6689 :         cs.next();
     236              :     }
     237          196 :     ec = BOOST_HTTP_PROTO_ERR(
     238              :         error::need_data);
     239              : }
     240              : 
     241              : void
     242       111557 : parse_eol(
     243              :     chained_sequence& cs,
     244              :     system::error_code& ec) noexcept
     245              : {
     246       111557 :     if(cs.size() >= 2)
     247              :     {
     248              :         // we are sure size is at least 2
     249       111543 :         if(cs.value() == '\r' && *cs.next() == '\n')
     250              :         {
     251       111540 :             cs.next();
     252       111540 :             return;
     253              :         }
     254            6 :         ec = BOOST_HTTP_PROTO_ERR(
     255              :             error::bad_payload);
     256            3 :         return;
     257              :     }
     258           28 :     ec = BOOST_HTTP_PROTO_ERR(
     259              :         error::need_data);
     260              : }
     261              : 
     262              : void
     263         4223 : skip_trailer_headers(
     264              :     chained_sequence& cs,
     265              :     system::error_code& ec) noexcept
     266              : {
     267         4507 :     while(!cs.is_empty())
     268              :     {
     269         4501 :         if(cs.value() == '\r')
     270              :         {
     271         4149 :             if(!cs.next())
     272            2 :                 break;
     273         4147 :             if(cs.value() != '\n')
     274              :             {
     275            4 :                 ec = BOOST_HTTP_PROTO_ERR(
     276              :                     error::bad_payload);
     277            2 :                 return;
     278              :             }
     279         4145 :             cs.next();
     280         4145 :             return;
     281              :         }
     282              :         // skip to the end of field
     283          352 :         find_eol(cs, ec);
     284          352 :         if(ec)
     285           68 :             return;
     286              :     }
     287           16 :     ec = BOOST_HTTP_PROTO_ERR(
     288              :         error::need_data);
     289              : }
     290              : 
     291              : template<class UInt>
     292              : std::size_t
     293       360930 : clamp(
     294              :     UInt x,
     295              :     std::size_t limit = (std::numeric_limits<
     296              :         std::size_t>::max)()) noexcept
     297              : {
     298       360930 :     if(x >= limit)
     299       101553 :         return limit;
     300       259377 :     return static_cast<std::size_t>(x);
     301              : }
     302              : 
     303              : class zlib_filter
     304              :     : public detail::zlib_filter_base
     305              : {
     306              :     capy::zlib::inflate_service& svc_;
     307              : 
     308              : public:
     309           72 :     zlib_filter(
     310              :         const capy::polystore& ctx,
     311              :         http_proto::detail::workspace& ws,
     312              :         int window_bits)
     313           72 :         : zlib_filter_base(ws)
     314           72 :         , svc_(ctx.get<capy::zlib::inflate_service>())
     315              :     {
     316              :         system::error_code ec = static_cast<capy::zlib::error>(
     317           72 :             svc_.init2(strm_, window_bits));
     318           72 :         if(ec != capy::zlib::error::ok)
     319            0 :             detail::throw_system_error(ec);
     320           72 :     }
     321              : 
     322              : private:
     323              :     virtual
     324              :     results
     325        56581 :     do_process(
     326              :         buffers::mutable_buffer out,
     327              :         buffers::const_buffer in,
     328              :         bool more) noexcept override
     329              :     {
     330        56581 :         strm_.next_out  = static_cast<unsigned char*>(out.data());
     331        56581 :         strm_.avail_out = saturate_cast(out.size());
     332        56581 :         strm_.next_in   = static_cast<unsigned char*>(const_cast<void *>(in.data()));
     333        56581 :         strm_.avail_in  = saturate_cast(in.size());
     334              : 
     335              :         auto rs = static_cast<capy::zlib::error>(
     336        56581 :             svc_.inflate(
     337        56581 :                 strm_,
     338              :                 more ? capy::zlib::no_flush : capy::zlib::finish));
     339              : 
     340        56581 :         results rv;
     341        56581 :         rv.out_bytes = saturate_cast(out.size()) - strm_.avail_out;
     342        56581 :         rv.in_bytes  = saturate_cast(in.size()) - strm_.avail_in;
     343        56581 :         rv.finished  = (rs == capy::zlib::error::stream_end);
     344              : 
     345        56581 :         if(rs < capy::zlib::error::ok && rs != capy::zlib::error::buf_err)
     346            0 :             rv.ec = rs;
     347              : 
     348        56581 :         return rv;
     349              :     }
     350              : };
     351              : 
     352              : class brotli_filter
     353              :     : public detail::brotli_filter_base
     354              : {
     355              :     capy::brotli::decode_service& svc_;
     356              :     capy::brotli::decoder_state* state_;
     357              : 
     358              : public:
     359            0 :     brotli_filter(
     360              :         const capy::polystore& ctx,
     361              :         http_proto::detail::workspace&)
     362            0 :         : svc_(ctx.get<capy::brotli::decode_service>())
     363              :     {
     364              :         // TODO: use custom allocator
     365            0 :         state_ = svc_.create_instance(nullptr, nullptr, nullptr);
     366              : 
     367            0 :         if(!state_)
     368            0 :             detail::throw_bad_alloc();
     369            0 :     }
     370              : 
     371            0 :     ~brotli_filter()
     372            0 :     {
     373            0 :         svc_.destroy_instance(state_);
     374            0 :     }
     375              : 
     376              : private:
     377              :     virtual
     378              :     results
     379            0 :     do_process(
     380              :         buffers::mutable_buffer out,
     381              :         buffers::const_buffer in,
     382              :         bool more) noexcept override
     383              :     {
     384            0 :         auto* next_in = reinterpret_cast<const std::uint8_t*>(in.data());
     385            0 :         auto available_in = in.size();
     386            0 :         auto* next_out = reinterpret_cast<std::uint8_t*>(out.data());
     387            0 :         auto available_out = out.size();
     388              : 
     389            0 :         auto rs = svc_.decompress_stream(
     390              :             state_,
     391              :             &available_in,
     392              :             &next_in,
     393              :             &available_out,
     394              :             &next_out,
     395              :             nullptr);
     396              : 
     397            0 :         results rv;
     398            0 :         rv.in_bytes  = in.size()  - available_in;
     399            0 :         rv.out_bytes = out.size() - available_out;
     400            0 :         rv.finished  = svc_.is_finished(state_);
     401              : 
     402            0 :         if(!more && rs == capy::brotli::decoder_result::needs_more_input)
     403            0 :             rv.ec = BOOST_HTTP_PROTO_ERR(error::bad_payload);
     404              : 
     405            0 :         if(rs == capy::brotli::decoder_result::error)
     406            0 :             rv.ec = BOOST_HTTP_PROTO_ERR(
     407              :                 svc_.get_error_code(state_));
     408              : 
     409            0 :         return rv;
     410              :     }
     411              : };
     412              : 
     413              : class parser_service
     414              : {
     415              : public:
     416              :     parser::config_base cfg;
     417              :     std::size_t space_needed = 0;
     418              :     std::size_t max_codec = 0;
     419              : 
     420           43 :     parser_service(
     421              :         parser::config_base const& cfg_)
     422           43 :         : cfg(cfg_)
     423              :     {
     424              :     /*
     425              :         | fb |     cb0     |     cb1     | C | T | f |
     426              : 
     427              :         fb  flat_buffer         headers.max_size
     428              :         cb0 circular_buffer     min_buffer
     429              :         cb1 circular_buffer     min_buffer
     430              :         C   codec               max_codec
     431              :         T   body                max_type_erase
     432              :         f   table               max_table_space
     433              : 
     434              :     */
     435              :         // validate
     436              :         //if(cfg.min_prepare > cfg.max_prepare)
     437              :             //detail::throw_invalid_argument();
     438              : 
     439           43 :         if(cfg.max_prepare < 1)
     440            0 :             detail::throw_invalid_argument();
     441              : 
     442              :         // VFALCO TODO OVERFLOW CHECING
     443              :         {
     444              :             //fb_.size() - h_.size +
     445              :             //svc_.cfg.min_buffer +
     446              :             //svc_.cfg.min_buffer +
     447              :             //svc_.max_codec;
     448              :         }
     449              : 
     450              :         // VFALCO OVERFLOW CHECKING ON THIS
     451           43 :         space_needed +=
     452           43 :             cfg.headers.valid_space_needed();
     453              : 
     454              :         // cb0_, cb1_
     455              :         // VFALCO OVERFLOW CHECKING ON THIS
     456           43 :         space_needed +=
     457           43 :             cfg.min_buffer +
     458              :             cfg.min_buffer;
     459              : 
     460              :         // T
     461           43 :         space_needed += cfg.max_type_erase;
     462              : 
     463              :         // max_codec
     464           43 :         if(cfg.apply_deflate_decoder || cfg.apply_gzip_decoder)
     465              :         {
     466              :             // TODO: Account for the number of allocations and
     467              :             // their overhead in the workspace.
     468              : 
     469              :             // https://www.zlib.net/zlib_tech.html
     470              :             std::size_t n =
     471            1 :                 (1 << cfg.zlib_window_bits) +
     472              :                 (7 * 1024) +
     473              :                 #ifdef __s390x__
     474              :                 5768 +
     475              :                 #endif
     476              :                 detail::workspace::space_needed<
     477            1 :                     zlib_filter>();
     478              : 
     479            1 :             if(max_codec < n)
     480            1 :                 max_codec = n;
     481              :         }
     482           43 :         space_needed += max_codec;
     483              : 
     484              :         // round up to alignof(detail::header::entry)
     485           43 :         auto const al = alignof(
     486              :             detail::header::entry);
     487           43 :         space_needed = al * ((
     488           43 :             space_needed + al - 1) / al);
     489           43 :     }
     490              : 
     491              :     std::size_t
     492        55255 :     max_overread() const noexcept
     493              :     {
     494              :         return
     495        55255 :             cfg.headers.max_size +
     496        55255 :             cfg.min_buffer;
     497              :     }
     498              : };
     499              : 
     500              : } // namespace
     501              : 
     502              : //------------------------------------------------
     503              : 
     504              : void
     505           43 : install_parser_service(
     506              :     capy::polystore& ctx,
     507              :     parser::config_base const& cfg)
     508              : {
     509           43 :     ctx.emplace<parser_service>(cfg);
     510           43 : }
     511              : 
     512              : //------------------------------------------------
     513              : 
     514              : class parser::impl
     515              : {
     516              :     enum class state
     517              :     {
     518              :         reset,
     519              :         start,
     520              :         header,
     521              :         header_done,
     522              :         body,
     523              :         set_body,
     524              :         complete_in_place,
     525              :         complete
     526              :     };
     527              : 
     528              :     enum class style
     529              :     {
     530              :         in_place,
     531              :         sink,
     532              :         elastic,
     533              :     };
     534              : 
     535              :     const capy::polystore& ctx_;
     536              :     parser_service& svc_;
     537              : 
     538              :     detail::workspace ws_;
     539              :     detail::header h_;
     540              :     std::uint64_t body_limit_;
     541              :     std::uint64_t body_total_;
     542              :     std::uint64_t payload_remain_;
     543              :     std::uint64_t chunk_remain_;
     544              :     std::size_t body_avail_;
     545              :     std::size_t nprepare_;
     546              : 
     547              :     buffers::flat_buffer fb_;
     548              :     buffers::circular_buffer cb0_;
     549              :     buffers::circular_buffer cb1_;
     550              : 
     551              :     buffers::mutable_buffer_pair mbp_;
     552              :     buffers::const_buffer_pair cbp_;
     553              : 
     554              :     detail::filter* filter_;
     555              :     buffers::any_dynamic_buffer* eb_;
     556              :     sink* sink_;
     557              : 
     558              :     state state_;
     559              :     style style_;
     560              :     bool got_header_;
     561              :     bool got_eof_;
     562              :     bool head_response_;
     563              :     bool needs_chunk_close_;
     564              :     bool trailer_headers_;
     565              :     bool chunked_body_ended;
     566              : 
     567              : public:
     568         1056 :     impl(const capy::polystore& ctx, detail::kind k)
     569         1056 :         : ctx_(ctx)
     570         1056 :         , svc_(ctx.get<parser_service>())
     571         1056 :         , ws_(svc_.space_needed)
     572         1056 :         , h_(detail::empty{ k })
     573         1056 :         , state_(state::reset)
     574         1056 :         , got_header_(false)
     575              :     {
     576         1056 :     }
     577              : 
     578              :     bool
     579        11938 :     got_header() const noexcept
     580              :     {
     581        11938 :         return got_header_;
     582              :     }
     583              : 
     584              :     bool
     585        51344 :     is_complete() const noexcept
     586              :     {
     587        51344 :         return state_ >= state::complete_in_place;
     588              :     }
     589              : 
     590              :     detail::header const&
     591          316 :     safe_get_header() const
     592              :     {
     593              :         // headers must be received
     594          316 :         if(! got_header_)
     595            0 :             detail::throw_logic_error();
     596              : 
     597          316 :         return h_;
     598              :     }
     599              : 
     600              :     bool
     601          755 :     is_body_set() const noexcept
     602              :     {
     603          755 :         return style_ != style::in_place;
     604              :     }
     605              : 
     606              :     void
     607         2480 :     reset() noexcept
     608              :     {
     609         2480 :         ws_.clear();
     610         2480 :         state_ = state::start;
     611         2480 :         got_header_ = false;
     612         2480 :         got_eof_ = false;
     613         2480 :     }
     614              : 
     615              :     void
     616        10307 :     start(
     617              :         bool head_response)
     618              :     {
     619        10307 :         std::size_t leftover = 0;
     620        10307 :         switch(state_)
     621              :         {
     622            1 :         default:
     623              :         case state::reset:
     624              :             // reset must be called first
     625            1 :             detail::throw_logic_error();
     626              : 
     627         2255 :         case state::start:
     628              :             // reset required on eof
     629         2255 :             if(got_eof_)
     630            0 :                 detail::throw_logic_error();
     631         2255 :             break;
     632              : 
     633            3 :         case state::header:
     634            3 :             if(fb_.size() == 0)
     635              :             {
     636              :                 // start() called twice
     637            2 :                 detail::throw_logic_error();
     638              :             }
     639              :             BOOST_FALLTHROUGH;
     640              : 
     641              :         case state::header_done:
     642              :         case state::body:
     643              :         case state::set_body:
     644              :             // current message is incomplete
     645            2 :             detail::throw_logic_error();
     646              : 
     647         8015 :         case state::complete_in_place:
     648              :             // remove available body.
     649         8015 :             if(is_plain())
     650         4000 :                 cb0_.consume(body_avail_);
     651              :             BOOST_FALLTHROUGH;
     652              : 
     653              :         case state::complete:
     654              :         {
     655              :             // move leftovers to front
     656              : 
     657         8047 :             ws_.clear();
     658         8047 :             leftover = cb0_.size();
     659              : 
     660         8047 :             auto* dest = reinterpret_cast<char*>(ws_.data());
     661         8047 :             auto cbp   = cb0_.data();
     662         8047 :             auto* a    = static_cast<char const*>(cbp[0].data());
     663         8047 :             auto* b    = static_cast<char const*>(cbp[1].data());
     664         8047 :             auto an    = cbp[0].size();
     665         8047 :             auto bn    = cbp[1].size();
     666              : 
     667         8047 :             if(bn == 0)
     668              :             {
     669         7609 :                 std::memmove(dest, a, an);
     670              :             }
     671              :             else
     672              :             {
     673              :                 // if `a` can fit between `dest` and `b`, shift `b` to the left
     674              :                 // and copy `a` to its position. if `a` fits perfectly, the
     675              :                 // shift will be of size 0.
     676              :                 // if `a` requires more space, shift `b` to the right and
     677              :                 // copy `a` to its position. this process may require multiple
     678              :                 // iterations and should be done chunk by chunk to prevent `b`
     679              :                 // from overlapping with `a`.
     680              :                 do
     681              :                 {
     682              :                     // clamp right shifts to prevent overlap with `a`
     683          438 :                     auto* bp = (std::min)(dest + an, const_cast<char*>(a) - bn);
     684          438 :                     b = static_cast<char const*>(std::memmove(bp, b, bn));
     685              : 
     686              :                     // a chunk or all of `a` based on available space
     687          438 :                     auto chunk_a = static_cast<std::size_t>(b - dest);
     688          438 :                     std::memcpy(dest, a, chunk_a); // never overlap
     689          438 :                     an   -= chunk_a;
     690          438 :                     dest += chunk_a;
     691          438 :                     a    += chunk_a;
     692          438 :                 } while(an);
     693              :             }
     694              : 
     695         8047 :             break;
     696              :         }
     697              :         }
     698              : 
     699        10302 :         ws_.clear();
     700              : 
     701        20604 :         fb_ = {
     702        10302 :             ws_.data(),
     703        10302 :             svc_.cfg.headers.max_size + svc_.cfg.min_buffer,
     704              :             leftover };
     705              : 
     706        10302 :         BOOST_ASSERT(
     707              :             fb_.capacity() == svc_.max_overread() - leftover);
     708              : 
     709        10302 :         BOOST_ASSERT(
     710              :             head_response == false ||
     711              :             h_.kind == detail::kind::response);
     712              : 
     713        10302 :         h_ = detail::header(detail::empty{h_.kind});
     714        10302 :         h_.buf = reinterpret_cast<char*>(ws_.data());
     715        10302 :         h_.cbuf = h_.buf;
     716        10302 :         h_.cap = ws_.size();
     717              : 
     718        10302 :         state_ = state::header;
     719        10302 :         style_ = style::in_place;
     720              : 
     721              :         // reset to the configured default
     722        10302 :         body_limit_ = svc_.cfg.body_limit;
     723              : 
     724        10302 :         body_total_ = 0;
     725        10302 :         payload_remain_ = 0;
     726        10302 :         chunk_remain_ = 0;
     727        10302 :         body_avail_ = 0;
     728        10302 :         nprepare_ = 0;
     729              : 
     730        10302 :         filter_ = nullptr;
     731        10302 :         eb_ = nullptr;
     732        10302 :         sink_ = nullptr;
     733              : 
     734        10302 :         got_header_ = false;
     735        10302 :         head_response_ = head_response;
     736        10302 :         needs_chunk_close_ = false;
     737        10302 :         trailer_headers_ = false;
     738        10302 :         chunked_body_ended = false;
     739        10302 :     }
     740              : 
     741              :     auto
     742        51003 :     prepare() ->
     743              :         mutable_buffers_type
     744              :     {
     745        51003 :         nprepare_ = 0;
     746              : 
     747        51003 :         switch(state_)
     748              :         {
     749            1 :         default:
     750              :         case state::reset:
     751              :             // reset must be called first
     752            1 :             detail::throw_logic_error();
     753              : 
     754            1 :         case state::start:
     755              :             // start must be called first
     756            1 :             detail::throw_logic_error();
     757              : 
     758        10374 :         case state::header:
     759              :         {
     760        10374 :             BOOST_ASSERT(
     761              :                 h_.size < svc_.cfg.headers.max_size);
     762        10374 :             std::size_t n = fb_.capacity();
     763        10374 :             BOOST_ASSERT(n <= svc_.max_overread());
     764        10374 :             n = clamp(n, svc_.cfg.max_prepare);
     765        10374 :             mbp_[0] = fb_.prepare(n);
     766        10374 :             nprepare_ = n;
     767        10374 :             return mutable_buffers_type(&mbp_[0], 1);
     768              :         }
     769              : 
     770            0 :         case state::header_done:
     771              :             // forgot to call parse()
     772            0 :             detail::throw_logic_error();
     773              : 
     774        40626 :         case state::body:
     775              :         {
     776        40626 :             if(got_eof_)
     777              :             {
     778              :                 // forgot to call parse()
     779            0 :                 detail::throw_logic_error();
     780              :             }
     781              : 
     782        40626 :             if(! is_plain())
     783              :             {
     784              :                 // buffered payload
     785        21575 :                 std::size_t n = cb0_.capacity();
     786        21575 :                 n = clamp(n, svc_.cfg.max_prepare);
     787        21575 :                 nprepare_ = n;
     788        21575 :                 mbp_ = cb0_.prepare(n);
     789        21575 :                 return detail::make_span(mbp_);
     790              :             }
     791              :             else
     792              :             {
     793        19051 :                 switch(style_)
     794              :                 {
     795        19030 :                 default:
     796              :                 case style::in_place:
     797              :                 case style::sink:
     798              :                 {
     799        19030 :                     std::size_t n = cb0_.capacity();
     800        19030 :                     n = clamp(n, svc_.cfg.max_prepare);
     801              : 
     802        19030 :                     if(h_.md.payload == payload::size)
     803              :                     {
     804        19005 :                         if(n > payload_remain_)
     805              :                         {
     806        17798 :                             std::size_t overread =
     807        17798 :                                 n - static_cast<std::size_t>(payload_remain_);
     808        17798 :                             if(overread > svc_.max_overread())
     809         7878 :                                 n = static_cast<std::size_t>(payload_remain_) +
     810         7878 :                                     svc_.max_overread();
     811              :                         }
     812              :                     }
     813              :                     else
     814              :                     {
     815           25 :                         BOOST_ASSERT(
     816              :                             h_.md.payload == payload::to_eof);
     817              :                         // No more messages can be pipelined, so
     818              :                         // limit the output buffer to the remaining
     819              :                         // body limit plus one byte to detect
     820              :                         // exhaustion.
     821           25 :                         std::uint64_t r = body_limit_remain();
     822           25 :                         if(r != std::uint64_t(-1))
     823           25 :                             r += 1;
     824           25 :                         n = clamp(r, n);
     825              :                     }
     826              : 
     827        19030 :                     nprepare_ = n;
     828        19030 :                     mbp_ = cb0_.prepare(n);
     829        19030 :                     return detail::make_span(mbp_);
     830              :                 }
     831           21 :                 case style::elastic:
     832              :                 {
     833           21 :                     BOOST_ASSERT(cb0_.size() == 0);
     834           21 :                     BOOST_ASSERT(body_avail_ == 0);
     835              : 
     836           21 :                     std::size_t n = svc_.cfg.min_buffer;
     837              : 
     838           21 :                     if(h_.md.payload == payload::size)
     839              :                     {
     840              :                         // Overreads are not allowed, or
     841              :                         // else the caller will see extra
     842              :                         // unrelated data.
     843            6 :                         n = clamp(payload_remain_, n);
     844              :                     }
     845              :                     else
     846              :                     {
     847           15 :                         BOOST_ASSERT(
     848              :                             h_.md.payload == payload::to_eof);
     849              :                         // No more messages can be pipelined, so
     850              :                         // limit the output buffer to the remaining
     851              :                         // body limit plus one byte to detect
     852              :                         // exhaustion.
     853           15 :                         std::uint64_t r = body_limit_remain();
     854           15 :                         if(r != std::uint64_t(-1))
     855           15 :                             r += 1;
     856           15 :                         n = clamp(r, n);
     857           15 :                         n = clamp(n, eb_->max_size() - eb_->size());
     858              :                         // fill capacity first to avoid an allocation
     859              :                         std::size_t avail =
     860           15 :                             eb_->capacity() - eb_->size();
     861           15 :                         if(avail != 0)
     862           15 :                             n = clamp(n, avail);
     863              : 
     864           15 :                         if(n == 0)
     865              :                         {
     866              :                             // dynamic buffer is full
     867              :                             // attempt a 1 byte read so
     868              :                             // we can detect overflow
     869            1 :                             nprepare_ = 1;
     870            1 :                             mbp_ = cb0_.prepare(1);
     871            1 :                             return detail::make_span(mbp_);
     872              :                         }
     873              :                     }
     874              : 
     875           20 :                     n = clamp(n, svc_.cfg.max_prepare);
     876           20 :                     BOOST_ASSERT(n != 0);
     877           20 :                     nprepare_ = n;
     878           20 :                     return eb_->prepare(n);
     879              :                 }
     880              :                 }
     881              :             }
     882              :         }
     883              : 
     884            0 :         case state::set_body:
     885              :             // forgot to call parse()
     886            0 :             detail::throw_logic_error();
     887              : 
     888            1 :         case state::complete_in_place:
     889              :         case state::complete:
     890              :             // already complete
     891            1 :             detail::throw_logic_error();
     892              :         }
     893              :     }
     894              : 
     895              :     void
     896        51000 :     commit(
     897              :         std::size_t n)
     898              :     {
     899        51000 :         switch(state_)
     900              :         {
     901            1 :         default:
     902              :         case state::reset:
     903              :         {
     904              :             // reset must be called first
     905            1 :             detail::throw_logic_error();
     906              :         }
     907              : 
     908            1 :         case state::start:
     909              :         {
     910              :             // forgot to call start()
     911            1 :             detail::throw_logic_error();
     912              :         }
     913              : 
     914        10374 :         case state::header:
     915              :         {
     916        10374 :             if(n > nprepare_)
     917              :             {
     918              :                 // n can't be greater than size of
     919              :                 // the buffers returned by prepare()
     920            1 :                 detail::throw_invalid_argument();
     921              :             }
     922              : 
     923        10373 :             if(got_eof_)
     924              :             {
     925              :                 // can't commit after EOF
     926            1 :                 detail::throw_logic_error();
     927              :             }
     928              : 
     929        10372 :             nprepare_ = 0; // invalidate
     930        10372 :             fb_.commit(n);
     931        10372 :             break;
     932              :         }
     933              : 
     934            0 :         case state::header_done:
     935              :         {
     936              :             // forgot to call parse()
     937            0 :             detail::throw_logic_error();
     938              :         }
     939              : 
     940        40623 :         case state::body:
     941              :         {
     942        40623 :             if(n > nprepare_)
     943              :             {
     944              :                 // n can't be greater than size of
     945              :                 // the buffers returned by prepare()
     946            2 :                 detail::throw_invalid_argument();
     947              :             }
     948              : 
     949        40621 :             if(got_eof_)
     950              :             {
     951              :                 // can't commit after EOF
     952            0 :                 detail::throw_logic_error();
     953              :             }
     954              :         
     955        40621 :             nprepare_ = 0; // invalidate
     956        40621 :             if(is_plain() && style_ == style::elastic)
     957              :             {
     958           20 :                 if(eb_->max_size() == eb_->size())
     959              :                 {
     960              :                     // borrowed 1 byte from
     961              :                     // cb0_ in prepare()
     962            1 :                     BOOST_ASSERT(n <= 1);
     963            1 :                     cb0_.commit(n);
     964              :                 }
     965              :                 else
     966              :                 {
     967           19 :                     eb_->commit(n);
     968           19 :                     payload_remain_ -= n;
     969           19 :                     body_total_     += n;
     970              :                 }
     971              :             }
     972              :             else
     973              :             {
     974        40601 :                 cb0_.commit(n);
     975              :             }
     976        40621 :             break;
     977              :         }
     978              : 
     979            0 :         case state::set_body:
     980              :         {
     981              :             // forgot to call parse()
     982            0 :             detail::throw_logic_error();
     983              :         }
     984              : 
     985            1 :         case state::complete_in_place:
     986              :         case state::complete:
     987              :         {
     988              :             // already complete
     989            1 :             detail::throw_logic_error();
     990              :         }
     991              :         }
     992        50993 :     }
     993              : 
     994              :     void
     995          401 :     commit_eof()
     996              :     {
     997          401 :         nprepare_ = 0; // invalidate
     998              : 
     999          401 :         switch(state_)
    1000              :         {
    1001            1 :         default:
    1002              :         case state::reset:
    1003              :             // reset must be called first
    1004            1 :             detail::throw_logic_error();
    1005              : 
    1006            1 :         case state::start:
    1007              :             // forgot to call start()
    1008            1 :             detail::throw_logic_error();
    1009              : 
    1010           30 :         case state::header:
    1011           30 :             got_eof_ = true;
    1012           30 :             break;
    1013              : 
    1014            0 :         case state::header_done:
    1015              :             // forgot to call parse()
    1016            0 :             detail::throw_logic_error();
    1017              : 
    1018          368 :         case state::body:
    1019          368 :             got_eof_ = true;
    1020          368 :             break;
    1021              : 
    1022            0 :         case state::set_body:
    1023              :             // forgot to call parse()
    1024            0 :             detail::throw_logic_error();
    1025              : 
    1026            1 :         case state::complete_in_place:
    1027              :         case state::complete:
    1028              :             // can't commit eof when complete
    1029            1 :             detail::throw_logic_error();
    1030              :         }
    1031          398 :     }
    1032              : 
    1033              :     void
    1034        69827 :     parse(
    1035              :         system::error_code& ec)
    1036              :     {
    1037        69827 :         ec = {};
    1038        69827 :         switch(state_)
    1039              :         {
    1040            1 :         default:
    1041              :         case state::reset:
    1042              :             // reset must be called first
    1043            1 :             detail::throw_logic_error();
    1044              : 
    1045            1 :         case state::start:
    1046              :             // start must be called first
    1047            1 :             detail::throw_logic_error();
    1048              : 
    1049        16650 :         case state::header:
    1050              :         {
    1051        16650 :             BOOST_ASSERT(h_.buf == static_cast<
    1052              :                 void const*>(ws_.data()));
    1053        16650 :             BOOST_ASSERT(h_.cbuf == static_cast<
    1054              :                 void const*>(ws_.data()));
    1055              : 
    1056        16650 :             h_.parse(fb_.size(), svc_.cfg.headers, ec);
    1057              : 
    1058        16650 :             if(ec == condition::need_more_input)
    1059              :             {
    1060         6385 :                 if(! got_eof_)
    1061              :                 {
    1062              :                     // headers incomplete
    1063         6358 :                     return;
    1064              :                 }
    1065              : 
    1066           27 :                 if(fb_.size() == 0)
    1067              :                 {
    1068              :                     // stream closed cleanly
    1069           12 :                     state_ = state::reset;
    1070           24 :                     ec = BOOST_HTTP_PROTO_ERR(
    1071              :                         error::end_of_stream);
    1072           12 :                     return;
    1073              :                 }
    1074              : 
    1075              :                 // stream closed with a
    1076              :                 // partial message received
    1077           15 :                 state_ = state::reset;
    1078           30 :                 ec = BOOST_HTTP_PROTO_ERR(
    1079              :                     error::incomplete);
    1080           15 :                 return;
    1081              :             }
    1082        10265 :             else if(ec.failed())
    1083              :             {
    1084              :                 // other error,
    1085              :                 //
    1086              :                 // VFALCO map this to a bad
    1087              :                 // request or bad response error?
    1088              :                 //
    1089          259 :                 state_ = state::reset; // unrecoverable
    1090          259 :                 return;
    1091              :             }
    1092              : 
    1093        10006 :             got_header_ = true;
    1094              : 
    1095              :             // reserve headers + table
    1096        10006 :             ws_.reserve_front(h_.size);
    1097        10006 :             ws_.reserve_back(h_.table_space());
    1098              : 
    1099              :             // no payload
    1100        10006 :             if(h_.md.payload == payload::none ||
    1101         9084 :                 head_response_)
    1102              :             {
    1103              :                 // octets of the next message
    1104          922 :                 auto overread = fb_.size() - h_.size;
    1105          922 :                 cb0_ = { ws_.data(), overread, overread };
    1106          922 :                 ws_.reserve_front(overread);
    1107          922 :                 state_ = state::complete_in_place;
    1108          922 :                 return;
    1109              :             }
    1110              : 
    1111         9084 :             state_ = state::header_done;
    1112         9084 :             break;
    1113              :         }
    1114              : 
    1115         9083 :         case state::header_done:
    1116              :         {
    1117              :             // metadata error
    1118         9083 :             if(h_.md.payload == payload::error)
    1119              :             {
    1120              :                 // VFALCO This needs looking at
    1121          360 :                 ec = BOOST_HTTP_PROTO_ERR(
    1122              :                     error::bad_payload);
    1123          180 :                 state_ = state::reset; // unrecoverable
    1124          180 :                 return;
    1125              :             }
    1126              : 
    1127              :             // overread currently includes any and all octets that
    1128              :             // extend beyond the current end of the header
    1129              :             // this can include associated body octets for the
    1130              :             // current message or octets of the next message in the
    1131              :             // stream, e.g. pipelining is being used
    1132         8903 :             auto const overread = fb_.size() - h_.size;
    1133         8903 :             BOOST_ASSERT(overread <= svc_.max_overread());
    1134              : 
    1135         8903 :             auto cap = fb_.capacity() + overread +
    1136         8903 :                 svc_.cfg.min_buffer;
    1137              : 
    1138              :             // reserve body buffers first, as the decoder
    1139              :             // must be installed after them.
    1140         8903 :             auto const p = ws_.reserve_front(cap);
    1141              : 
    1142         8903 :             switch(h_.md.content_encoding.coding)
    1143              :             {
    1144           36 :             case content_coding::deflate:
    1145           36 :                 if(!svc_.cfg.apply_deflate_decoder)
    1146            0 :                     goto no_filter;
    1147           72 :                 filter_ = &ws_.emplace<zlib_filter>(
    1148           36 :                     ctx_, ws_, svc_.cfg.zlib_window_bits);
    1149           36 :                 break;
    1150              : 
    1151           36 :             case content_coding::gzip:
    1152           36 :                 if(!svc_.cfg.apply_gzip_decoder)
    1153            0 :                     goto no_filter;
    1154           72 :                 filter_ = &ws_.emplace<zlib_filter>(
    1155           36 :                     ctx_, ws_, svc_.cfg.zlib_window_bits + 16);
    1156           36 :                 break;
    1157              : 
    1158            0 :             case content_coding::br:
    1159            0 :                 if(!svc_.cfg.apply_brotli_decoder)
    1160            0 :                     goto no_filter;
    1161            0 :                 filter_ = &ws_.emplace<brotli_filter>(
    1162            0 :                     ctx_, ws_);
    1163            0 :                 break;
    1164              : 
    1165            0 :             no_filter:
    1166         8831 :             default:
    1167         8831 :                 cap += svc_.max_codec;
    1168         8831 :                 ws_.reserve_front(svc_.max_codec);
    1169         8831 :                 break;
    1170              :             }
    1171              : 
    1172         8903 :             if(is_plain() || style_ == style::elastic)
    1173              :             {
    1174         4722 :                 cb0_ = { p, cap, overread };
    1175         4722 :                 cb1_ = {};
    1176              :             }
    1177              :             else
    1178              :             {
    1179              :                 // buffered payload
    1180         8362 :                 std::size_t n0 = (overread > svc_.cfg.min_buffer)
    1181         4181 :                     ? overread
    1182         4157 :                     : svc_.cfg.min_buffer;
    1183         4181 :                 std::size_t n1 = svc_.cfg.min_buffer;
    1184              : 
    1185         4181 :                 cb0_ = { p      , n0, overread };
    1186         4181 :                 cb1_ = { p + n0 , n1 };
    1187              :             }
    1188              : 
    1189         8903 :             if(h_.md.payload == payload::size)
    1190              :             {
    1191         4374 :                 if(!filter_ &&
    1192         4350 :                     body_limit_ < h_.md.payload_size)
    1193              :                 {
    1194            2 :                     ec = BOOST_HTTP_PROTO_ERR(
    1195              :                         error::body_too_large);
    1196            1 :                     state_ = state::reset;
    1197            1 :                     return;
    1198              :                 }
    1199         4373 :                 payload_remain_ = h_.md.payload_size;
    1200              :             }
    1201              : 
    1202         8902 :             state_ = state::body;
    1203              :             BOOST_FALLTHROUGH;
    1204              :         }
    1205              : 
    1206              :         case state::body:
    1207              :         {
    1208        50201 :         do_body:
    1209        50201 :             BOOST_ASSERT(state_ == state::body);
    1210        50201 :             BOOST_ASSERT(h_.md.payload != payload::none);
    1211        50201 :             BOOST_ASSERT(h_.md.payload != payload::error);
    1212              : 
    1213         8797 :             auto set_state_to_complete = [&]()
    1214              :             {
    1215         8797 :                 if(style_ == style::in_place)
    1216              :                 {
    1217         8337 :                     state_ = state::complete_in_place;
    1218         8337 :                     return;
    1219              :                 }
    1220          460 :                 state_ = state::complete;
    1221        50201 :             };
    1222              : 
    1223        50201 :             if(h_.md.payload == payload::chunked)
    1224              :             {
    1225              :                 for(;;)
    1226              :                 {
    1227       125495 :                     if(chunk_remain_ == 0
    1228       124342 :                         && !chunked_body_ended)
    1229              :                     {
    1230       120197 :                         auto cs = chained_sequence(cb0_.data());
    1231        19411 :                         auto check_ec = [&]()
    1232              :                         {
    1233        19411 :                             if(ec == condition::need_more_input && got_eof_)
    1234              :                             {
    1235            0 :                                 ec = BOOST_HTTP_PROTO_ERR(error::incomplete);
    1236            0 :                                 state_ = state::reset;
    1237              :                             }
    1238       139608 :                         };
    1239              : 
    1240       120197 :                         if(needs_chunk_close_)
    1241              :                         {
    1242       111557 :                             parse_eol(cs, ec);
    1243       111557 :                             if(ec)
    1244              :                             {
    1245           17 :                                 check_ec();
    1246        19411 :                                 return;
    1247              :                             }
    1248              :                         }
    1249         8640 :                         else if(trailer_headers_)
    1250              :                         {
    1251         4223 :                             skip_trailer_headers(cs, ec);
    1252         4223 :                             if(ec)
    1253              :                             {
    1254           78 :                                 check_ec();
    1255           78 :                                 return;
    1256              :                             }
    1257         4145 :                             cb0_.consume(cb0_.size() - cs.size());
    1258         4145 :                             chunked_body_ended = true;
    1259         8292 :                             continue;
    1260              :                         }
    1261              :                         
    1262       115957 :                         auto chunk_size = parse_hex(cs, ec);
    1263       115957 :                         if(ec)
    1264              :                         {
    1265        19284 :                             check_ec();
    1266        19284 :                             return;
    1267              :                         }
    1268              : 
    1269              :                         // skip chunk extensions
    1270        96673 :                         find_eol(cs, ec);
    1271        96673 :                         if(ec)
    1272              :                         {
    1273           32 :                             check_ec();
    1274           32 :                             return;
    1275              :                         }
    1276              : 
    1277        96641 :                         cb0_.consume(cb0_.size() - cs.size());
    1278        96641 :                         chunk_remain_ = chunk_size;
    1279              : 
    1280        96641 :                         needs_chunk_close_ = true;
    1281        96641 :                         if(chunk_remain_ == 0)
    1282              :                         {
    1283         4147 :                             needs_chunk_close_ = false;
    1284         4147 :                             trailer_headers_ = true;
    1285         4147 :                             continue;
    1286              :                         }
    1287              :                     }
    1288              : 
    1289        97792 :                     if(cb0_.size() == 0 && !chunked_body_ended)
    1290              :                     {
    1291          340 :                         if(got_eof_)
    1292              :                         {
    1293            2 :                             ec = BOOST_HTTP_PROTO_ERR(
    1294              :                                 error::incomplete);
    1295            1 :                             state_ = state::reset;
    1296            1 :                             return;
    1297              :                         }
    1298              : 
    1299          678 :                         ec = BOOST_HTTP_PROTO_ERR(
    1300              :                             error::need_data);
    1301          339 :                         return;
    1302              :                     }
    1303              : 
    1304        97452 :                     if(filter_)
    1305              :                     {
    1306        51070 :                         chunk_remain_ -= apply_filter(
    1307              :                             ec,
    1308              :                             clamp(chunk_remain_, cb0_.size()),
    1309        51070 :                             !chunked_body_ended);
    1310              : 
    1311        51070 :                         if(ec || chunked_body_ended)
    1312          564 :                             return;
    1313              :                     }
    1314              :                     else
    1315              :                     {
    1316              :                         const std::size_t chunk_avail =
    1317        46382 :                             clamp(chunk_remain_, cb0_.size());
    1318              :                         const auto chunk =
    1319        46382 :                             buffers::prefix(cb0_.data(), chunk_avail);
    1320              : 
    1321        46382 :                         if(body_limit_remain() < chunk_avail)
    1322              :                         {
    1323            0 :                             ec = BOOST_HTTP_PROTO_ERR(
    1324              :                                 error::body_too_large);
    1325            0 :                             state_ = state::reset;
    1326         4121 :                             return;
    1327              :                         }
    1328              : 
    1329        46382 :                         switch(style_)
    1330              :                         {
    1331        46382 :                         case style::in_place:
    1332              :                         {
    1333        46382 :                             auto copied = buffers::copy(
    1334        46382 :                                 cb1_.prepare(cb1_.capacity()),
    1335              :                                 chunk);
    1336        46382 :                             chunk_remain_ -= copied;
    1337        46382 :                             body_avail_   += copied;
    1338        46382 :                             body_total_   += copied;
    1339        46382 :                             cb0_.consume(copied);
    1340        46382 :                             cb1_.commit(copied);
    1341        46382 :                             if(cb1_.capacity() == 0
    1342        46382 :                                 && !chunked_body_ended)
    1343              :                             {
    1344            0 :                                 ec = BOOST_HTTP_PROTO_ERR(
    1345              :                                     error::in_place_overflow);
    1346            0 :                                 return;
    1347              :                             }
    1348        46382 :                             break;
    1349              :                         }
    1350            0 :                         case style::sink:
    1351              :                         {
    1352            0 :                             auto sink_rs = sink_->write(
    1353            0 :                                 chunk, !chunked_body_ended);
    1354            0 :                             chunk_remain_ -= sink_rs.bytes;
    1355            0 :                             body_total_   += sink_rs.bytes;
    1356            0 :                             cb0_.consume(sink_rs.bytes);
    1357            0 :                             if(sink_rs.ec.failed())
    1358              :                             {
    1359            0 :                                 body_avail_ += 
    1360            0 :                                     chunk_avail - sink_rs.bytes;
    1361            0 :                                 ec  = sink_rs.ec;
    1362            0 :                                 state_ = state::reset;
    1363            0 :                                 return;
    1364              :                             }
    1365            0 :                             break;
    1366              :                         }
    1367            0 :                         case style::elastic:
    1368              :                         {
    1369            0 :                             if(eb_->max_size() - eb_->size()
    1370            0 :                                 < chunk_avail)
    1371              :                             {
    1372            0 :                                 ec = BOOST_HTTP_PROTO_ERR(
    1373              :                                     error::buffer_overflow);
    1374            0 :                                 state_ = state::reset;
    1375            0 :                                 return;
    1376              :                             }
    1377            0 :                             buffers::copy(
    1378            0 :                                 eb_->prepare(chunk_avail),
    1379              :                                 chunk);
    1380            0 :                             chunk_remain_ -= chunk_avail;
    1381            0 :                             body_total_   += chunk_avail;
    1382            0 :                             cb0_.consume(chunk_avail);
    1383            0 :                             eb_->commit(chunk_avail);
    1384            0 :                             break;
    1385              :                         }
    1386              :                         }
    1387              : 
    1388        46382 :                         if(chunked_body_ended)
    1389              :                         {
    1390         4121 :                             set_state_to_complete();
    1391         4121 :                             return;
    1392              :                         }
    1393              :                     }
    1394       101059 :                 }
    1395              :             }
    1396              :             else
    1397              :             {
    1398              :                 // non-chunked payload
    1399              : 
    1400        77295 :                 const std::size_t payload_avail = [&]()
    1401              :                 {
    1402        25765 :                     auto ret = cb0_.size();
    1403        25765 :                     if(!filter_)
    1404        24121 :                         ret -= body_avail_;
    1405        25765 :                     if(h_.md.payload == payload::size)
    1406        24159 :                         return clamp(payload_remain_, ret);
    1407              :                     // payload::eof
    1408         1606 :                     return ret;
    1409        25765 :                 }();
    1410              : 
    1411        77295 :                 const bool is_complete = [&]()
    1412              :                 {
    1413        25765 :                     if(h_.md.payload == payload::size)
    1414        24159 :                         return payload_avail == payload_remain_;
    1415              :                     // payload::eof
    1416         1606 :                     return got_eof_;
    1417        25765 :                 }();
    1418              : 
    1419        25765 :                 if(filter_)
    1420              :                 {
    1421         3288 :                     payload_remain_ -= apply_filter(
    1422         1644 :                         ec, payload_avail, !is_complete);
    1423         1644 :                     if(ec || is_complete)
    1424         1128 :                         return;
    1425              :                 }
    1426              :                 else
    1427              :                 {
    1428              :                     // plain body
    1429              : 
    1430        24121 :                     if(h_.md.payload == payload::to_eof)
    1431              :                     {
    1432          764 :                         if(body_limit_remain() < payload_avail)
    1433              :                         {
    1434            2 :                             ec = BOOST_HTTP_PROTO_ERR(
    1435              :                                 error::body_too_large);
    1436            1 :                             state_ = state::reset;
    1437            1 :                             return;
    1438              :                         }
    1439              :                     }
    1440              : 
    1441        24120 :                     switch(style_)
    1442              :                     {
    1443        23363 :                     case style::in_place:
    1444              :                     {
    1445        23363 :                         payload_remain_ -= payload_avail;
    1446        23363 :                         body_avail_     += payload_avail;
    1447        23363 :                         body_total_     += payload_avail;
    1448        23363 :                         if(cb0_.capacity() == 0 && !is_complete)
    1449              :                         {
    1450            2 :                             ec = BOOST_HTTP_PROTO_ERR(
    1451              :                                 error::in_place_overflow);
    1452            1 :                             return;
    1453              :                         }
    1454        23362 :                         break;
    1455              :                     }
    1456          371 :                     case style::sink:
    1457              :                     {
    1458          371 :                         payload_remain_ -= payload_avail;
    1459          371 :                         body_total_     += payload_avail;
    1460          371 :                         auto sink_rs = sink_->write(
    1461          371 :                             buffers::prefix(cb0_.data(), payload_avail),
    1462          371 :                             !is_complete);
    1463          371 :                         cb0_.consume(sink_rs.bytes);
    1464          371 :                         if(sink_rs.ec.failed())
    1465              :                         {
    1466            0 :                             body_avail_ += 
    1467            0 :                                 payload_avail - sink_rs.bytes;
    1468            0 :                             ec  = sink_rs.ec;
    1469            0 :                             state_ = state::reset;
    1470            0 :                             return;
    1471              :                         }
    1472          371 :                         break;
    1473              :                     }
    1474          386 :                     case style::elastic:
    1475              :                     {
    1476              :                         // payload_remain_ and body_total_
    1477              :                         // are already updated in commit()
    1478              : 
    1479              :                         // cb0_ contains data
    1480          386 :                         if(payload_avail != 0)
    1481              :                         {
    1482          193 :                             if(eb_->max_size() - eb_->size()
    1483          193 :                                 < payload_avail)
    1484              :                             {
    1485            2 :                                 ec = BOOST_HTTP_PROTO_ERR(
    1486              :                                     error::buffer_overflow);
    1487            1 :                                 state_ = state::reset;
    1488            1 :                                 return;
    1489              :                             }
    1490              :                             // only happens when an elastic body
    1491              :                             // is attached in header_done state
    1492          192 :                             buffers::copy(
    1493          192 :                                 eb_->prepare(payload_avail),
    1494          192 :                                 cb0_.data());
    1495          192 :                             cb0_.consume(payload_avail);
    1496          192 :                             eb_->commit(payload_avail);
    1497          192 :                             payload_remain_ -= payload_avail;
    1498          192 :                             body_total_ += payload_avail;
    1499              :                         }
    1500          385 :                         break;
    1501              :                     }
    1502              :                     }
    1503              : 
    1504        24118 :                     if(is_complete)
    1505              :                     {
    1506         4676 :                         set_state_to_complete();
    1507         4676 :                         return;
    1508              :                     }
    1509              :                 }
    1510              : 
    1511        19958 :                 if(h_.md.payload == payload::size && got_eof_)
    1512              :                 {
    1513            2 :                     ec = BOOST_HTTP_PROTO_ERR(
    1514              :                         error::incomplete);
    1515            1 :                     state_ = state::reset;
    1516            1 :                     return;
    1517              :                 }
    1518              : 
    1519        39914 :                 ec = BOOST_HTTP_PROTO_ERR(
    1520              :                     error::need_data);
    1521        19957 :                 return;
    1522              :             }
    1523              : 
    1524              :             break;
    1525              :         }
    1526              : 
    1527         2333 :         case state::set_body:
    1528              :         case state::complete_in_place:
    1529              :         {
    1530         2333 :             auto& body_buf = is_plain() ? cb0_ : cb1_;
    1531              : 
    1532         2333 :             switch(style_)
    1533              :             {
    1534         2216 :             case style::in_place:
    1535         2216 :                 return; // no-op
    1536           58 :             case style::sink:
    1537              :             {
    1538           58 :                 auto rs = sink_->write(
    1539           58 :                     buffers::prefix(body_buf.data(), body_avail_),
    1540           58 :                     state_ == state::set_body);
    1541           58 :                 body_buf.consume(rs.bytes);
    1542           58 :                 body_avail_ -= rs.bytes;
    1543           58 :                 if(rs.ec.failed())
    1544              :                 {
    1545            0 :                     ec  = rs.ec;
    1546            0 :                     state_ = state::reset;
    1547            0 :                     return;
    1548              :                 }
    1549           58 :                 break;
    1550              :             }
    1551           59 :             case style::elastic:
    1552              :             {
    1553           59 :                 if(eb_->max_size() - eb_->size()
    1554           59 :                     < body_avail_)
    1555              :                 {
    1556            0 :                     ec = BOOST_HTTP_PROTO_ERR(
    1557              :                         error::buffer_overflow);
    1558            0 :                     return;
    1559              :                 }
    1560           59 :                 buffers::copy(
    1561           59 :                     eb_->prepare(body_avail_),
    1562           59 :                     body_buf.data());
    1563           59 :                 body_buf.consume(body_avail_);
    1564           59 :                 eb_->commit(body_avail_);
    1565           59 :                 body_avail_ = 0;
    1566              :                 // TODO: expand cb0_ when possible?
    1567           59 :                 break;
    1568              :             }
    1569              :             }
    1570              : 
    1571          117 :             if(state_ == state::set_body)
    1572              :             {
    1573            0 :                 state_ = state::body;
    1574            0 :                 goto do_body;
    1575              :             }
    1576              : 
    1577          117 :             state_ = state::complete;
    1578          117 :             break;
    1579              :         }
    1580              : 
    1581          460 :         case state::complete:
    1582          460 :             break;
    1583              :         }
    1584              :     }
    1585              : 
    1586              :     auto
    1587        41250 :     pull_body() ->
    1588              :         const_buffers_type
    1589              :     {
    1590        41250 :         switch(state_)
    1591              :         {
    1592            0 :         case state::header_done:
    1593            0 :             return {};
    1594        41250 :         case state::body:
    1595              :         case state::complete_in_place:
    1596        41250 :             cbp_ = buffers::prefix(
    1597        41250 :                 (is_plain() ? cb0_ : cb1_).data(),
    1598              :                 body_avail_);
    1599        41250 :             return detail::make_span(cbp_);
    1600            0 :         default:
    1601            0 :             detail::throw_logic_error();
    1602              :         }
    1603              :     }
    1604              : 
    1605              :     void
    1606        39606 :     consume_body(std::size_t n)
    1607              :     {
    1608        39606 :         switch(state_)
    1609              :         {
    1610            0 :         case state::header_done:
    1611            0 :             return;
    1612        39606 :         case state::body:
    1613              :         case state::complete_in_place:
    1614        39606 :             n = clamp(n, body_avail_);
    1615        39606 :             (is_plain() ? cb0_ : cb1_).consume(n);
    1616        39606 :             body_avail_ -= n;
    1617        39606 :             return;
    1618            0 :         default:
    1619            0 :             detail::throw_logic_error();
    1620              :         }
    1621              :     }
    1622              : 
    1623              :     core::string_view
    1624          700 :     body() const
    1625              :     {
    1626              :         // Precondition violation
    1627          700 :         if(state_ != state::complete_in_place)
    1628            0 :             detail::throw_logic_error();
    1629              : 
    1630              :         // Precondition violation
    1631          700 :         if(body_avail_ != body_total_)
    1632            0 :             detail::throw_logic_error();
    1633              : 
    1634          700 :         auto cbp = (is_plain() ? cb0_ : cb1_).data();
    1635          700 :         BOOST_ASSERT(cbp[1].size() == 0);
    1636          700 :         BOOST_ASSERT(cbp[0].size() == body_avail_);
    1637          700 :         return core::string_view(
    1638          700 :             static_cast<char const*>(cbp[0].data()),
    1639         1400 :             body_avail_);
    1640              :     }
    1641              : 
    1642              :     void
    1643           77 :     set_body_limit(std::uint64_t n)
    1644              :     {
    1645           77 :         switch(state_)
    1646              :         {
    1647           73 :         case state::header:
    1648              :         case state::header_done:
    1649           73 :             body_limit_ = n;
    1650           73 :             break;
    1651            2 :         case state::complete_in_place:
    1652              :             // only allowed for empty bodies
    1653            2 :             if(body_total_ == 0)
    1654            1 :                 break;
    1655              :             BOOST_FALLTHROUGH;
    1656              :         default:
    1657              :             // set body_limit before parsing the body
    1658            3 :             detail::throw_logic_error();
    1659              :         }
    1660           74 :     }
    1661              : 
    1662              :     void
    1663          383 :     set_body(
    1664              :         buffers::any_dynamic_buffer& eb) noexcept
    1665              :     {
    1666          383 :         eb_ = &eb;
    1667          383 :         style_ = style::elastic;
    1668          383 :         nprepare_ = 0; // invalidate
    1669          383 :         if(state_ == state::body)
    1670            0 :             state_ = state::set_body;
    1671          383 :     }
    1672              : 
    1673              :     void
    1674          372 :     set_body(sink& s) noexcept
    1675              :     {
    1676          372 :         sink_ = &s;
    1677          372 :         style_ = style::sink;
    1678          372 :         nprepare_ = 0; // invalidate
    1679          372 :         if(state_ == state::body)
    1680            0 :             state_ = state::set_body;
    1681          372 :     }
    1682              : 
    1683              :     detail::workspace&
    1684          755 :     ws() noexcept
    1685              :     {
    1686          755 :         return ws_;
    1687              :     }
    1688              : 
    1689              : private:
    1690              :     bool
    1691       182054 :     is_plain() const noexcept
    1692              :     {
    1693       354400 :         return ! filter_ &&
    1694       354400 :             h_.md.payload != payload::chunked;
    1695              :     }
    1696              : 
    1697              :     std::uint64_t
    1698       157918 :     body_limit_remain() const noexcept
    1699              :     {
    1700       157918 :         return body_limit_ - body_total_;
    1701              :     }
    1702              : 
    1703              :     std::size_t
    1704        52714 :     apply_filter(
    1705              :         system::error_code& ec,
    1706              :         std::size_t payload_avail,
    1707              :         bool more)
    1708              :     {
    1709        52714 :         std::size_t p0 = payload_avail;
    1710              :         for(;;)
    1711              :         {
    1712       107150 :             if(payload_avail == 0 && more)
    1713        51094 :                 break;
    1714              : 
    1715            0 :             auto f_rs = [&](){
    1716        56176 :                 BOOST_ASSERT(filter_ != nullptr);
    1717        56176 :                 if(style_ == style::elastic)
    1718              :                 {
    1719        18143 :                     std::size_t n = clamp(body_limit_remain());
    1720        18143 :                     n = clamp(n, svc_.cfg.min_buffer);
    1721        18143 :                     n = clamp(n, eb_->max_size() - eb_->size());
    1722              : 
    1723              :                     // fill capacity first to avoid
    1724              :                     // an allocation
    1725              :                     std::size_t avail = 
    1726        18143 :                         eb_->capacity() - eb_->size();
    1727        18143 :                     if(avail != 0)
    1728        18143 :                         n = clamp(n, avail);
    1729              : 
    1730        36286 :                     return filter_->process(
    1731        18143 :                         eb_->prepare(n),
    1732        18143 :                         buffers::prefix(cb0_.data(), payload_avail),
    1733        36286 :                         more);
    1734              :                 }
    1735              :                 else // in-place and sink 
    1736              :                 {
    1737        38033 :                     std::size_t n = clamp(body_limit_remain());
    1738        38033 :                     n = clamp(n, cb1_.capacity());
    1739              : 
    1740        76066 :                     return filter_->process(
    1741        38033 :                         detail::make_span(cb1_.prepare(n)),
    1742        38033 :                         buffers::prefix(cb0_.data(), payload_avail),
    1743        76066 :                         more);
    1744              :                 }
    1745        56176 :             }();
    1746              : 
    1747        56176 :             cb0_.consume(f_rs.in_bytes);
    1748        56176 :             payload_avail -= f_rs.in_bytes;
    1749        56176 :             body_total_   += f_rs.out_bytes;
    1750              : 
    1751        56176 :             switch(style_)
    1752              :             {
    1753        20026 :             case style::in_place:
    1754              :             {
    1755        20026 :                 cb1_.commit(f_rs.out_bytes);
    1756        20026 :                 body_avail_ += f_rs.out_bytes;
    1757        20026 :                 if(cb1_.capacity() == 0 &&
    1758        20026 :                     !f_rs.finished && f_rs.in_bytes == 0)
    1759              :                 {
    1760         3240 :                     ec = BOOST_HTTP_PROTO_ERR(
    1761              :                         error::in_place_overflow);
    1762         1620 :                     goto done;
    1763              :                 }
    1764        18406 :                 break;
    1765              :             }
    1766        18007 :             case style::sink:
    1767              :             {
    1768        18007 :                 cb1_.commit(f_rs.out_bytes);
    1769        18007 :                 auto sink_rs = sink_->write(
    1770        18007 :                     cb1_.data(), !f_rs.finished || more);
    1771        18007 :                 cb1_.consume(sink_rs.bytes);
    1772        18007 :                 if(sink_rs.ec.failed())
    1773              :                 {
    1774            0 :                     ec  = sink_rs.ec;
    1775            0 :                     state_ = state::reset;
    1776            0 :                     goto done;
    1777              :                 }
    1778        18007 :                 break;
    1779              :             }
    1780        18143 :             case style::elastic:
    1781              :             {
    1782        18143 :                 eb_->commit(f_rs.out_bytes);
    1783        18143 :                 if(eb_->max_size() - eb_->size() == 0 &&
    1784        18143 :                     !f_rs.finished && f_rs.in_bytes == 0)
    1785              :                 {
    1786            0 :                     ec = BOOST_HTTP_PROTO_ERR(
    1787              :                         error::buffer_overflow);
    1788            0 :                     state_ = state::reset;
    1789            0 :                     goto done;
    1790              :                 }
    1791        18143 :                 break;
    1792              :             }
    1793              :             }
    1794              : 
    1795        54556 :             if(f_rs.ec.failed())
    1796              :             {
    1797            0 :                 ec = f_rs.ec;
    1798            0 :                 state_ = state::reset;
    1799            0 :                 break;
    1800              :             }
    1801              : 
    1802        54556 :             if(body_limit_remain() == 0 &&
    1803        54556 :                 !f_rs.finished && f_rs.in_bytes == 0)
    1804              :             {
    1805            0 :                 ec = BOOST_HTTP_PROTO_ERR(
    1806              :                     error::body_too_large);
    1807            0 :                 state_ = state::reset;
    1808            0 :                 break;
    1809              :             }
    1810              : 
    1811        54556 :             if(f_rs.finished)
    1812              :             {
    1813          120 :                 if(!more)
    1814              :                 {
    1815           72 :                     state_ = (style_ == style::in_place)
    1816           72 :                         ? state::complete_in_place
    1817              :                         : state::complete;
    1818              :                 }
    1819          120 :                 break;
    1820              :             }
    1821        54436 :         }
    1822              : 
    1823        52714 :     done:
    1824        52714 :         return p0 - payload_avail;
    1825              :     }
    1826              : };
    1827              : 
    1828              : //------------------------------------------------
    1829              : //
    1830              : // Special Members
    1831              : //
    1832              : //------------------------------------------------
    1833              : 
    1834         1070 : parser::
    1835              : ~parser()
    1836              : {
    1837         1070 :     delete impl_;
    1838         1070 : }
    1839              : 
    1840           11 : parser::
    1841           11 : parser() noexcept
    1842           11 :     : impl_(nullptr)
    1843              : {
    1844           11 : }
    1845              : 
    1846            3 : parser::
    1847            3 : parser(parser&& other) noexcept
    1848            3 :     : impl_(other.impl_)
    1849              : {
    1850            3 :     other.impl_ = nullptr;
    1851            3 : }
    1852              : 
    1853         1056 : parser::
    1854              : parser(
    1855              :     capy::polystore& ctx,
    1856         1056 :     detail::kind k)
    1857         1056 :     : impl_(new impl(ctx, k))
    1858              : {
    1859              :     // TODO: use a single allocation for
    1860              :     // impl and workspace buffer.
    1861         1056 : }
    1862              : 
    1863              : void
    1864            4 : parser::
    1865              : assign(parser&& other) noexcept
    1866              : {
    1867            4 :     if(this == &other)
    1868            0 :         return;
    1869            4 :     delete impl_;
    1870            4 :     impl_ = other.impl_;
    1871            4 :     other.impl_ = nullptr;
    1872              : }
    1873              : 
    1874              : //--------------------------------------------
    1875              : //
    1876              : // Observers
    1877              : //
    1878              : //--------------------------------------------
    1879              : 
    1880              : bool
    1881        11938 : parser::got_header() const noexcept
    1882              : {
    1883        11938 :     BOOST_ASSERT(impl_);
    1884        11938 :     return impl_->got_header();
    1885              : }
    1886              : 
    1887              : bool
    1888        51344 : parser::is_complete() const noexcept
    1889              : {
    1890        51344 :     BOOST_ASSERT(impl_);
    1891        51344 :     return impl_->is_complete();
    1892              : }
    1893              : 
    1894              : //------------------------------------------------
    1895              : //
    1896              : // Modifiers
    1897              : //
    1898              : //------------------------------------------------
    1899              : 
    1900              : void
    1901         2480 : parser::
    1902              : reset() noexcept
    1903              : {
    1904         2480 :     BOOST_ASSERT(impl_);
    1905         2480 :     impl_->reset();
    1906         2480 : }
    1907              : 
    1908              : void
    1909        10307 : parser::start()
    1910              : {
    1911        10307 :     BOOST_ASSERT(impl_);
    1912        10307 :     impl_->start(false);
    1913        10302 : }
    1914              : 
    1915              : auto
    1916        51003 : parser::
    1917              : prepare() ->
    1918              :     mutable_buffers_type
    1919              : {
    1920        51003 :     BOOST_ASSERT(impl_);
    1921        51003 :     return impl_->prepare();
    1922              : }
    1923              : 
    1924              : void
    1925        51000 : parser::
    1926              : commit(
    1927              :     std::size_t n)
    1928              : {
    1929        51000 :     BOOST_ASSERT(impl_);
    1930        51000 :     impl_->commit(n);
    1931        50993 : }
    1932              : 
    1933              : void
    1934          401 : parser::
    1935              : commit_eof()
    1936              : {
    1937          401 :     BOOST_ASSERT(impl_);
    1938          401 :     impl_->commit_eof();
    1939          398 : }
    1940              : 
    1941              : void
    1942        69827 : parser::
    1943              : parse(
    1944              :     system::error_code& ec)
    1945              : {
    1946        69827 :     BOOST_ASSERT(impl_);
    1947        69827 :     impl_->parse(ec);
    1948        69825 : }
    1949              : 
    1950              : auto
    1951        41250 : parser::
    1952              : pull_body() ->
    1953              :     const_buffers_type
    1954              : {
    1955        41250 :     BOOST_ASSERT(impl_);
    1956        41250 :     return impl_->pull_body();
    1957              : }
    1958              : 
    1959              : void
    1960        39606 : parser::
    1961              : consume_body(std::size_t n)
    1962              : {
    1963        39606 :     BOOST_ASSERT(impl_);
    1964        39606 :     impl_->consume_body(n);
    1965        39606 : }
    1966              : 
    1967              : core::string_view
    1968          700 : parser::
    1969              : body() const
    1970              : {
    1971          700 :     BOOST_ASSERT(impl_);
    1972          700 :     return impl_->body();
    1973              : }
    1974              : 
    1975              : core::string_view
    1976            0 : parser::
    1977              : release_buffered_data() noexcept
    1978              : {
    1979              :     // TODO
    1980            0 :     return {};
    1981              : }
    1982              : 
    1983              : void
    1984           77 : parser::
    1985              : set_body_limit(std::uint64_t n)
    1986              : {
    1987           77 :     BOOST_ASSERT(impl_);
    1988           77 :     impl_->set_body_limit(n);
    1989           74 : }
    1990              : 
    1991              : //------------------------------------------------
    1992              : //
    1993              : // Implementation
    1994              : //
    1995              : //------------------------------------------------
    1996              : 
    1997              : void
    1998            0 : parser::
    1999              : start_impl(bool head_response)
    2000              : {
    2001            0 :     BOOST_ASSERT(impl_);
    2002            0 :     impl_->start(head_response);
    2003            0 : }
    2004              : 
    2005              : detail::header const&
    2006          316 : parser::
    2007              : safe_get_header() const
    2008              : {
    2009          316 :     BOOST_ASSERT(impl_);
    2010          316 :     return impl_->safe_get_header();
    2011              : }
    2012              : 
    2013              : detail::workspace&
    2014          755 : parser::
    2015              : ws() noexcept
    2016              : {
    2017          755 :     BOOST_ASSERT(impl_);
    2018          755 :     return impl_->ws();
    2019              : }
    2020              : 
    2021              : bool
    2022          755 : parser::
    2023              : is_body_set() const noexcept
    2024              : {
    2025          755 :     BOOST_ASSERT(impl_);
    2026          755 :     return impl_->is_body_set();
    2027              : }
    2028              : 
    2029              : void
    2030          383 : parser::
    2031              : set_body_impl(
    2032              :     buffers::any_dynamic_buffer& eb) noexcept
    2033              : {
    2034          383 :     BOOST_ASSERT(impl_);
    2035          383 :     impl_->set_body(eb);
    2036          383 : }
    2037              : 
    2038              : void
    2039          372 : parser::
    2040              : set_body_impl(sink& s) noexcept
    2041              : {
    2042          372 :     BOOST_ASSERT(impl_);
    2043          372 :     impl_->set_body(s);
    2044          372 : }
    2045              : 
    2046              : } // http_proto
    2047              : } // boost
        

Generated by: LCOV version 2.1