33#include " hpack/basic_types.hpp"
44#include " hpack/dynamic_table.hpp"
55
6+ #include < span>
67#include < utility>
78
89namespace hpack {
910
11+ // helper for `decode_string`. Temporal storage for decoded strings
12+ //
13+ // may store string_view (AND NOT OWN IT!), may store huffman decoded string
14+ // and in this case memory will be allocated and owned
15+ // tries to reuse memory when new huffman string setted and memory already allocated
1016struct decoded_string {
1117 private:
1218 const char * data = nullptr ;
@@ -15,10 +21,6 @@ struct decoded_string {
1521 // default -1 for removing ambiguity between 'not allocated' and 'allocated 1 byte' (log2(1) == 0)
1622 int8_t allocated_sz_log2 = -1 ;
1723
18- friend void decode_string (In&, In, decoded_string&);
19-
20- void set_huffman (const char * ptr, size_type len);
21-
2224 public:
2325 decoded_string () = default ;
2426
@@ -43,12 +45,18 @@ struct decoded_string {
4345 return *this ;
4446 }
4547
48+ void set_huffman (const char * ptr, size_type len);
49+ // Note: *this will not own `ptr` memory, only contain a view
50+ void set_not_huffman (const char * ptr, size_type len) {
51+ reset ();
52+ data = ptr;
53+ sz = len;
54+ }
55+
4656 // not huffman encoded string
4757 decoded_string& operator =(std::string_view str) noexcept {
4858 assert (std::in_range<size_type>(str.size ()));
49- reset ();
50- data = str.data ();
51- sz = str.size ();
59+ set_not_huffman (str.data (), str.size ());
5260 return *this ;
5361 }
5462
@@ -123,7 +131,6 @@ struct header_view {
123131 }
124132};
125133
126- // precondition: in != e
127134void decode_string (In& in, In e, decoded_string& out);
128135
129136struct decoder {
@@ -137,17 +144,96 @@ struct decoder {
137144
138145 decoder (decoder&&) = default ;
139146 decoder& operator =(decoder&&) noexcept = default ;
147+
140148 /*
141149 Note: this function ignores special 'cookie' header case
142150 https://www.rfc-editor.org/rfc/rfc7540#section-8.1.2.5
143151 and protocol error if decoded header name is not lowercase
144152 */
145- // precondition: in != e
146153 void decode_header (In& in, In e, header_view& out);
147154
148155 // returns status code
149156 // its always first header of response, so 'in' must point to first byte of headers block
150157 int decode_response_status (In& in, In e);
151158};
152159
160+ // eats parts of headers fragment, allowing to parse CONTINUATIONS in HTTP/2 part by part
161+ struct stream_decoder {
162+ private:
163+ decoder& dec;
164+ std::vector<byte_t > incomplete;
165+
166+ // returns where first unparsed byte starts
167+ template <typename V>
168+ In do_feed (std::span<byte_t > chunk, bool last_chunk, V&& visitor, size_t & approx) {
169+ In in = chunk.data ();
170+ In e = in + chunk.size ();
171+ assert (in != e);
172+ In in_just_before_fail;
173+ approx = 0 ;
174+ try {
175+ header_view header;
176+ while (in != e) {
177+ in_just_before_fail = in;
178+
179+ dec.decode_header (in, e, header);
180+
181+ if (header) [[likely]] // dynamic size update decoded without error
182+ visitor (header.name .str (), header.value .str ());
183+ }
184+ // successfully parsed all headers
185+ return e;
186+ } catch (hpack::incomplete_data_error& e) {
187+ approx = e.required_bytes ;
188+ if (last_chunk)
189+ throw ;
190+ return in_just_before_fail;
191+ }
192+ }
193+
194+ public:
195+ stream_decoder (decoder& d) noexcept : dec(d) {
196+ }
197+
198+ stream_decoder (stream_decoder&&) = delete ;
199+ void operator =(stream_decoder&&) = delete ;
200+
201+ // `visitor` should accept two string_views, name and value
202+ // optimized for case when each `chunk` >> 1 header
203+ // returns approx count of bytes required for receiving next part of header
204+ // or 0 if there are no unhandled data in chunk
205+ // e.g. may be used to detect too big string before receiving it
206+ template <typename V>
207+ size_t feed (std::span<byte_t > chunk, bool last_chunk, V&& visitor) {
208+ if (chunk.empty ()) [[unlikely]]
209+ return 0 ;
210+ size_t approx;
211+ if (!incomplete.empty ()) {
212+ incomplete.insert (incomplete.end (), chunk.begin (), chunk.end ());
213+ In i = do_feed (incomplete, last_chunk, std::forward<V>(visitor), approx);
214+ In e = incomplete.data () + incomplete.size ();
215+ auto sz = e - i;
216+ // avoid UB on .assign (iterators into vector itself)
217+ memmove (incomplete.data (), i, sz);
218+ incomplete.resize (sz);
219+ } else {
220+ In i = do_feed (chunk, last_chunk, std::forward<V>(visitor), approx);
221+ incomplete.assign (i, In (chunk.data ()) + chunk.size ());
222+ }
223+ return approx;
224+ }
225+
226+ // returns such value, that `pending_data_size` + `feed` result == almost exact value of bytes which will be
227+ // stored until next part of header (not header itself!) will be parsed.
228+ // Note, there are only 2 parts of header - name and value
229+ [[nodiscard]] size_t pending_data_size () const noexcept {
230+ return incomplete.size ();
231+ }
232+
233+ // makes possible start from beginning, forgetting previous `feed` calls
234+ void clear () noexcept {
235+ incomplete.clear ();
236+ }
237+ };
238+
153239} // namespace hpack
0 commit comments