/src/h2o/lib/http3/qpack.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2018 Fastly, Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <stddef.h> |
23 | | #include <stdint.h> |
24 | | #include <stdio.h> |
25 | | #include <stdlib.h> |
26 | | #include "picotls.h" |
27 | | #include "h2o.h" |
28 | | #include "h2o/hpack.h" |
29 | | #include "h2o/qpack.h" |
30 | | #include "h2o/http3_common.h" |
31 | | |
32 | 0 | #define HEADER_ENTRY_SIZE_OFFSET 32 |
33 | | |
34 | | /** |
35 | | * a mem-shared object that contains the name and value of a header field |
36 | | */ |
37 | | struct st_h2o_qpack_header_t { |
38 | | h2o_iovec_t *name; |
39 | | size_t value_len; |
40 | | h2o_iovec_t _name_buf; |
41 | | unsigned soft_errors; |
42 | | char value[1]; |
43 | | }; |
44 | | |
45 | | struct st_h2o_qpack_header_table_t { |
46 | | /** |
47 | | * pointers to the buffer structure; [buf_start, buf_end) is the memory allocated, [first, last) are the active entries |
48 | | */ |
49 | | struct st_h2o_qpack_header_t **buf_start, **first, **last, **buf_end; |
50 | | /** |
51 | | * absolute index of `first` |
52 | | */ |
53 | | int64_t base_offset; |
54 | | /** |
55 | | * current and maximum size |
56 | | */ |
57 | | size_t num_bytes, max_size; |
58 | | }; |
59 | | |
60 | | struct st_h2o_qpack_blocked_streams_t { |
61 | | int64_t stream_id; |
62 | | int64_t largest_ref; |
63 | | union { |
64 | | struct { |
65 | | uint8_t is_blocking; |
66 | | } encoder_flags; |
67 | | }; |
68 | | }; |
69 | | |
70 | | struct st_h2o_qpack_decoder_t { |
71 | | /** |
72 | | * |
73 | | */ |
74 | | struct st_h2o_qpack_header_table_t table; |
75 | | /** |
76 | | * maximum header table size declared by itself. Current max set by peer is available in `table.max_size`. |
77 | | */ |
78 | | uint32_t header_table_size; |
79 | | /** |
80 | | * |
81 | | */ |
82 | | uint32_t max_entries; |
83 | | /** |
84 | | * number of updates since last sync |
85 | | */ |
86 | | uint32_t insert_count; |
87 | | /** |
88 | | * |
89 | | */ |
90 | | uint64_t total_inserts; |
91 | | /** |
92 | | * |
93 | | */ |
94 | | uint16_t max_blocked; |
95 | | struct { |
96 | | /** |
97 | | * contains list of blocked streams (sorted in the ascending order of largest_ref) |
98 | | */ |
99 | | H2O_VECTOR(struct st_h2o_qpack_blocked_streams_t) list; |
100 | | /** |
101 | | * number of blocked streams that are unblocked. They are evicted parse_request / response is being called. |
102 | | */ |
103 | | size_t num_unblocked; |
104 | | } blocked_streams; |
105 | | }; |
106 | | |
107 | | struct st_h2o_qpack_encoder_t { |
108 | | /** |
109 | | * the header table |
110 | | */ |
111 | | struct st_h2o_qpack_header_table_t table; |
112 | | /** |
113 | | * maximum id of the insertion being acked (inclusive) |
114 | | */ |
115 | | int64_t largest_known_received; |
116 | | /** |
117 | | * SETTINGS_QPACK_BLOCKED_STREAMS |
118 | | */ |
119 | | uint16_t max_blocked; |
120 | | /** |
121 | | * number of potentially blocked HEADERS (not streams, sorry!) We count header blocks rather than streams because it is easier. |
122 | | * Hopefully it would work well. |
123 | | */ |
124 | | uint16_t num_blocked; |
125 | | /** |
126 | | * list of unacked streams |
127 | | */ |
128 | | H2O_VECTOR(struct st_h2o_qpack_blocked_streams_t) inflight; |
129 | | }; |
130 | | |
131 | | struct st_h2o_qpack_flatten_context_t { |
132 | | h2o_qpack_encoder_t *qpack; |
133 | | h2o_mem_pool_t *pool; |
134 | | int64_t stream_id; |
135 | | h2o_byte_vector_t *encoder_buf; |
136 | | h2o_byte_vector_t headers_buf; |
137 | | int64_t base_index; |
138 | | int64_t largest_ref; |
139 | | }; |
140 | | |
141 | | const char *h2o_qpack_err_header_name_too_long = "header name too long"; |
142 | | const char *h2o_qpack_err_header_value_too_long = "header value too long"; |
143 | | const char *h2o_qpack_err_header_exceeds_table_size = "header exceeds table size"; |
144 | | const char *h2o_qpack_err_invalid_max_size = "invalid max size"; |
145 | | const char *h2o_qpack_err_invalid_static_reference = "invalid static reference"; |
146 | | const char *h2o_qpack_err_invalid_dynamic_reference = "invalid dynamic reference"; |
147 | | const char *h2o_qpack_err_invalid_duplicate = "invalid duplicate"; |
148 | | const char *h2o_qpack_err_invalid_pseudo_header = "invalid pseudo header"; |
149 | | |
150 | | static void header_table_init(struct st_h2o_qpack_header_table_t *table, size_t max_size) |
151 | 6.55k | { |
152 | 6.55k | *table = (struct st_h2o_qpack_header_table_t){NULL, NULL, NULL, NULL, 1, 0, max_size}; |
153 | 6.55k | } |
154 | | |
155 | | static void header_table_dispose(struct st_h2o_qpack_header_table_t *table) |
156 | 6.55k | { |
157 | 6.55k | while (table->first != table->last) |
158 | 0 | h2o_mem_release_shared(*table->first++); |
159 | 6.55k | free(table->buf_start); |
160 | 6.55k | } |
161 | | |
162 | | static void header_table_evict(struct st_h2o_qpack_header_table_t *table, size_t delta) |
163 | 0 | { |
164 | 0 | while (table->first != table->last) { |
165 | 0 | if (table->num_bytes + delta <= table->max_size) |
166 | 0 | return; |
167 | 0 | table->num_bytes -= (*table->first)->name->len + (*table->first)->value_len + HEADER_ENTRY_SIZE_OFFSET; |
168 | 0 | h2o_mem_release_shared(*table->first); |
169 | 0 | *table->first++ = NULL; |
170 | 0 | ++table->base_offset; |
171 | 0 | } |
172 | 0 | assert(table->num_bytes == 0); |
173 | 0 | } |
174 | | |
175 | | static void header_table_insert(struct st_h2o_qpack_header_table_t *table, struct st_h2o_qpack_header_t *added) |
176 | 0 | { |
177 | 0 | header_table_evict(table, added->name->len + added->value_len + HEADER_ENTRY_SIZE_OFFSET); |
178 | |
|
179 | 0 | if (table->last == table->buf_end) { |
180 | 0 | size_t count = table->last - table->first, new_capacity = count <= 2 ? 4 : count * 2; |
181 | 0 | if (new_capacity > table->buf_end - table->buf_start) { |
182 | 0 | struct st_h2o_qpack_header_t **newbuf = h2o_mem_alloc(sizeof(*newbuf) * new_capacity); |
183 | 0 | memcpy(newbuf, table->first, sizeof(*newbuf) * count); |
184 | 0 | free(table->buf_start); |
185 | 0 | table->buf_start = newbuf; |
186 | 0 | table->first = newbuf; |
187 | 0 | table->last = newbuf + count; |
188 | 0 | table->buf_end = newbuf + new_capacity; |
189 | 0 | } else { |
190 | 0 | assert(table->buf_start != table->first); |
191 | 0 | memmove(table->buf_start, table->first, sizeof(*table->buf_start) * count); |
192 | 0 | table->first = table->buf_start; |
193 | 0 | table->last = table->buf_start + count; |
194 | 0 | } |
195 | 0 | memset(table->last, 0, sizeof(*table->last) * (table->buf_end - table->last)); |
196 | 0 | } |
197 | 0 | *table->last++ = added; |
198 | 0 | table->num_bytes += added->name->len + added->value_len + HEADER_ENTRY_SIZE_OFFSET; |
199 | 0 | } |
200 | | |
201 | | static const h2o_qpack_static_table_entry_t *resolve_static_abs(int64_t index, const char **err_desc) |
202 | 0 | { |
203 | 0 | if (index >= sizeof(h2o_qpack_static_table) / sizeof(h2o_qpack_static_table[0])) { |
204 | 0 | *err_desc = h2o_qpack_err_invalid_static_reference; |
205 | 0 | return NULL; |
206 | 0 | } |
207 | 0 | return h2o_qpack_static_table + index; |
208 | 0 | } |
209 | | |
210 | | static struct st_h2o_qpack_header_t *resolve_dynamic_abs(struct st_h2o_qpack_header_table_t *table, int64_t index, |
211 | | const char **err_desc) |
212 | 647 | { |
213 | 647 | if (index < table->base_offset) |
214 | 0 | goto Invalid; |
215 | 647 | index -= table->base_offset; |
216 | 647 | if (index >= table->last - table->first) |
217 | 647 | goto Invalid; |
218 | 0 | return table->first[index]; |
219 | 647 | Invalid: |
220 | 647 | *err_desc = h2o_qpack_err_invalid_dynamic_reference; |
221 | 647 | return NULL; |
222 | 647 | } |
223 | | |
224 | | static int decode_int(int64_t *value, const uint8_t **src, const uint8_t *src_end, unsigned prefix_bits) |
225 | 751k | { |
226 | 751k | if ((*value = h2o_hpack_decode_int(src, src_end, prefix_bits)) < 0) |
227 | 92 | return *value == H2O_HTTP2_ERROR_INCOMPLETE ? H2O_HTTP3_ERROR_INCOMPLETE : H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
228 | 751k | return 0; |
229 | 751k | } |
230 | | |
231 | | static size_t decode_value(char *outbuf, unsigned *soft_errors, int is_huff, const uint8_t *src, size_t srclen, |
232 | | const char **err_desc) |
233 | 63.8k | { |
234 | 63.8k | size_t outlen; |
235 | | |
236 | 63.8k | if (is_huff) { |
237 | 14.9k | if ((outlen = h2o_hpack_decode_huffman(outbuf, soft_errors, src, srclen, 0, err_desc)) == SIZE_MAX) |
238 | 40 | return SIZE_MAX; |
239 | 48.9k | } else { |
240 | 48.9k | h2o_hpack_validate_header_value(soft_errors, (void *)src, srclen); |
241 | 48.9k | memcpy(outbuf, src, srclen); |
242 | 48.9k | outlen = srclen; |
243 | 48.9k | } |
244 | 63.7k | outbuf[outlen] = '\0'; |
245 | | |
246 | 63.7k | return outlen; |
247 | 63.8k | } |
248 | | |
249 | | h2o_qpack_decoder_t *h2o_qpack_create_decoder(uint32_t header_table_size, uint16_t max_blocked) |
250 | 6.55k | { |
251 | 6.55k | h2o_qpack_decoder_t *qpack = h2o_mem_alloc(sizeof(*qpack)); |
252 | | |
253 | 6.55k | qpack->insert_count = 0; |
254 | 6.55k | qpack->header_table_size = header_table_size; |
255 | 6.55k | qpack->max_entries = header_table_size / 32; |
256 | 6.55k | qpack->total_inserts = 0; |
257 | 6.55k | qpack->max_blocked = max_blocked; |
258 | 6.55k | header_table_init(&qpack->table, qpack->header_table_size); |
259 | 6.55k | memset(&qpack->blocked_streams, 0, sizeof(qpack->blocked_streams)); |
260 | | |
261 | 6.55k | return qpack; |
262 | 6.55k | } |
263 | | |
264 | | void h2o_qpack_destroy_decoder(h2o_qpack_decoder_t *qpack) |
265 | 6.55k | { |
266 | 6.55k | header_table_dispose(&qpack->table); |
267 | 6.55k | free(qpack->blocked_streams.list.entries); |
268 | 6.55k | free(qpack); |
269 | 6.55k | } |
270 | | |
271 | | static void decoder_link_blocked(h2o_qpack_decoder_t *qpack, int64_t stream_id, int64_t largest_ref) |
272 | 0 | { |
273 | 0 | size_t i; |
274 | |
|
275 | 0 | h2o_vector_reserve(NULL, &qpack->blocked_streams.list, qpack->blocked_streams.list.size + 1); |
276 | 0 | for (i = qpack->blocked_streams.list.size; i != 0; --i) |
277 | 0 | if (qpack->blocked_streams.list.entries[i - 1].largest_ref <= largest_ref) |
278 | 0 | break; |
279 | 0 | if (i != qpack->blocked_streams.list.size) |
280 | 0 | memmove(qpack->blocked_streams.list.entries + i + 1, qpack->blocked_streams.list.entries + i, |
281 | 0 | sizeof(qpack->blocked_streams.list.entries[0]) * (qpack->blocked_streams.list.size - i)); |
282 | 0 | qpack->blocked_streams.list.entries[i] = (struct st_h2o_qpack_blocked_streams_t){stream_id, largest_ref}; |
283 | 0 | ++qpack->blocked_streams.list.size; |
284 | 0 | } |
285 | | |
286 | | static void decoder_insert(h2o_qpack_decoder_t *qpack, struct st_h2o_qpack_header_t *added) |
287 | 0 | { |
288 | 0 | ++qpack->insert_count; |
289 | 0 | ++qpack->total_inserts; |
290 | 0 | fprintf(stderr, "#%s:%" PRIu64 ":%.*s\t%.*s\n", __FUNCTION__, qpack->total_inserts, (int)added->name->len, added->name->base, |
291 | 0 | (int)added->value_len, added->value); |
292 | 0 | header_table_insert(&qpack->table, added); |
293 | 0 | } |
294 | | |
295 | | static int decode_value_and_insert(h2o_qpack_decoder_t *qpack, struct st_h2o_qpack_header_t *header, int is_huff, |
296 | | const uint8_t *qstr, size_t qstrlen, const char **err_desc) |
297 | 0 | { |
298 | 0 | if ((header->value_len = decode_value(header->value, &header->soft_errors, is_huff, qstr, qstrlen, err_desc)) == SIZE_MAX) |
299 | 0 | goto Fail; |
300 | 0 | if (header->name->len + header->value_len + HEADER_ENTRY_SIZE_OFFSET > qpack->table.max_size) { |
301 | 0 | *err_desc = h2o_qpack_err_header_exceeds_table_size; |
302 | 0 | goto Fail; |
303 | 0 | } |
304 | 0 | decoder_insert(qpack, header); |
305 | 0 | return 0; |
306 | 0 | Fail: |
307 | 0 | h2o_mem_release_shared(header); |
308 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
309 | 0 | } |
310 | | |
311 | | static int insert_token_header(h2o_qpack_decoder_t *qpack, const h2o_token_t *name, int value_is_huff, const uint8_t *value, |
312 | | size_t value_len, const char **err_desc) |
313 | 0 | { |
314 | 0 | struct st_h2o_qpack_header_t *header = |
315 | 0 | h2o_mem_alloc_shared(NULL, offsetof(struct st_h2o_qpack_header_t, value) + (value_len * 2) + 1, NULL); |
316 | |
|
317 | 0 | header->name = (h2o_iovec_t *)&name->buf; |
318 | 0 | header->soft_errors = 0; |
319 | 0 | return decode_value_and_insert(qpack, header, value_is_huff, value, value_len, err_desc); |
320 | 0 | } |
321 | | |
322 | | static int insert_literal_header(h2o_qpack_decoder_t *qpack, const char *name, size_t name_len, int value_is_huff, |
323 | | const uint8_t *value, size_t value_len, unsigned soft_errors, const char **err_desc) |
324 | 0 | { |
325 | 0 | size_t value_capacity = (value_is_huff ? value_len * 2 : value_len) + 1; |
326 | 0 | struct st_h2o_qpack_header_t *header = |
327 | 0 | h2o_mem_alloc_shared(NULL, offsetof(struct st_h2o_qpack_header_t, value) + value_capacity + name_len + 1, NULL); |
328 | |
|
329 | 0 | header->_name_buf = h2o_iovec_init(header->value + value_capacity, name_len); |
330 | 0 | memcpy(header->_name_buf.base, name, name_len); |
331 | 0 | header->_name_buf.base[name_len] = '\0'; |
332 | 0 | header->_name_buf.len = name_len; |
333 | 0 | header->name = &header->_name_buf; |
334 | 0 | header->soft_errors = soft_errors; |
335 | |
|
336 | 0 | return decode_value_and_insert(qpack, header, value_is_huff, value, value_len, err_desc); |
337 | 0 | } |
338 | | |
339 | | static int64_t qpack_table_total_inserts(struct st_h2o_qpack_header_table_t *table) |
340 | 5.81k | { |
341 | 5.81k | return table->base_offset + (table->last - table->first); |
342 | 5.81k | } |
343 | | |
344 | | static int insert_with_name_reference(h2o_qpack_decoder_t *qpack, int name_is_static, int64_t name_index, int value_is_huff, |
345 | | const uint8_t *value, int64_t value_len, const char **err_desc) |
346 | 0 | { |
347 | 0 | if (name_is_static) { |
348 | 0 | const h2o_qpack_static_table_entry_t *ref; |
349 | 0 | if ((ref = resolve_static_abs(name_index, err_desc)) == NULL) |
350 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
351 | 0 | return insert_token_header(qpack, ref->name, value_is_huff, value, value_len, err_desc); |
352 | 0 | } else { |
353 | 0 | struct st_h2o_qpack_header_t *ref; |
354 | 0 | int64_t base_index = qpack_table_total_inserts(&qpack->table) - 1; |
355 | 0 | if (name_index > base_index) { |
356 | 0 | *err_desc = h2o_qpack_err_invalid_dynamic_reference; |
357 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
358 | 0 | } |
359 | 0 | if ((ref = resolve_dynamic_abs(&qpack->table, base_index - name_index, err_desc)) == NULL) |
360 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
361 | 0 | if (h2o_iovec_is_token(ref->name)) { |
362 | 0 | return insert_token_header(qpack, (h2o_token_t *)ref->name, value_is_huff, value, value_len, err_desc); |
363 | 0 | } else { |
364 | 0 | return insert_literal_header(qpack, ref->name->base, ref->name->len, value_is_huff, value, value_len, |
365 | 0 | ref->soft_errors & H2O_HPACK_SOFT_ERROR_BIT_INVALID_NAME, err_desc); |
366 | 0 | } |
367 | 0 | } |
368 | 0 | } |
369 | | |
370 | | static int insert_without_name_reference(h2o_qpack_decoder_t *qpack, int qnhuff, const uint8_t *qn, int64_t qnlen, int qvhuff, |
371 | | const uint8_t *qv, int64_t qvlen, const char **err_desc) |
372 | 0 | { |
373 | 0 | h2o_iovec_t name; |
374 | 0 | unsigned soft_errors = 0; |
375 | |
|
376 | 0 | if (qnhuff) { |
377 | 0 | name.base = alloca(qnlen * 2); |
378 | 0 | if ((name.len = h2o_hpack_decode_huffman(name.base, &soft_errors, qn, qnlen, 1, err_desc)) == SIZE_MAX) |
379 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
380 | 0 | } else { |
381 | 0 | if (!h2o_hpack_validate_header_name(&soft_errors, (void *)qn, qnlen, err_desc)) |
382 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
383 | 0 | name = h2o_iovec_init(qn, qnlen); |
384 | 0 | } |
385 | | |
386 | 0 | const h2o_token_t *token; |
387 | 0 | if ((token = h2o_lookup_token(name.base, name.len)) != NULL) { |
388 | 0 | return insert_token_header(qpack, token, qvhuff, qv, qvlen, err_desc); |
389 | 0 | } else { |
390 | 0 | return insert_literal_header(qpack, name.base, name.len, qvhuff, qv, qvlen, soft_errors, err_desc); |
391 | 0 | } |
392 | 0 | } |
393 | | |
394 | | static int duplicate(h2o_qpack_decoder_t *qpack, int64_t index, const char **err_desc) |
395 | 0 | { |
396 | 0 | if (index >= qpack->table.last - qpack->table.first) { |
397 | 0 | *err_desc = h2o_qpack_err_invalid_duplicate; |
398 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
399 | 0 | } |
400 | | |
401 | 0 | struct st_h2o_qpack_header_t *header = qpack->table.last[-index - 1]; |
402 | 0 | h2o_mem_addref_shared(header); |
403 | 0 | decoder_insert(qpack, header); |
404 | 0 | return 0; |
405 | 0 | } |
406 | | |
407 | | static int dynamic_table_size_update(h2o_qpack_decoder_t *qpack, int64_t max_size, const char **err_desc) |
408 | 0 | { |
409 | 0 | if (max_size > qpack->header_table_size) { |
410 | 0 | *err_desc = h2o_qpack_err_invalid_max_size; |
411 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
412 | 0 | } |
413 | | |
414 | 0 | qpack->table.max_size = max_size; |
415 | 0 | header_table_evict(&qpack->table, 0); |
416 | 0 | return 0; |
417 | 0 | } |
418 | | |
419 | | int h2o_qpack_decoder_handle_input(h2o_qpack_decoder_t *qpack, int64_t **unblocked_stream_ids, size_t *num_unblocked, |
420 | | const uint8_t **_src, const uint8_t *src_end, const char **err_desc) |
421 | 0 | { |
422 | 0 | if (qpack->blocked_streams.num_unblocked != 0) { |
423 | 0 | size_t remaining = qpack->blocked_streams.list.size - qpack->blocked_streams.num_unblocked; |
424 | 0 | if (remaining != 0) |
425 | 0 | memmove(qpack->blocked_streams.list.entries, qpack->blocked_streams.list.entries + remaining, |
426 | 0 | sizeof(qpack->blocked_streams.list.entries[0]) * remaining); |
427 | 0 | qpack->blocked_streams.list.size = remaining; |
428 | 0 | qpack->blocked_streams.num_unblocked = 0; |
429 | 0 | } |
430 | |
|
431 | 0 | const uint8_t *src = *_src; |
432 | 0 | int ret = 0; |
433 | |
|
434 | 0 | while (src != src_end && ret == 0) { |
435 | 0 | switch (*src >> 5) { |
436 | 0 | default: /* insert with name reference */ { |
437 | 0 | int64_t name_index, value_len; |
438 | 0 | int name_is_static = (*src & 0x40) != 0; |
439 | 0 | if ((ret = decode_int(&name_index, &src, src_end, 6)) != 0) |
440 | 0 | goto Exit; |
441 | 0 | if (src == src_end) |
442 | 0 | goto Exit; |
443 | 0 | int value_is_huff = (*src & 0x80) != 0; |
444 | 0 | if ((ret = decode_int(&value_len, &src, src_end, 7)) != 0) |
445 | 0 | goto Exit; |
446 | 0 | if (!(src + value_len <= src_end)) |
447 | 0 | goto Exit; |
448 | 0 | ret = insert_with_name_reference(qpack, name_is_static, name_index, value_is_huff, src, value_len, err_desc); |
449 | 0 | src += value_len; |
450 | 0 | } break; |
451 | 0 | case 2: |
452 | 0 | case 3: /* insert without name reference */ { |
453 | 0 | int64_t name_len, value_len; |
454 | 0 | int name_is_huff = (*src & 0x20) != 0; |
455 | 0 | if ((ret = decode_int(&name_len, &src, src_end, 5)) != 0) |
456 | 0 | goto Exit; |
457 | 0 | if (!(src + name_len < src_end)) |
458 | 0 | goto Exit; |
459 | 0 | const uint8_t *name = src; |
460 | 0 | src += name_len; |
461 | 0 | int value_is_huff = (*src & 0x80) != 0; |
462 | 0 | if ((ret = decode_int(&value_len, &src, src_end, 7)) != 0) |
463 | 0 | goto Exit; |
464 | 0 | if (!(src + value_len <= src_end)) |
465 | 0 | goto Exit; |
466 | 0 | ret = insert_without_name_reference(qpack, name_is_huff, name, name_len, value_is_huff, src, value_len, err_desc); |
467 | 0 | src += value_len; |
468 | 0 | } break; |
469 | 0 | case 0: /* duplicate */ { |
470 | 0 | int64_t index; |
471 | 0 | if ((ret = decode_int(&index, &src, src_end, 5)) != 0) |
472 | 0 | goto Exit; |
473 | 0 | ret = duplicate(qpack, index, err_desc); |
474 | 0 | } break; |
475 | 0 | case 1: /* dynamic table size update */ { |
476 | 0 | int64_t max_size; |
477 | 0 | if ((ret = decode_int(&max_size, &src, src_end, 5)) != 0) |
478 | 0 | goto Exit; |
479 | 0 | ret = dynamic_table_size_update(qpack, max_size, err_desc); |
480 | 0 | } break; |
481 | 0 | } |
482 | 0 | *_src = src; |
483 | 0 | } |
484 | | |
485 | 0 | Exit: |
486 | 0 | if (ret == H2O_HTTP3_ERROR_INCOMPLETE) |
487 | 0 | ret = 0; |
488 | 0 | if (ret == 0) { |
489 | | /* build list of newly unblocked streams ids reusing the memory of the blocked streams list (nasty!) */ |
490 | 0 | *unblocked_stream_ids = &qpack->blocked_streams.list.entries[0].stream_id; |
491 | 0 | for (qpack->blocked_streams.num_unblocked = 0; qpack->blocked_streams.num_unblocked < qpack->blocked_streams.list.size; |
492 | 0 | ++qpack->blocked_streams.num_unblocked) { |
493 | 0 | if (qpack->blocked_streams.list.entries[qpack->blocked_streams.num_unblocked].largest_ref > qpack->total_inserts) |
494 | 0 | break; |
495 | 0 | (*unblocked_stream_ids)[qpack->blocked_streams.num_unblocked] = |
496 | 0 | qpack->blocked_streams.list.entries[qpack->blocked_streams.num_unblocked].stream_id; |
497 | 0 | } |
498 | 0 | *num_unblocked = qpack->blocked_streams.num_unblocked; |
499 | 0 | } |
500 | 0 | return (int)ret; |
501 | 0 | } |
502 | | |
503 | | size_t h2o_qpack_decoder_send_state_sync(h2o_qpack_decoder_t *qpack, uint8_t *outbuf) |
504 | 0 | { |
505 | 0 | if (qpack->insert_count == 0) |
506 | 0 | return 0; |
507 | | |
508 | 0 | uint8_t *dst = outbuf; |
509 | 0 | *dst = 0; |
510 | 0 | dst = h2o_hpack_encode_int(dst, qpack->insert_count, 6); |
511 | 0 | qpack->insert_count = 0; |
512 | |
|
513 | 0 | return dst - outbuf; |
514 | 0 | } |
515 | | |
516 | | size_t h2o_qpack_decoder_send_stream_cancel(h2o_qpack_decoder_t *qpack, uint8_t *outbuf, int64_t stream_id) |
517 | 0 | { |
518 | 0 | outbuf[0] = 0x40; |
519 | 0 | return h2o_hpack_encode_int(outbuf, stream_id, 6) - outbuf; |
520 | 0 | } |
521 | | |
522 | | static const h2o_qpack_static_table_entry_t *resolve_static(const uint8_t **src, const uint8_t *src_end, unsigned prefix_bits, |
523 | | const char **err_desc) |
524 | 617k | { |
525 | 617k | int64_t index; |
526 | | |
527 | 617k | if (decode_int(&index, src, src_end, prefix_bits) != 0) |
528 | 12 | goto Fail; |
529 | 617k | assert(index >= 0); |
530 | 617k | if (!(index < sizeof(h2o_qpack_static_table) / sizeof(h2o_qpack_static_table[0]))) |
531 | 152 | goto Fail; |
532 | 617k | return h2o_qpack_static_table + index; |
533 | | |
534 | 164 | Fail: |
535 | 164 | *err_desc = h2o_qpack_err_invalid_static_reference; |
536 | 164 | return NULL; |
537 | 617k | } |
538 | | |
539 | | static struct st_h2o_qpack_header_t *resolve_dynamic(struct st_h2o_qpack_header_table_t *table, int64_t base_index, |
540 | | const uint8_t **src, const uint8_t *src_end, unsigned prefix_bits, |
541 | | const char **err_desc) |
542 | 550 | { |
543 | 550 | int64_t off; |
544 | | |
545 | 550 | if (decode_int(&off, src, src_end, prefix_bits) != 0 || off >= base_index) { |
546 | 264 | *err_desc = h2o_qpack_err_invalid_dynamic_reference; |
547 | 264 | return NULL; |
548 | 264 | } |
549 | 286 | return resolve_dynamic_abs(table, base_index - off, err_desc); |
550 | 550 | } |
551 | | |
552 | | static struct st_h2o_qpack_header_t *resolve_dynamic_postbase(struct st_h2o_qpack_header_table_t *table, int64_t base_index, |
553 | | const uint8_t **src, const uint8_t *src_end, unsigned prefix_bits, |
554 | | const char **err_desc) |
555 | 483 | { |
556 | 483 | int64_t off; |
557 | | |
558 | 483 | if (decode_int(&off, src, src_end, prefix_bits) != 0 || off > INT64_MAX - base_index - 1) { |
559 | 122 | *err_desc = h2o_qpack_err_invalid_dynamic_reference; |
560 | 122 | return NULL; |
561 | 122 | } |
562 | 361 | return resolve_dynamic_abs(table, base_index + off + 1, err_desc); |
563 | 483 | } |
564 | | |
565 | | static h2o_iovec_t *decode_header_name_literal(h2o_mem_pool_t *pool, unsigned *soft_errors, const uint8_t **src, |
566 | | const uint8_t *src_end, unsigned prefix_bits, const char **err_desc) |
567 | 57.0k | { |
568 | 57.0k | h2o_iovec_t buf = {NULL}; |
569 | 57.0k | const h2o_token_t *token; |
570 | 57.0k | int is_huff; |
571 | 57.0k | int64_t len; |
572 | | |
573 | | /* obtain flags and length */ |
574 | 57.0k | is_huff = (**src >> prefix_bits) & 1; |
575 | 57.0k | if (decode_int(&len, src, src_end, prefix_bits) != 0) { |
576 | 7 | *err_desc = h2o_qpack_err_header_name_too_long; |
577 | 7 | goto Fail; |
578 | 7 | } |
579 | 57.0k | if (src_end - *src < len) |
580 | 117 | goto Fail; |
581 | | |
582 | | /* decode and convert to token (if possible) */ |
583 | 56.9k | if (is_huff) { |
584 | 16.9k | buf.base = h2o_mem_alloc_pool(pool, char, len * 2 + 1); |
585 | 16.9k | if ((buf.len = h2o_hpack_decode_huffman(buf.base, soft_errors, *src, len, 1, err_desc)) == SIZE_MAX) |
586 | 70 | goto Fail; |
587 | 16.8k | buf.base[buf.len] = '\0'; |
588 | 16.8k | token = h2o_lookup_token(buf.base, buf.len); |
589 | 39.9k | } else if ((token = h2o_lookup_token((const char *)*src, len)) != NULL) { |
590 | | /* was an uncompressed token */ |
591 | 28.0k | } else { |
592 | 28.0k | if (!h2o_hpack_validate_header_name(soft_errors, (void *)*src, len, err_desc)) |
593 | 47 | goto Fail; |
594 | 28.0k | buf = h2o_strdup(pool, (void *)*src, len); |
595 | 28.0k | } |
596 | 56.7k | *src += len; |
597 | | |
598 | | /* return the result */ |
599 | 56.7k | if (token != NULL) |
600 | 12.1k | return (h2o_iovec_t *)&token->buf; |
601 | 44.5k | h2o_iovec_t *ret = h2o_mem_alloc_pool(pool, h2o_iovec_t, 1); |
602 | 44.5k | *ret = buf; |
603 | 44.5k | return ret; |
604 | | |
605 | 241 | Fail: |
606 | 241 | return NULL; |
607 | 56.7k | } |
608 | | |
609 | | static h2o_iovec_t decode_header_value_literal(h2o_mem_pool_t *pool, unsigned *soft_errors, const uint8_t **src, |
610 | | const uint8_t *src_end, const char **err_desc) |
611 | 65.7k | { |
612 | 65.7k | h2o_iovec_t buf; |
613 | 65.7k | int64_t len; |
614 | | |
615 | | /* validate *src pointer before dereferencing it for the huffman bit check */ |
616 | 65.7k | if (!(*src < src_end)) |
617 | 1.47k | goto Fail; |
618 | 64.2k | int is_huff = (**src & 0x80) != 0; |
619 | | |
620 | 64.2k | if (decode_int(&len, src, src_end, 7) != 0) { |
621 | 9 | *err_desc = h2o_qpack_err_header_value_too_long; |
622 | 9 | goto Fail; |
623 | 9 | } |
624 | 64.2k | if (src_end - *src < len) |
625 | 465 | goto Fail; |
626 | | |
627 | 63.8k | buf.base = h2o_mem_alloc_pool(pool, char, is_huff ? len * 2 + 1 : len + 1); |
628 | 63.8k | if ((buf.len = decode_value(buf.base, soft_errors, is_huff, *src, len, err_desc)) == SIZE_MAX) |
629 | 40 | goto Fail; |
630 | 63.7k | *src += len; |
631 | | |
632 | 63.7k | return buf; |
633 | 1.99k | Fail: |
634 | 1.99k | return h2o_iovec_init(NULL, 0); |
635 | 63.8k | } |
636 | | |
637 | | struct st_h2o_qpack_decode_header_ctx_t { |
638 | | h2o_qpack_decoder_t *qpack; |
639 | | /** |
640 | | * These values are non-negative. |
641 | | */ |
642 | | int64_t req_insert_count, base_index; |
643 | | }; |
644 | | |
645 | | static size_t send_header_ack(h2o_qpack_decoder_t *qpack, struct st_h2o_qpack_decode_header_ctx_t *ctx, uint8_t *outbuf, |
646 | | int64_t stream_id) |
647 | 2.27k | { |
648 | 2.27k | if (ctx->req_insert_count == 0) |
649 | 2.27k | return 0; |
650 | 0 | outbuf[0] = 0x80; |
651 | 0 | return h2o_hpack_encode_int(outbuf, stream_id, 7) - outbuf; |
652 | 2.27k | } |
653 | | |
654 | | static int decode_header(h2o_mem_pool_t *pool, void *_ctx, h2o_iovec_t **name, h2o_iovec_t *value, const uint8_t **src, |
655 | | const uint8_t *src_end, const char **err_desc) |
656 | 675k | { |
657 | 675k | struct st_h2o_qpack_decode_header_ctx_t *ctx = _ctx; |
658 | 675k | unsigned soft_errors; |
659 | | |
660 | 675k | switch (**src >> 4) { |
661 | 220k | case 12: |
662 | 230k | case 13: |
663 | 268k | case 14: |
664 | 608k | case 15: /* indexed static header field */ { |
665 | 608k | const h2o_qpack_static_table_entry_t *entry; |
666 | 608k | if ((entry = resolve_static(src, src_end, 6, err_desc)) == NULL) |
667 | 90 | goto Fail; |
668 | 608k | *name = (h2o_iovec_t *)&entry->name->buf; |
669 | 608k | *value = entry->value; |
670 | 608k | soft_errors = 0; |
671 | 608k | } break; |
672 | 91 | case 8: |
673 | 109 | case 9: |
674 | 130 | case 10: |
675 | 235 | case 11: /* indexed dynamic header field */ { |
676 | 235 | struct st_h2o_qpack_header_t *entry; |
677 | 235 | if ((entry = resolve_dynamic(&ctx->qpack->table, ctx->base_index, src, src_end, 6, err_desc)) == NULL) |
678 | 235 | goto Fail; |
679 | 0 | h2o_mem_link_shared(pool, entry); |
680 | 0 | *name = entry->name; |
681 | 0 | *value = h2o_iovec_init(entry->value, entry->value_len); |
682 | 0 | soft_errors = entry->soft_errors; |
683 | 0 | } break; |
684 | 4.45k | case 5: |
685 | 9.06k | case 7: /* literal header field with static name reference */ { |
686 | 9.06k | const h2o_qpack_static_table_entry_t *entry; |
687 | 9.06k | if ((entry = resolve_static(src, src_end, 4, err_desc)) == NULL) |
688 | 74 | goto Fail; |
689 | 8.99k | *name = (h2o_iovec_t *)&entry->name->buf; |
690 | 8.99k | soft_errors = 0; |
691 | 8.99k | if ((*value = decode_header_value_literal(pool, &soft_errors, src, src_end, err_desc)).base == NULL) |
692 | 139 | goto Fail; |
693 | 8.99k | } break; |
694 | 8.85k | case 4: |
695 | 315 | case 6: /* literal header field with dynamic name reference */ { |
696 | 315 | struct st_h2o_qpack_header_t *entry; |
697 | 315 | if ((entry = resolve_dynamic(&ctx->qpack->table, ctx->base_index, src, src_end, 4, err_desc)) == NULL) |
698 | 315 | goto Fail; |
699 | 0 | h2o_mem_link_shared(pool, entry); |
700 | 0 | *name = entry->name; |
701 | 0 | soft_errors = (entry->soft_errors) & H2O_HPACK_SOFT_ERROR_BIT_INVALID_NAME; |
702 | 0 | if ((*value = decode_header_value_literal(pool, &soft_errors, src, src_end, err_desc)).base == NULL) |
703 | 0 | goto Fail; |
704 | 0 | } break; |
705 | 46.4k | case 2: |
706 | 57.0k | case 3: /* literal header field without name reference */ { |
707 | 57.0k | soft_errors = 0; |
708 | 57.0k | if ((*name = decode_header_name_literal(pool, &soft_errors, src, src_end, 3, err_desc)) == NULL) |
709 | 241 | goto Fail; |
710 | 56.7k | if ((*value = decode_header_value_literal(pool, &soft_errors, src, src_end, err_desc)).base == NULL) |
711 | 1.85k | goto Fail; |
712 | 56.7k | } break; |
713 | 54.9k | case 1: /* indexed header field with post-base index */ { |
714 | 189 | struct st_h2o_qpack_header_t *entry; |
715 | 189 | if ((entry = resolve_dynamic_postbase(&ctx->qpack->table, ctx->base_index, src, src_end, 4, err_desc)) == NULL) |
716 | 189 | goto Fail; |
717 | 0 | h2o_mem_link_shared(pool, entry); |
718 | 0 | *name = entry->name; |
719 | 0 | *value = h2o_iovec_init(entry->value, entry->value_len); |
720 | 0 | soft_errors = entry->soft_errors; |
721 | 0 | } break; |
722 | 294 | case 0: /* literal header field with post-base name reference */ { |
723 | 294 | struct st_h2o_qpack_header_t *entry; |
724 | 294 | if ((entry = resolve_dynamic_postbase(&ctx->qpack->table, ctx->base_index, src, src_end, 3, err_desc)) == NULL) |
725 | 294 | goto Fail; |
726 | 0 | h2o_mem_link_shared(pool, entry); |
727 | 0 | *name = entry->name; |
728 | 0 | soft_errors = (entry->soft_errors) & H2O_HPACK_SOFT_ERROR_BIT_INVALID_NAME; |
729 | 0 | if ((*value = decode_header_value_literal(pool, &soft_errors, src, src_end, err_desc)).base == NULL) |
730 | 0 | goto Fail; |
731 | 0 | } break; |
732 | 0 | default: |
733 | 0 | h2o_fatal("unreachable"); |
734 | 0 | soft_errors = 0; |
735 | 0 | break; |
736 | 675k | } |
737 | | |
738 | 672k | if (soft_errors != 0) { |
739 | 38.8k | *err_desc = (soft_errors & H2O_HPACK_SOFT_ERROR_BIT_INVALID_NAME) != 0 |
740 | 38.8k | ? h2o_hpack_soft_err_found_invalid_char_in_header_name |
741 | 38.8k | : h2o_hpack_soft_err_found_invalid_char_in_header_value; |
742 | 38.8k | return H2O_HTTP2_ERROR_INVALID_HEADER_CHAR; |
743 | 38.8k | } |
744 | 633k | return 0; |
745 | 3.42k | Fail: |
746 | 3.42k | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
747 | 672k | } |
748 | | |
749 | | static int parse_decode_context(h2o_qpack_decoder_t *qpack, struct st_h2o_qpack_decode_header_ctx_t *ctx, int64_t stream_id, |
750 | | const uint8_t **src, const uint8_t *src_end) |
751 | 6.07k | { |
752 | 6.07k | ctx->qpack = qpack; |
753 | | |
754 | | /* largest reference */ |
755 | 6.07k | if (decode_int(&ctx->req_insert_count, src, src_end, 8) != 0) |
756 | 51 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
757 | 6.02k | if (ctx->req_insert_count > 0) { |
758 | 81 | if (qpack->max_entries == 0) |
759 | 81 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
760 | 0 | const uint32_t full_range = 2 * qpack->max_entries; |
761 | 0 | uint64_t max_value = qpack->total_inserts + qpack->max_entries; |
762 | 0 | uint64_t rounded = max_value / full_range * full_range; |
763 | 0 | ctx->req_insert_count += rounded - 1; |
764 | 0 | if (ctx->req_insert_count > max_value) { |
765 | 0 | if (ctx->req_insert_count <= full_range) |
766 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
767 | 0 | ctx->req_insert_count -= full_range; |
768 | 0 | } |
769 | 0 | if (ctx->req_insert_count == 0) |
770 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
771 | | /* Peer cannot send no more than PTLS_QUICINT_MAX instructions. That is because one QPACK instruction is no smaller than one |
772 | | * byte, and the maximum length of a QUIC stream (that conveys QPACK instructions) is 2^62 bytes in QUIC v1. */ |
773 | 0 | if (ctx->req_insert_count > PTLS_QUICINT_MAX) |
774 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
775 | 0 | } |
776 | | |
777 | | /* sign and base index */ |
778 | 5.93k | if (*src >= src_end) |
779 | 1 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
780 | 5.93k | int sign = (**src & 0x80) != 0; |
781 | 5.93k | int64_t delta_base; |
782 | 5.93k | if (decode_int(&delta_base, src, src_end, 7) != 0) |
783 | 2 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
784 | 5.93k | if (delta_base > PTLS_QUICINT_MAX) |
785 | 1 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
786 | 5.93k | ctx->base_index = sign == 0 ? ctx->req_insert_count + delta_base : ctx->req_insert_count - delta_base - 1; |
787 | 5.93k | if (ctx->base_index < 0) { |
788 | | /* Reject negative base index though current QPACK specification doesn't mention such case; let's keep our eyes on |
789 | | * https://github.com/quicwg/base-drafts/issues/4938 */ |
790 | 123 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
791 | 123 | } |
792 | | |
793 | | /* is the stream blocked? */ |
794 | 5.81k | if (ctx->req_insert_count >= qpack_table_total_inserts(&qpack->table)) { |
795 | 0 | if (qpack->blocked_streams.list.size + 1 >= qpack->max_blocked) |
796 | 0 | return H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
797 | 0 | decoder_link_blocked(qpack, stream_id, ctx->req_insert_count); |
798 | 0 | return H2O_HTTP3_ERROR_INCOMPLETE; |
799 | 0 | } |
800 | | |
801 | 5.81k | return 0; |
802 | 5.81k | } |
803 | | |
804 | | static int normalize_error_code(int err) |
805 | 3.53k | { |
806 | | /* convert H2 errors (except invaild_header_char) to QPACK error code */ |
807 | 3.53k | if (err < 0 && err != H2O_HTTP2_ERROR_INVALID_HEADER_CHAR) |
808 | 108 | err = H2O_HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; |
809 | 3.53k | return err; |
810 | 3.53k | } |
811 | | |
812 | | int h2o_qpack_parse_request(h2o_mem_pool_t *pool, h2o_qpack_decoder_t *qpack, int64_t stream_id, h2o_iovec_t *method, |
813 | | const h2o_url_scheme_t **scheme, h2o_iovec_t *authority, h2o_iovec_t *path, h2o_iovec_t *protocol, |
814 | | h2o_headers_t *headers, int *pseudo_header_exists_map, size_t *content_length, h2o_iovec_t *expect, |
815 | | h2o_cache_digests_t **digests, h2o_iovec_t *datagram_flow_id, uint8_t *outbuf, size_t *outbufsize, |
816 | | const uint8_t *_src, size_t len, const char **err_desc) |
817 | 6.07k | { |
818 | 6.07k | struct st_h2o_qpack_decode_header_ctx_t ctx; |
819 | 6.07k | const uint8_t *src = _src, *src_end = src + len; |
820 | 6.07k | int ret; |
821 | | |
822 | 6.07k | if ((ret = parse_decode_context(qpack, &ctx, stream_id, &src, src_end)) != 0) |
823 | 259 | return ret; |
824 | 5.81k | if ((ret = h2o_hpack_parse_request(pool, decode_header, &ctx, method, scheme, authority, path, protocol, headers, |
825 | 5.81k | pseudo_header_exists_map, content_length, expect, digests, datagram_flow_id, src, |
826 | 5.81k | src_end - src, err_desc)) != 0) { |
827 | | /* bail out if the error is a hard error, otherwise build header ack then return */ |
828 | 3.80k | if (ret != H2O_HTTP2_ERROR_INVALID_HEADER_CHAR) |
829 | 3.53k | return normalize_error_code(ret); |
830 | 3.80k | } |
831 | | |
832 | 2.27k | *outbufsize = send_header_ack(qpack, &ctx, outbuf, stream_id); |
833 | 2.27k | return ret; |
834 | 5.81k | } |
835 | | |
836 | | int h2o_qpack_parse_response(h2o_mem_pool_t *pool, h2o_qpack_decoder_t *qpack, int64_t stream_id, int *status, |
837 | | h2o_headers_t *headers, h2o_iovec_t *datagram_flow_id, uint8_t *outbuf, size_t *outbufsize, |
838 | | const uint8_t *_src, size_t len, const char **err_desc) |
839 | 0 | { |
840 | 0 | struct st_h2o_qpack_decode_header_ctx_t ctx; |
841 | 0 | const uint8_t *src = _src, *src_end = src + len; |
842 | 0 | int ret; |
843 | |
|
844 | 0 | if ((ret = parse_decode_context(qpack, &ctx, stream_id, &src, src_end)) != 0) |
845 | 0 | return ret; |
846 | 0 | if ((ret = h2o_hpack_parse_response(pool, decode_header, &ctx, status, headers, datagram_flow_id, src, src_end - src, |
847 | 0 | err_desc)) != 0) |
848 | 0 | return normalize_error_code(ret); |
849 | | |
850 | 0 | *outbufsize = send_header_ack(qpack, &ctx, outbuf, stream_id); |
851 | 0 | return 0; |
852 | 0 | } |
853 | | |
854 | | h2o_qpack_encoder_t *h2o_qpack_create_encoder(uint32_t header_table_size, uint16_t max_blocked) |
855 | 0 | { |
856 | 0 | h2o_qpack_encoder_t *qpack = h2o_mem_alloc(sizeof(*qpack)); |
857 | 0 | header_table_init(&qpack->table, header_table_size); |
858 | 0 | qpack->largest_known_received = 0; |
859 | 0 | qpack->max_blocked = max_blocked; |
860 | 0 | qpack->num_blocked = 0; |
861 | 0 | memset(&qpack->inflight, 0, sizeof(qpack->inflight)); |
862 | 0 | return qpack; |
863 | 0 | } |
864 | | |
865 | | void h2o_qpack_destroy_encoder(h2o_qpack_encoder_t *qpack) |
866 | 0 | { |
867 | 0 | header_table_dispose(&qpack->table); |
868 | 0 | free(qpack->inflight.entries); |
869 | 0 | free(qpack); |
870 | 0 | } |
871 | | |
872 | | static int handle_table_state_synchronize(h2o_qpack_encoder_t *qpack, int64_t insert_count, const char **err_desc) |
873 | 0 | { |
874 | 0 | if (qpack == NULL || insert_count == 0) |
875 | 0 | goto Error; |
876 | | |
877 | 0 | int64_t new_value = qpack->largest_known_received + insert_count; |
878 | 0 | if (new_value >= qpack_table_total_inserts(&qpack->table)) |
879 | 0 | goto Error; |
880 | 0 | qpack->largest_known_received = new_value; |
881 | |
|
882 | 0 | return 0; |
883 | 0 | Error: |
884 | 0 | *err_desc = "Table State Synchronize: invalid argument"; |
885 | 0 | return H2O_HTTP3_ERROR_QPACK_DECODER_STREAM; |
886 | 0 | } |
887 | | |
888 | | static void evict_inflight_by_index(h2o_qpack_encoder_t *qpack, size_t index) |
889 | 0 | { |
890 | 0 | if (qpack->inflight.entries[index].encoder_flags.is_blocking) |
891 | 0 | --qpack->num_blocked; |
892 | 0 | --qpack->inflight.size; |
893 | |
|
894 | 0 | if (qpack->inflight.size == 0) { |
895 | 0 | free(qpack->inflight.entries); |
896 | 0 | memset(&qpack->inflight, 0, sizeof(qpack->inflight)); |
897 | 0 | } else if (index < qpack->inflight.size) { |
898 | 0 | memmove(qpack->inflight.entries + index, qpack->inflight.entries + index + 1, qpack->inflight.size - index); |
899 | 0 | } |
900 | 0 | } |
901 | | |
902 | | static int handle_header_ack(h2o_qpack_encoder_t *qpack, int64_t stream_id, const char **err_desc) |
903 | 0 | { |
904 | 0 | size_t i; |
905 | |
|
906 | 0 | if (qpack != NULL) { |
907 | 0 | for (i = 0; i < qpack->inflight.size; ++i) |
908 | 0 | if (qpack->inflight.entries[i].stream_id == stream_id) |
909 | 0 | goto Found; |
910 | 0 | } |
911 | | /* not found */ |
912 | 0 | *err_desc = "Header Acknowledgement: invalid stream id"; |
913 | 0 | return H2O_HTTP3_ERROR_QPACK_DECODER_STREAM; |
914 | | |
915 | 0 | Found: |
916 | | /* update largest reference */ |
917 | 0 | if (qpack->largest_known_received < qpack->inflight.entries[i].largest_ref) |
918 | 0 | qpack->largest_known_received = qpack->inflight.entries[i].largest_ref; |
919 | | /* evict the found entry */ |
920 | 0 | evict_inflight_by_index(qpack, i); |
921 | |
|
922 | 0 | return 0; |
923 | 0 | } |
924 | | |
925 | | static int handle_stream_cancellation(h2o_qpack_encoder_t *qpack, int64_t stream_id, const char **err_desc) |
926 | 0 | { |
927 | 0 | size_t index = 0; |
928 | |
|
929 | 0 | if (qpack != NULL) { |
930 | 0 | while (index < qpack->inflight.size) { |
931 | 0 | if (qpack->inflight.entries[index].stream_id == stream_id) { |
932 | 0 | evict_inflight_by_index(qpack, index); |
933 | 0 | } else { |
934 | 0 | ++index; |
935 | 0 | } |
936 | 0 | } |
937 | 0 | } |
938 | |
|
939 | 0 | return 0; |
940 | 0 | } |
941 | | |
942 | | int h2o_qpack_encoder_handle_input(h2o_qpack_encoder_t *qpack, const uint8_t **_src, const uint8_t *src_end, const char **err_desc) |
943 | 0 | { |
944 | 0 | const uint8_t *src = *_src; |
945 | 0 | int ret = 0; |
946 | |
|
947 | 0 | while (src != src_end && ret == 0) { |
948 | 0 | switch (*src >> 6) { |
949 | 0 | case 0: /* table state synchronize */ { |
950 | 0 | int64_t insert_count; |
951 | 0 | if ((ret = decode_int(&insert_count, &src, src_end, 6)) != 0) |
952 | 0 | goto Exit; |
953 | 0 | ret = handle_table_state_synchronize(qpack, insert_count, err_desc); |
954 | 0 | } break; |
955 | 0 | default: /* header ack */ { |
956 | 0 | int64_t stream_id; |
957 | 0 | if ((ret = decode_int(&stream_id, &src, src_end, 7)) != 0) |
958 | 0 | goto Exit; |
959 | 0 | ret = handle_header_ack(qpack, stream_id, err_desc); |
960 | 0 | } break; |
961 | 0 | case 1: /* stream cancellation */ { |
962 | 0 | int64_t stream_id; |
963 | 0 | if ((ret = decode_int(&stream_id, &src, src_end, 6)) != 0) |
964 | 0 | goto Exit; |
965 | 0 | ret = handle_stream_cancellation(qpack, stream_id, err_desc); |
966 | 0 | } break; |
967 | 0 | } |
968 | 0 | *_src = src; |
969 | 0 | } |
970 | | |
971 | 0 | Exit: |
972 | 0 | if (ret == H2O_HTTP3_ERROR_INCOMPLETE) |
973 | 0 | ret = 0; |
974 | 0 | return (int)ret; |
975 | 0 | } |
976 | | |
977 | | static int64_t lookup_dynamic(h2o_qpack_encoder_t *qpack, const h2o_iovec_t *name, h2o_iovec_t value, int acked_only, int *is_exact) |
978 | 0 | { |
979 | 0 | size_t i; |
980 | 0 | int64_t name_found = -1; |
981 | |
|
982 | 0 | for (i = acked_only ? qpack->largest_known_received : qpack_table_total_inserts(&qpack->table) - 1; |
983 | 0 | i >= qpack->table.base_offset; --i) { |
984 | 0 | struct st_h2o_qpack_header_t *entry = qpack->table.first[i - qpack->table.base_offset]; |
985 | | /* compare names (and continue unless they match) */ |
986 | 0 | if (h2o_iovec_is_token(name)) { |
987 | 0 | if (name != entry->name) |
988 | 0 | continue; |
989 | 0 | } else { |
990 | 0 | if (!h2o_memis(name->base, name->len, entry->name->base, entry->name->len)) |
991 | 0 | continue; |
992 | 0 | } |
993 | | /* compare values */ |
994 | 0 | if (h2o_memis(value.base, value.len, entry->value, entry->value_len)) { |
995 | 0 | *is_exact = 1; |
996 | 0 | return i; |
997 | 0 | } |
998 | 0 | if (name_found == -1) |
999 | 0 | name_found = i; |
1000 | 0 | } |
1001 | | |
1002 | 0 | *is_exact = 0; |
1003 | 0 | return name_found; |
1004 | 0 | } |
1005 | | |
1006 | | static void flatten_int(h2o_byte_vector_t *buf, int64_t value, unsigned prefix_bits) |
1007 | 7.69k | { |
1008 | 7.69k | uint8_t *p = h2o_hpack_encode_int(buf->entries + buf->size, value, prefix_bits); |
1009 | 7.69k | buf->size = p - buf->entries; |
1010 | 7.69k | } |
1011 | | |
1012 | | static void flatten_string(h2o_byte_vector_t *buf, const char *src, size_t len, unsigned prefix_bits, int dont_compress) |
1013 | 4.79k | { |
1014 | 4.79k | size_t hufflen; |
1015 | | |
1016 | 4.79k | if (dont_compress || (hufflen = h2o_hpack_encode_huffman(buf->entries + buf->size + 1, (void *)src, len)) == SIZE_MAX) { |
1017 | | /* uncompressed */ |
1018 | 1.35k | buf->entries[buf->size] &= ~((2 << prefix_bits) - 1); /* clear huffman mark */ |
1019 | 1.35k | flatten_int(buf, len, prefix_bits); |
1020 | 1.35k | memcpy(buf->entries + buf->size, src, len); |
1021 | 1.35k | buf->size += len; |
1022 | 3.44k | } else { |
1023 | | /* build huffman header and adjust the location (if necessary) */ |
1024 | 3.44k | uint8_t tmpbuf[H2O_HPACK_ENCODE_INT_MAX_LENGTH], *p = tmpbuf; |
1025 | 3.44k | *p = buf->entries[buf->size] & ~((1 << prefix_bits) - 1); |
1026 | 3.44k | *p |= (1 << prefix_bits); /* huffman mark */ |
1027 | 3.44k | p = h2o_hpack_encode_int(p, hufflen, prefix_bits); |
1028 | 3.44k | if (p - tmpbuf == 1) { |
1029 | 3.44k | buf->entries[buf->size] = tmpbuf[0]; |
1030 | 3.44k | } else { |
1031 | 0 | memmove(buf->entries + buf->size + (p - tmpbuf), buf->entries + buf->size + 1, hufflen); |
1032 | 0 | memcpy(buf->entries + buf->size, tmpbuf, p - tmpbuf); |
1033 | 0 | } |
1034 | 3.44k | buf->size += p - tmpbuf + hufflen; |
1035 | 3.44k | } |
1036 | 4.79k | } |
1037 | | |
1038 | | static void emit_insert_with_nameref(h2o_qpack_encoder_t *qpack, h2o_mem_pool_t *pool, h2o_byte_vector_t *buf, int is_static, |
1039 | | int64_t index, h2o_iovec_t value) |
1040 | 0 | { |
1041 | 0 | h2o_vector_reserve(pool, buf, buf->size + (H2O_HPACK_ENCODE_INT_MAX_LENGTH * 2) + value.len); |
1042 | 0 | buf->entries[buf->size] = 0x80 | (is_static ? 0x40 : 0); |
1043 | 0 | flatten_int(buf, index, 6); |
1044 | 0 | flatten_string(buf, value.base, value.len, 7, 0); |
1045 | 0 | } |
1046 | | |
1047 | | static void emit_insert_without_nameref(h2o_qpack_encoder_t *qpack, h2o_mem_pool_t *pool, h2o_byte_vector_t *buf, |
1048 | | const h2o_iovec_t *name, h2o_iovec_t value) |
1049 | 0 | { |
1050 | 0 | h2o_vector_reserve(pool, buf, buf->size + (H2O_HPACK_ENCODE_INT_MAX_LENGTH * 2) + name->len + value.len); |
1051 | 0 | buf->entries[buf->size] = 0x40; |
1052 | 0 | flatten_string(buf, name->base, name->len, 5, 0); |
1053 | 0 | flatten_string(buf, value.base, value.len, 7, 0); |
1054 | 0 | } |
1055 | | |
1056 | | static void flatten_static_indexed(struct st_h2o_qpack_flatten_context_t *ctx, int32_t index) |
1057 | 1.55k | { |
1058 | 1.55k | h2o_vector_reserve(ctx->pool, &ctx->headers_buf, ctx->headers_buf.size + H2O_HPACK_ENCODE_INT_MAX_LENGTH); |
1059 | 1.55k | ctx->headers_buf.entries[ctx->headers_buf.size] = 0xc0; |
1060 | 1.55k | flatten_int(&ctx->headers_buf, index, 6); |
1061 | 1.55k | } |
1062 | | |
1063 | | static void flatten_dynamic_indexed(struct st_h2o_qpack_flatten_context_t *ctx, int64_t index) |
1064 | 0 | { |
1065 | 0 | h2o_vector_reserve(ctx->pool, &ctx->headers_buf, ctx->headers_buf.size + H2O_HPACK_ENCODE_INT_MAX_LENGTH); |
1066 | |
|
1067 | 0 | if (index > ctx->largest_ref) |
1068 | 0 | ctx->largest_ref = index; |
1069 | |
|
1070 | 0 | if (index <= ctx->base_index) { |
1071 | | /* indexed */ |
1072 | 0 | ctx->headers_buf.entries[ctx->headers_buf.size] = 0x80; |
1073 | 0 | flatten_int(&ctx->headers_buf, ctx->base_index - index, 6); |
1074 | 0 | } else { |
1075 | | /* indexed (post-base) */ |
1076 | 0 | ctx->headers_buf.entries[ctx->headers_buf.size] = 0x10; |
1077 | 0 | flatten_int(&ctx->headers_buf, index - ctx->base_index - 1, 4); |
1078 | 0 | } |
1079 | 0 | } |
1080 | | |
1081 | | static void flatten_static_nameref(struct st_h2o_qpack_flatten_context_t *ctx, int32_t index, h2o_iovec_t value, int dont_compress) |
1082 | 4.79k | { |
1083 | 4.79k | h2o_vector_reserve(ctx->pool, &ctx->headers_buf, ctx->headers_buf.size + H2O_HPACK_ENCODE_INT_MAX_LENGTH * 2 + value.len); |
1084 | 4.79k | ctx->headers_buf.entries[ctx->headers_buf.size] = 0x50 | (dont_compress ? 0x20 : 0); |
1085 | 4.79k | flatten_int(&ctx->headers_buf, index, 4); |
1086 | 4.79k | flatten_string(&ctx->headers_buf, value.base, value.len, 7, dont_compress); |
1087 | 4.79k | } |
1088 | | |
1089 | | static void flatten_dynamic_nameref(struct st_h2o_qpack_flatten_context_t *ctx, int64_t index, h2o_iovec_t value, int dont_compress) |
1090 | 0 | { |
1091 | 0 | if (index > ctx->largest_ref) |
1092 | 0 | ctx->largest_ref = index; |
1093 | |
|
1094 | 0 | h2o_vector_reserve(ctx->pool, &ctx->headers_buf, ctx->headers_buf.size + H2O_HPACK_ENCODE_INT_MAX_LENGTH * 2 + value.len); |
1095 | 0 | if (index <= ctx->base_index) { |
1096 | 0 | ctx->headers_buf.entries[ctx->headers_buf.size] = 0x40 | (dont_compress ? 0x20 : 0); |
1097 | 0 | flatten_int(&ctx->headers_buf, ctx->base_index - index, 4); |
1098 | 0 | } else { |
1099 | 0 | ctx->headers_buf.entries[ctx->headers_buf.size] = dont_compress ? 0x8 : 0; |
1100 | 0 | flatten_int(&ctx->headers_buf, index - ctx->base_index - 1, 3); |
1101 | 0 | } |
1102 | 0 | flatten_string(&ctx->headers_buf, value.base, value.len, 7, dont_compress); |
1103 | 0 | } |
1104 | | |
1105 | | static void flatten_without_nameref(struct st_h2o_qpack_flatten_context_t *ctx, const h2o_iovec_t *name, h2o_iovec_t value, |
1106 | | int dont_compress) |
1107 | 0 | { |
1108 | 0 | h2o_vector_reserve(ctx->pool, &ctx->headers_buf, |
1109 | 0 | ctx->headers_buf.size + H2O_HPACK_ENCODE_INT_MAX_LENGTH * 2 + name->len + value.len); |
1110 | 0 | ctx->headers_buf.entries[ctx->headers_buf.size] = 0x20 | (dont_compress ? 0x10 : 0); |
1111 | 0 | flatten_string(&ctx->headers_buf, name->base, name->len, 3, 0); |
1112 | 0 | flatten_string(&ctx->headers_buf, value.base, value.len, 7, dont_compress); |
1113 | 0 | } |
1114 | | |
1115 | | static void do_flatten_header(struct st_h2o_qpack_flatten_context_t *ctx, int32_t static_index, int is_exact, int likely_to_repeat, |
1116 | | const h2o_iovec_t *name, h2o_iovec_t value, h2o_header_flags_t flags) |
1117 | 4.79k | { |
1118 | 4.79k | int64_t dynamic_index; |
1119 | | |
1120 | 4.79k | if (static_index >= 0 && is_exact) { |
1121 | 0 | flatten_static_indexed(ctx, static_index); |
1122 | 0 | return; |
1123 | 0 | } |
1124 | | |
1125 | 4.79k | if (ctx->qpack != NULL) { |
1126 | | /* try dynamic indexed */ |
1127 | 0 | if ((dynamic_index = lookup_dynamic(ctx->qpack, name, value, ctx->encoder_buf == NULL, &is_exact)) >= 0 && is_exact) { |
1128 | 0 | flatten_dynamic_indexed(ctx, dynamic_index); |
1129 | 0 | return; |
1130 | 0 | } |
1131 | | /* emit to encoder buf and dynamic index? |
1132 | | * At the moment the strategy is dumb; we emit encoder stream data until the table becomes full. Never triggers eviction. |
1133 | | */ |
1134 | 0 | if (likely_to_repeat && ctx->encoder_buf != NULL && ((static_index < 0 && dynamic_index < 0) || value.len >= 8) && |
1135 | 0 | name->len + value.len + HEADER_ENTRY_SIZE_OFFSET <= ctx->qpack->table.max_size - ctx->qpack->table.num_bytes) { |
1136 | | /* emit instruction to decoder stream */ |
1137 | 0 | if (static_index >= 0) { |
1138 | 0 | emit_insert_with_nameref(ctx->qpack, ctx->pool, ctx->encoder_buf, 1, static_index, value); |
1139 | 0 | } else if (dynamic_index >= 0) { |
1140 | 0 | emit_insert_with_nameref(ctx->qpack, ctx->pool, ctx->encoder_buf, 0, dynamic_index, value); |
1141 | 0 | } else { |
1142 | 0 | emit_insert_without_nameref(ctx->qpack, ctx->pool, ctx->encoder_buf, name, value); |
1143 | 0 | } |
1144 | | /* register the entry to table */ |
1145 | 0 | struct st_h2o_qpack_header_t *added; |
1146 | 0 | if (h2o_iovec_is_token(name)) { |
1147 | 0 | added = h2o_mem_alloc_shared(NULL, offsetof(struct st_h2o_qpack_header_t, value) + value.len + 1, NULL); |
1148 | 0 | added->name = (h2o_iovec_t *)name; |
1149 | 0 | } else { |
1150 | 0 | added = |
1151 | 0 | h2o_mem_alloc_shared(NULL, offsetof(struct st_h2o_qpack_header_t, value) + name->len + 1 + value.len + 1, NULL); |
1152 | 0 | added->name = &added->_name_buf; |
1153 | 0 | added->_name_buf = h2o_iovec_init(added->value + added->value_len + 1, name->len); |
1154 | 0 | memcpy(added->_name_buf.base, name->base, name->len); |
1155 | 0 | added->_name_buf.base[name->len] = '\0'; |
1156 | 0 | } |
1157 | 0 | added->value_len = value.len; |
1158 | 0 | memcpy(added->value, value.base, value.len); |
1159 | 0 | added->value[value.len] = '\0'; |
1160 | 0 | header_table_insert(&ctx->qpack->table, added); |
1161 | | /* emit header field to headers block */ |
1162 | 0 | flatten_dynamic_indexed(ctx, qpack_table_total_inserts(&ctx->qpack->table) - 1); |
1163 | 0 | return; |
1164 | 0 | } |
1165 | 4.79k | } else { |
1166 | 4.79k | dynamic_index = -1; |
1167 | 4.79k | } |
1168 | | |
1169 | 4.79k | if (static_index >= 0) { |
1170 | 4.79k | flatten_static_nameref(ctx, static_index, value, flags.dont_compress); |
1171 | 4.79k | } else if (dynamic_index >= 0) { |
1172 | 0 | flatten_dynamic_nameref(ctx, dynamic_index, value, flags.dont_compress); |
1173 | 0 | } else { |
1174 | 0 | flatten_without_nameref(ctx, name, value, flags.dont_compress); |
1175 | 0 | } |
1176 | 4.79k | } |
1177 | | |
1178 | | static void flatten_header(struct st_h2o_qpack_flatten_context_t *ctx, const h2o_header_t *header) |
1179 | 1.72k | { |
1180 | 1.72k | int32_t static_index = -1; |
1181 | 1.72k | int is_exact = 0, likely_to_repeat = 0; |
1182 | | |
1183 | | /* obtain static index if possible */ |
1184 | 1.72k | if (h2o_iovec_is_token(header->name)) { |
1185 | 1.72k | const h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, header->name); |
1186 | 1.72k | static_index = h2o_qpack_lookup_static[token - h2o__tokens](header->value, &is_exact); |
1187 | 1.72k | likely_to_repeat = token->flags.likely_to_repeat; |
1188 | 1.72k | } |
1189 | | |
1190 | 1.72k | return do_flatten_header(ctx, static_index, is_exact, likely_to_repeat, header->name, header->value, header->flags); |
1191 | 1.72k | } |
1192 | | |
1193 | | static void flatten_known_header_with_static_lookup(struct st_h2o_qpack_flatten_context_t *ctx, |
1194 | | h2o_qpack_lookup_static_cb lookup_cb, const h2o_token_t *name, |
1195 | | h2o_iovec_t value) |
1196 | 0 | { |
1197 | 0 | int is_exact; |
1198 | 0 | int32_t static_index = lookup_cb(value, &is_exact); |
1199 | 0 | assert(index >= 0); |
1200 | | |
1201 | 0 | do_flatten_header(ctx, static_index, is_exact, name->flags.likely_to_repeat, &name->buf, value, (h2o_header_flags_t){0}); |
1202 | 0 | } |
1203 | | |
1204 | | /* header of the qpack message that are written afterwards */ |
1205 | | static const size_t PREFIX_CAPACITY = |
1206 | | 1 /* frame header */ + 8 /* frame payload len */ + H2O_HPACK_ENCODE_INT_MAX_LENGTH + H2O_HPACK_ENCODE_INT_MAX_LENGTH; |
1207 | | |
1208 | | static void prepare_flatten(struct st_h2o_qpack_flatten_context_t *ctx, h2o_qpack_encoder_t *qpack, h2o_mem_pool_t *pool, |
1209 | | int64_t stream_id, h2o_byte_vector_t *encoder_buf) |
1210 | 1.72k | { |
1211 | 1.72k | ctx->qpack = qpack; |
1212 | 1.72k | ctx->pool = pool; |
1213 | 1.72k | ctx->stream_id = stream_id; |
1214 | 1.72k | ctx->encoder_buf = qpack != NULL && qpack->num_blocked < qpack->max_blocked ? encoder_buf : NULL; |
1215 | 1.72k | ctx->headers_buf = (h2o_byte_vector_t){NULL}; |
1216 | 1.72k | ctx->base_index = qpack != NULL ? qpack_table_total_inserts(&qpack->table) - 1 : 0; |
1217 | 1.72k | ctx->largest_ref = 0; |
1218 | | |
1219 | | /* allocate some space, hoping to avoid realloc, but not wasting too much */ |
1220 | 1.72k | h2o_vector_reserve(ctx->pool, &ctx->headers_buf, PREFIX_CAPACITY + 100); |
1221 | 1.72k | ctx->headers_buf.size = PREFIX_CAPACITY; |
1222 | 1.72k | } |
1223 | | |
1224 | | static h2o_iovec_t finalize_flatten(struct st_h2o_qpack_flatten_context_t *ctx, size_t *serialized_header_len) |
1225 | 1.72k | { |
1226 | 1.72k | if (ctx->largest_ref == 0) { |
1227 | 1.72k | ctx->base_index = 0; |
1228 | 1.72k | } else { |
1229 | 0 | int is_blocking = 0; |
1230 | | /* adjust largest reference to achieve more compact representation on wire without risking blocking */ |
1231 | 0 | if (ctx->largest_ref < ctx->qpack->largest_known_received) { |
1232 | 0 | ctx->largest_ref = ctx->qpack->largest_known_received; |
1233 | 0 | } else if (ctx->largest_ref > ctx->qpack->largest_known_received) { |
1234 | 0 | assert(ctx->qpack->num_blocked < ctx->qpack->max_blocked); |
1235 | 0 | ++ctx->qpack->num_blocked; |
1236 | 0 | is_blocking = 1; |
1237 | 0 | } |
1238 | | /* mark as inflight */ |
1239 | 0 | h2o_vector_reserve(NULL, &ctx->qpack->inflight, ctx->qpack->inflight.size + 1); |
1240 | 0 | ctx->qpack->inflight.entries[ctx->qpack->inflight.size++] = |
1241 | 0 | (struct st_h2o_qpack_blocked_streams_t){ctx->stream_id, ctx->largest_ref, {{is_blocking}}}; |
1242 | 0 | } |
1243 | | |
1244 | 1.72k | size_t start_off = PREFIX_CAPACITY; |
1245 | | |
1246 | 1.72k | { /* prepend largest ref and delta base index */ |
1247 | 1.72k | uint8_t buf[H2O_HPACK_ENCODE_INT_MAX_LENGTH * 2], *p = buf; |
1248 | | /* largest_ref */ |
1249 | 1.72k | *p = 0; |
1250 | 1.72k | p = h2o_hpack_encode_int(p, ctx->largest_ref != 0 ? ctx->largest_ref + 1 : 0, 8); |
1251 | | /* delta base index */ |
1252 | 1.72k | if (ctx->largest_ref <= ctx->base_index) { |
1253 | 1.72k | *p = 0; |
1254 | 1.72k | p = h2o_hpack_encode_int(p, ctx->base_index - ctx->largest_ref, 7); |
1255 | 1.72k | } else { |
1256 | 0 | *p = 0x80; |
1257 | 0 | p = h2o_hpack_encode_int(p, ctx->largest_ref - ctx->base_index - 1, 7); |
1258 | 0 | } |
1259 | 1.72k | memcpy(ctx->headers_buf.entries + start_off - (p - buf), buf, p - buf); |
1260 | 1.72k | start_off -= p - buf; |
1261 | 1.72k | } |
1262 | | |
1263 | 1.72k | if (serialized_header_len != NULL) |
1264 | 1.72k | *serialized_header_len = ctx->headers_buf.size - start_off; |
1265 | | |
1266 | | /* prepend frame header */ |
1267 | 1.72k | size_t len_len = quicly_encodev_capacity(ctx->headers_buf.size - start_off); |
1268 | 1.72k | quicly_encodev(ctx->headers_buf.entries + start_off - len_len, ctx->headers_buf.size - start_off); |
1269 | 1.72k | start_off -= len_len; |
1270 | 1.72k | ctx->headers_buf.entries[--start_off] = H2O_HTTP3_FRAME_TYPE_HEADERS; |
1271 | | |
1272 | 1.72k | return h2o_iovec_init(ctx->headers_buf.entries + start_off, ctx->headers_buf.size - start_off); |
1273 | 1.72k | } |
1274 | | |
1275 | | h2o_iovec_t h2o_qpack_flatten_request(h2o_qpack_encoder_t *_qpack, h2o_mem_pool_t *_pool, int64_t _stream_id, |
1276 | | h2o_byte_vector_t *_encoder_buf, h2o_iovec_t method, const h2o_url_scheme_t *scheme, |
1277 | | h2o_iovec_t authority, h2o_iovec_t path, h2o_iovec_t protocol, const h2o_header_t *headers, |
1278 | | size_t num_headers, h2o_iovec_t datagram_flow_id) |
1279 | 0 | { |
1280 | 0 | struct st_h2o_qpack_flatten_context_t ctx; |
1281 | |
|
1282 | 0 | prepare_flatten(&ctx, _qpack, _pool, _stream_id, _encoder_buf); |
1283 | | |
1284 | | /* pseudo headers */ |
1285 | 0 | flatten_known_header_with_static_lookup(&ctx, h2o_qpack_lookup_method, H2O_TOKEN_METHOD, method); |
1286 | | /* CONNECT request that is not an RFC 9220 extended connect */ |
1287 | 0 | int old_style_connect = h2o_memis(method.base, method.len, H2O_STRLIT("CONNECT")) && protocol.base == NULL; |
1288 | 0 | if (!old_style_connect) { |
1289 | 0 | if (scheme == &H2O_URL_SCHEME_HTTP) { |
1290 | 0 | flatten_static_indexed(&ctx, 22); |
1291 | 0 | } else if (scheme == &H2O_URL_SCHEME_HTTPS) { |
1292 | 0 | flatten_static_indexed(&ctx, 23); |
1293 | 0 | } else { |
1294 | 0 | flatten_known_header_with_static_lookup(&ctx, h2o_qpack_lookup_scheme, H2O_TOKEN_SCHEME, scheme->name); |
1295 | 0 | } |
1296 | 0 | } |
1297 | 0 | flatten_known_header_with_static_lookup(&ctx, h2o_qpack_lookup_authority, H2O_TOKEN_AUTHORITY, authority); |
1298 | 0 | if (!old_style_connect) |
1299 | 0 | flatten_known_header_with_static_lookup(&ctx, h2o_qpack_lookup_path, H2O_TOKEN_PATH, path); |
1300 | 0 | if (protocol.base != NULL) |
1301 | 0 | flatten_known_header_with_static_lookup(&ctx, h2o_qpack_lookup_protocol, H2O_TOKEN_PROTOCOL, protocol); |
1302 | | |
1303 | | /* flatten headers */ |
1304 | 0 | size_t i; |
1305 | 0 | for (i = 0; i != num_headers; ++i) |
1306 | 0 | flatten_header(&ctx, headers + i); |
1307 | |
|
1308 | 0 | if (datagram_flow_id.base != NULL) |
1309 | 0 | flatten_known_header_with_static_lookup(&ctx, h2o_qpack_lookup_datagram_flow_id, H2O_TOKEN_DATAGRAM_FLOW_ID, |
1310 | 0 | datagram_flow_id); |
1311 | |
|
1312 | 0 | return finalize_flatten(&ctx, NULL); |
1313 | 0 | } |
1314 | | |
1315 | | h2o_iovec_t h2o_qpack_flatten_response(h2o_qpack_encoder_t *_qpack, h2o_mem_pool_t *_pool, int64_t _stream_id, |
1316 | | h2o_byte_vector_t *_encoder_buf, int status, const h2o_header_t *headers, size_t num_headers, |
1317 | | const h2o_iovec_t *server_name, size_t content_length, h2o_iovec_t datagram_flow_id, |
1318 | | size_t *serialized_header_len) |
1319 | 1.72k | { |
1320 | 1.72k | struct st_h2o_qpack_flatten_context_t ctx; |
1321 | | |
1322 | 1.72k | prepare_flatten(&ctx, _qpack, _pool, _stream_id, _encoder_buf); |
1323 | | |
1324 | | /* pseudo headers */ |
1325 | 1.72k | switch (status) { |
1326 | 0 | #define INDEXED_STATUS(cp, st) \ |
1327 | 1.55k | case st: \ |
1328 | 1.55k | flatten_static_indexed(&ctx, cp); \ |
1329 | 1.55k | break |
1330 | 0 | INDEXED_STATUS(24, 103); |
1331 | 539 | INDEXED_STATUS(25, 200); |
1332 | 0 | INDEXED_STATUS(26, 304); |
1333 | 594 | INDEXED_STATUS(27, 404); |
1334 | 0 | INDEXED_STATUS(28, 503); |
1335 | 0 | INDEXED_STATUS(63, 100); |
1336 | 0 | INDEXED_STATUS(64, 204); |
1337 | 0 | INDEXED_STATUS(65, 206); |
1338 | 0 | INDEXED_STATUS(66, 302); |
1339 | 410 | INDEXED_STATUS(67, 400); |
1340 | 11 | INDEXED_STATUS(68, 403); |
1341 | 0 | INDEXED_STATUS(69, 421); |
1342 | 0 | INDEXED_STATUS(70, 425); |
1343 | 0 | INDEXED_STATUS(71, 500); |
1344 | 0 | #undef INDEXED_STATUS |
1345 | 168 | default: { |
1346 | 168 | char status_str[sizeof(H2O_UINT16_LONGEST_STR)]; |
1347 | 168 | sprintf(status_str, "%" PRIu16, (uint16_t)status); |
1348 | 168 | do_flatten_header(&ctx, 24, 0, H2O_TOKEN_STATUS->flags.likely_to_repeat, &H2O_TOKEN_STATUS->buf, |
1349 | 168 | h2o_iovec_init(status_str, strlen(status_str)), (h2o_header_flags_t){0}); |
1350 | 168 | } break; |
1351 | 1.72k | } |
1352 | | |
1353 | | /* TODO keep some kind of reference to the indexed Server header, and reuse it */ |
1354 | 1.72k | if (server_name != NULL && server_name->len != 0) |
1355 | 1.72k | do_flatten_header(&ctx, 92, 0, H2O_TOKEN_SERVER->flags.likely_to_repeat, &H2O_TOKEN_SERVER->buf, *server_name, |
1356 | 1.72k | (h2o_header_flags_t){0}); |
1357 | | |
1358 | | /* content-length */ |
1359 | 1.72k | if (content_length != SIZE_MAX) { |
1360 | 1.18k | if (content_length == 0) { |
1361 | 0 | flatten_static_indexed(&ctx, 4); |
1362 | 1.18k | } else { |
1363 | 1.18k | char cl_str[sizeof(H2O_SIZE_T_LONGEST_STR)]; |
1364 | 1.18k | size_t cl_len = (size_t)sprintf(cl_str, "%zu", content_length); |
1365 | 1.18k | do_flatten_header(&ctx, 4, 0, H2O_TOKEN_CONTENT_LENGTH->flags.likely_to_repeat, &H2O_TOKEN_CONTENT_LENGTH->buf, |
1366 | 1.18k | h2o_iovec_init(cl_str, cl_len), (h2o_header_flags_t){0}); |
1367 | 1.18k | } |
1368 | 1.18k | } |
1369 | | |
1370 | | /* flatten headers */ |
1371 | 1.72k | size_t i; |
1372 | 3.44k | for (i = 0; i != num_headers; ++i) |
1373 | 1.72k | flatten_header(&ctx, headers + i); |
1374 | | |
1375 | 1.72k | if (datagram_flow_id.base != NULL) |
1376 | 0 | flatten_known_header_with_static_lookup(&ctx, h2o_qpack_lookup_datagram_flow_id, H2O_TOKEN_DATAGRAM_FLOW_ID, |
1377 | 0 | datagram_flow_id); |
1378 | | |
1379 | 1.72k | return finalize_flatten(&ctx, serialized_header_len); |
1380 | 1.72k | } |