From c9f20bf5a931757e8fe920c87291c0f650376fcf Mon Sep 17 00:00:00 2001 From: Matus Fabian Date: Thu, 25 Sep 2025 06:08:56 -0400 Subject: [PATCH] http: move generic functions to hpack_inlines.h Type: refactor Change-Id: Ia7ce7b7651098a5e25e3adadaa854529008ccef6 Signed-off-by: Matus Fabian --- src/plugins/http/http2/hpack.c | 746 ++------------------------------ src/plugins/http/http2/hpack.h | 63 --- src/plugins/http/http2/hpack_inlines.h | 765 +++++++++++++++++++++++++++++++++ src/plugins/http/http3/qpack.c | 57 ++- src/plugins/http/test/http_test.c | 43 +- 5 files changed, 853 insertions(+), 821 deletions(-) create mode 100644 src/plugins/http/http2/hpack_inlines.h diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c index 0dd68b6a5b1..b1461dfb638 100644 --- a/src/plugins/http/http2/hpack.c +++ b/src/plugins/http/http2/hpack.c @@ -2,12 +2,11 @@ * Copyright(c) 2025 Cisco Systems, Inc. */ -#include #include #include -#include #include #include +#include #define HPACK_STATIC_TABLE_SIZE 61 @@ -109,153 +108,14 @@ static http_token_t http_methods[] = { #define http_method_token(e) http_methods[e].base, http_methods[e].len -__clib_export uword -hpack_decode_int (u8 **src, u8 *end, u8 prefix_len) -{ - uword value, new_value; - u8 *p, shift = 0, byte; - u16 prefix_max; - - ASSERT (*src < end); - ASSERT (prefix_len >= 1 && prefix_len <= 8); - - p = *src; - prefix_max = (1 << prefix_len) - 1; - value = *p & (u8) prefix_max; - p++; - /* if integer value is less than 2^prefix_len-1 it's encoded within prefix */ - if (value != prefix_max) - { - *src = p; - return value; - } - - while (p != end) - { - byte = *p; - p++; - new_value = value + ((uword) (byte & 0x7F) << shift); - shift += 7; - /* check for overflow */ - if (new_value < value) - return HPACK_INVALID_INT; - value = new_value; - /* MSB of the last byte is zero */ - if ((byte & 0x80) == 0) - { - *src = p; - return value; - } - } - - return HPACK_INVALID_INT; -} - -http2_error_t -hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) -{ - u64 accumulator = 0; - u8 accumulator_len = 0; - u8 *p; - hpack_huffman_code_t *code; - - p = *src; - while (1) - { - /* out of space? */ - if (*buf_len == 0) - return HTTP2_ERROR_INTERNAL_ERROR; - /* refill */ - while (p < end && accumulator_len <= 56) - { - accumulator <<= 8; - accumulator_len += 8; - accumulator |= (u64) *p++; - } - /* first try short codes (5 - 8 bits) */ - code = - &huff_code_table_fast[(u8) (accumulator >> (accumulator_len - 8))]; - /* zero code length mean no luck */ - if (PREDICT_TRUE (code->code_len)) - { - **buf = code->symbol; - (*buf)++; - (*buf_len)--; - accumulator_len -= code->code_len; - } - else - { - /* slow path / long codes (10 - 30 bits) */ - u32 tmp; - /* group boundaries are aligned to 32 bits */ - if (accumulator_len < 32) - tmp = accumulator << (32 - accumulator_len); - else - tmp = accumulator >> (accumulator_len - 32); - /* figure out which interval code falls into, this is possible - * because HPACK use canonical Huffman codes - * see Schwartz, E. and B. Kallick, “Generating a canonical prefix - * encoding” - */ - hpack_huffman_group_t *hg = hpack_huffman_get_group (tmp); - /* this might happen with invalid EOS (longer than 7 bits) */ - if (hg->code_len > accumulator_len) - return HTTP2_ERROR_COMPRESSION_ERROR; - /* trim code to correct length */ - u32 code = (accumulator >> (accumulator_len - hg->code_len)) & - ((1 << hg->code_len) - 1); - if (!code) - return HTTP2_ERROR_COMPRESSION_ERROR; - /* find symbol in the list */ - **buf = hg->symbols[code - hg->first_code]; - (*buf)++; - (*buf_len)--; - accumulator_len -= hg->code_len; - } - /* all done */ - if (p == end && accumulator_len < 8) - { - /* there might be one more symbol encoded with short code */ - if (accumulator_len >= 5) - { - /* first check EOS case */ - if (((1 << accumulator_len) - 1) == - (accumulator & ((1 << accumulator_len) - 1))) - break; - - /* out of space? */ - if (*buf_len == 0) - return HTTP2_ERROR_INTERNAL_ERROR; - - /* if bogus EOF check bellow will fail */ - code = &huff_code_table_fast[(u8) (accumulator - << (8 - accumulator_len))]; - **buf = code->symbol; - (*buf)++; - (*buf_len)--; - accumulator_len -= code->code_len; - /* end at byte boundary? */ - if (accumulator_len == 0) - break; - } - /* we must end with EOS here */ - if (((1 << accumulator_len) - 1) != - (accumulator & ((1 << accumulator_len) - 1))) - return HTTP2_ERROR_COMPRESSION_ERROR; - break; - } - } - return HTTP2_ERROR_NO_ERROR; -} - -__clib_export http2_error_t +__clib_export hpack_error_t hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len) { u8 *p, is_huffman; uword len; if (*src == end) - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; p = *src; /* H flag in first bit */ @@ -264,11 +124,11 @@ hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len) /* length is integer with 7 bit prefix */ len = hpack_decode_int (&p, end, 7); if (PREDICT_FALSE (len == HPACK_INVALID_INT)) - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; /* do we have everything? */ if (len > (end - p)) - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; if (is_huffman) { @@ -279,108 +139,14 @@ hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len) { /* enough space? */ if (len > *buf_len) - return HTTP2_ERROR_INTERNAL_ERROR; + return HPACK_ERROR_UNKNOWN; clib_memcpy (*buf, p, len); *buf_len -= len; *buf += len; *src = (p + len); - return HTTP2_ERROR_NO_ERROR; - } -} - -__clib_export u8 * -hpack_encode_int (u8 *dst, uword value, u8 prefix_len) -{ - u16 prefix_max; - - ASSERT (prefix_len >= 1 && prefix_len <= 8); - - prefix_max = (1 << prefix_len) - 1; - - /* if integer value is less than 2^prefix_len-1 it's encoded within prefix */ - if (value < prefix_max) - { - *dst++ |= (u8) value; - return dst; - } - - /* otherwise all bits of the prefix are set to 1 */ - *dst++ |= (u8) prefix_max; - /* and the value is decreased by 2^prefix_len-1 */ - value -= prefix_max; - /* MSB of each byte is used as continuation flag */ - for (; value >= 0x80; value >>= 7) - *dst++ = 0x80 | (value & 0x7F); - /* except for the last byte */ - *dst++ = (u8) value; - - return dst; -} - -uword -hpack_huffman_encoded_len (const u8 *value, uword value_len) -{ - uword len = 0; - u8 *end; - hpack_huffman_symbol_t *sym; - - end = (u8 *) value + value_len; - while (value != end) - { - sym = &huff_sym_table[*value++]; - len += sym->code_len; - } - /* round up to byte boundary */ - return (len + 7) / 8; -} - -u8 * -hpack_encode_huffman (u8 *dst, const u8 *value, uword value_len) -{ - u8 *end; - hpack_huffman_symbol_t *sym; - u8 accumulator_len = 40; /* leftover (1 byte) + max code_len (4 bytes) */ - u64 accumulator = 0; /* to fit leftover and current code */ - - end = (u8 *) value + value_len; - - while (value != end) - { - sym = &huff_sym_table[*value++]; - /* add current code to leftover of previous one */ - accumulator |= (u64) sym->code << (accumulator_len - sym->code_len); - accumulator_len -= sym->code_len; - /* write only fully occupied bytes (max 4) */ - switch (accumulator_len) - { - case 1 ... 8: -#define WRITE_BYTE() \ - *dst = (u8) (accumulator >> 32); \ - accumulator_len += 8; \ - accumulator <<= 8; \ - dst++; - WRITE_BYTE (); - case 9 ... 16: - WRITE_BYTE (); - case 17 ... 24: - WRITE_BYTE (); - case 25 ... 32: - WRITE_BYTE (); - default: - break; - } - } - - /* padding (0-7 bits)*/ - ASSERT (accumulator_len > 32 && accumulator_len <= 40); - if (accumulator_len != 40) - { - accumulator |= (u64) 0x7F << (accumulator_len - 7); - *dst = (u8) (accumulator >> 32); - dst++; + return HPACK_ERROR_NONE; } - return dst; } __clib_export u8 * @@ -511,7 +277,7 @@ hpack_dynamic_table_add (hpack_dynamic_table_t *table, http_token_t *name, hpack_dynamic_table_entry_value_len (e)); } -static http2_error_t +static hpack_error_t hpack_get_table_entry (uword index, http_token_t *name, http_token_t *value, u8 value_is_indexed, hpack_dynamic_table_t *dt) { @@ -527,7 +293,7 @@ hpack_get_table_entry (uword index, http_token_t *name, http_token_t *value, } HTTP_DBG (2, "[%llu] %U: %U", index, format_http_bytes, e->name, e->name_len, format_http_bytes, e->value, e->value_len); - return HTTP2_ERROR_NO_ERROR; + return HPACK_ERROR_NONE; } else { @@ -536,7 +302,7 @@ hpack_get_table_entry (uword index, http_token_t *name, http_token_t *value, if (PREDICT_FALSE (!e)) { HTTP_DBG (1, "index %llu not in dynamic table", index); - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; } name->base = (char *) e->buf; name->len = e->name_len; @@ -544,19 +310,20 @@ hpack_get_table_entry (uword index, http_token_t *name, http_token_t *value, value->len = hpack_dynamic_table_entry_value_len (e); HTTP_DBG (2, "[%llu] %U: %U", index, format_http_bytes, name->base, name->len, format_http_bytes, value->base, value->len); - return HTTP2_ERROR_NO_ERROR; + return HPACK_ERROR_NONE; } } -__clib_export http2_error_t +__clib_export hpack_error_t hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, - u32 *name_len, u32 *value_len, hpack_dynamic_table_t *dt) + u32 *name_len, u32 *value_len, void *decoder_ctx) { + hpack_dynamic_table_t *dt = (hpack_dynamic_table_t *) decoder_ctx; u8 *p; u8 value_is_indexed = 0, add_new_entry = 0; uword old_len, new_max, index = 0; http_token_t name, value; - http2_error_t rv; + hpack_error_t rv; ASSERT (*src < end); p = *src; @@ -568,7 +335,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, if (p == end || new_max > (uword) dt->max_size) { HTTP_DBG (1, "invalid dynamic table size update"); - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; } while (clib_ring_n_enq (dt->entries) && new_max > dt->used) hpack_dynamic_table_evict_one (dt); @@ -582,7 +349,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, if (index == 0 || index == HPACK_INVALID_INT) { HTTP_DBG (1, "invalid index"); - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; } value_is_indexed = 1; } @@ -593,7 +360,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, if (index == 0 || index == HPACK_INVALID_INT) { HTTP_DBG (1, "invalid index"); - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; } add_new_entry = 1; } @@ -613,7 +380,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, if (index == 0 || index == HPACK_INVALID_INT) { HTTP_DBG (1, "invalid index"); - return HTTP2_ERROR_COMPRESSION_ERROR; + return HPACK_ERROR_COMPRESSION; } } } @@ -621,7 +388,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, if (index) { rv = hpack_get_table_entry (index, &name, &value, value_is_indexed, dt); - if (rv != HTTP2_ERROR_NO_ERROR) + if (rv) { HTTP_DBG (1, "entry index %llu error", index); return rv; @@ -629,7 +396,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, if (name.len > *buf_len) { HTTP_DBG (1, "not enough space"); - return HTTP2_ERROR_INTERNAL_ERROR; + return HPACK_ERROR_UNKNOWN; } clib_memcpy (*buf, name.base, name.len); *buf_len -= name.len; @@ -640,7 +407,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, if (value.len > *buf_len) { HTTP_DBG (1, "not enough space"); - return HTTP2_ERROR_INTERNAL_ERROR; + return HPACK_ERROR_UNKNOWN; } clib_memcpy (*buf, value.base, value.len); *buf_len -= value.len; @@ -653,7 +420,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, old_len = *buf_len; name.base = (char *) *buf; rv = hpack_decode_string (&p, end, buf, buf_len); - if (rv != HTTP2_ERROR_NO_ERROR) + if (rv) { HTTP_DBG (1, "invalid header name"); return rv; @@ -667,7 +434,7 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, old_len = *buf_len; value.base = (char *) *buf; rv = hpack_decode_string (&p, end, buf, buf_len); - if (rv != HTTP2_ERROR_NO_ERROR) + if (rv) { HTTP_DBG (1, "invalid header value"); return rv; @@ -680,290 +447,15 @@ hpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, hpack_dynamic_table_add (dt, &name, &value); *src = p; - return HTTP2_ERROR_NO_ERROR; + return HPACK_ERROR_NONE; } -static inline u8 -hpack_header_name_is_valid (u8 *name, u32 name_len) -{ - u32 i; - static uword tchar[4] = { - /* !#$%'*+-.0123456789 */ - 0x03ff6cba00000000, - /* ^_`abcdefghijklmnopqrstuvwxyz|~ */ - 0x57ffffffc0000000, - 0x0000000000000000, - 0x0000000000000000, - }; - for (i = 0; i < name_len; i++) - { - if (!clib_bitmap_get_no_check (tchar, name[i])) - return 0; - } - return 1; -} - -static inline u8 -hpack_header_value_is_valid (u8 *value, u32 value_len) -{ - u32 i; - /* VCHAR / SP / HTAB / %x80-FF */ - static uword tchar[4] = { - 0xffffffff00000200, - 0x7fffffffffffffff, - 0xffffffffffffffff, - 0xffffffffffffffff, - }; - - if (value_len == 0) - return 1; - - /* must not start or end with SP or HTAB */ - if ((value[0] == 0x20 || value[0] == 0x09 || value[value_len - 1] == 0x20 || - value[value_len - 1] == 0x09)) - return 0; - - for (i = 0; i < value_len; i++) - { - if (!clib_bitmap_get_no_check (tchar, value[i])) - return 0; - } - return 1; -} - -static inline http_req_method_t -hpack_parse_method (u8 *value, u32 value_len) -{ - switch (value_len) - { - case 3: - if (!memcmp (value, "GET", 3)) - return HTTP_REQ_GET; - break; - case 4: - if (!memcmp (value, "POST", 4)) - return HTTP_REQ_POST; - break; - case 7: - if (!memcmp (value, "CONNECT", 7)) - return HTTP_REQ_CONNECT; - break; - default: - break; - } - /* HPACK should return only connection errors, this one is stream error */ - return HTTP_REQ_UNKNOWN; -} - -static inline http_url_scheme_t -hpack_parse_scheme (u8 *value, u32 value_len) -{ - switch (value_len) - { - case 4: - if (!memcmp (value, "http", 4)) - return HTTP_URL_SCHEME_HTTP; - break; - case 5: - if (!memcmp (value, "https", 5)) - return HTTP_URL_SCHEME_HTTPS; - break; - default: - break; - } - /* HPACK should return only connection errors, this one is stream error */ - return HTTP_URL_SCHEME_UNKNOWN; -} - -static inline int -hpack_parse_status_code (u8 *value, u32 value_len, http_status_code_t *sc) -{ - u16 status_code = 0; - u8 *p; - - if (value_len != 3) - return HTTP2_ERROR_PROTOCOL_ERROR; - - p = value; - parse_int (status_code, 100); - parse_int (status_code, 10); - parse_int (status_code, 1); - if (status_code < 100 || status_code > 599) - { - HTTP_DBG (1, "invalid status code %d", status_code); - return -1; - } - HTTP_DBG (1, "status code: %d", status_code); - *sc = http_sc_by_u16 (status_code); - - return 0; -} - -static http2_error_t -hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, - u32 value_len, - hpack_request_control_data_t *control_data) -{ - HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, format_http_bytes, - value, value_len); - switch (name_len) - { - case 5: - if (!memcmp (name + 1, "path", 4)) - { - if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED || - value_len == 0) - return HTTP2_ERROR_PROTOCOL_ERROR; - control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; - control_data->path = value; - control_data->path_len = value_len; - break; - } - return HTTP2_ERROR_PROTOCOL_ERROR; - case 7: - switch (name[1]) - { - case 'm': - if (!memcmp (name + 2, "ethod", 5)) - { - if (control_data->parsed_bitmap & - HPACK_PSEUDO_HEADER_METHOD_PARSED) - return HTTP2_ERROR_PROTOCOL_ERROR; - control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_METHOD_PARSED; - control_data->method = hpack_parse_method (value, value_len); - break; - } - return HTTP2_ERROR_PROTOCOL_ERROR; - case 's': - if (!memcmp (name + 2, "cheme", 5)) - { - if (control_data->parsed_bitmap & - HPACK_PSEUDO_HEADER_SCHEME_PARSED) - return HTTP2_ERROR_PROTOCOL_ERROR; - control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_SCHEME_PARSED; - control_data->scheme = hpack_parse_scheme (value, value_len); - break; - } - return HTTP2_ERROR_PROTOCOL_ERROR; - default: - return HTTP2_ERROR_PROTOCOL_ERROR; - } - break; - case 9: - if (!memcmp (name + 1, "protocol", 8)) - { - if (control_data->parsed_bitmap & - HPACK_PSEUDO_HEADER_PROTOCOL_PARSED) - return HTTP2_ERROR_PROTOCOL_ERROR; - control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_PROTOCOL_PARSED; - control_data->protocol = value; - control_data->protocol_len = value_len; - break; - } - break; - case 10: - if (!memcmp (name + 1, "authority", 9)) - { - if (control_data->parsed_bitmap & - HPACK_PSEUDO_HEADER_AUTHORITY_PARSED) - return HTTP2_ERROR_PROTOCOL_ERROR; - control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; - control_data->authority = value; - control_data->authority_len = value_len; - break; - } - return HTTP2_ERROR_PROTOCOL_ERROR; - default: - return HTTP2_ERROR_PROTOCOL_ERROR; - } - - return HTTP2_ERROR_NO_ERROR; -} - -static http2_error_t -hpack_parse_resp_pseudo_header (u8 *name, u32 name_len, u8 *value, - u32 value_len, - hpack_response_control_data_t *control_data) -{ - HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, format_http_bytes, - value, value_len); - switch (name_len) - { - case 7: - if (!memcmp (name + 1, "status", 6)) - { - if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_STATUS_PARSED) - return HTTP2_ERROR_PROTOCOL_ERROR; - control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_STATUS_PARSED; - if (hpack_parse_status_code (value, value_len, &control_data->sc)) - return HTTP2_ERROR_PROTOCOL_ERROR; - } - break; - default: - return HTTP2_ERROR_PROTOCOL_ERROR; - } - - return HTTP2_ERROR_NO_ERROR; -} - -/* Special treatment for headers like: - * - * RFC9113 8.2.2: any message containing connection-specific header - * fields MUST be treated as malformed (connection, upgrade, keep-alive, - * proxy-connection, transfer-encoding), TE header MUST NOT contain any value - * other than "trailers" - * - * find headers that will be used later in preprocessing (content-length) - */ -always_inline http2_error_t -hpack_preprocess_header (u8 *name, u32 name_len, u8 *value, u32 value_len, - uword index, uword *content_len_header_index) -{ - switch (name_len) - { - case 2: - if (name[0] == 't' && name[1] == 'e' && - !http_token_is_case ((const char *) value, value_len, - http_token_lit ("trailers"))) - return HTTP2_ERROR_PROTOCOL_ERROR; - break; - case 7: - if (!memcmp (name, "upgrade", 7)) - return HTTP2_ERROR_PROTOCOL_ERROR; - break; - case 10: - switch (name[0]) - { - case 'c': - if (!memcmp (name + 1, "onnection", 9)) - return HTTP2_ERROR_PROTOCOL_ERROR; - break; - case 'k': - if (!memcmp (name + 1, "eep-alive", 9)) - return HTTP2_ERROR_PROTOCOL_ERROR; - break; - default: - break; - } - break; - case 14: - if (!memcmp (name, "content-length", 14) && - *content_len_header_index == ~0) - *content_len_header_index = index; - break; - case 16: - if (!memcmp (name, "proxy-connection", 16)) - return HTTP2_ERROR_PROTOCOL_ERROR; - break; - case 17: - if (!memcmp (name, "transfer-encoding", 17)) - return HTTP2_ERROR_PROTOCOL_ERROR; - break; - default: - break; - } - return HTTP2_ERROR_NO_ERROR; -} +static const http2_error_t hpack_error_to_http2_error[] = { + [HPACK_ERROR_NONE] = HTTP2_ERROR_NO_ERROR, + [HPACK_ERROR_COMPRESSION] = HTTP2_ERROR_COMPRESSION_ERROR, + [HPACK_ERROR_PROTOCOL] = HTTP2_ERROR_PROTOCOL_ERROR, + [HPACK_ERROR_UNKNOWN] = HTTP2_ERROR_INTERNAL_ERROR, +}; __clib_export http2_error_t hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, @@ -971,88 +463,12 @@ hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len, http_field_line_t **headers, hpack_dynamic_table_t *dynamic_table) { - u8 *p, *end, *b, *name, *value; - u8 regular_header_parsed = 0; - u32 name_len, value_len; - uword b_left; - http_field_line_t *header; - http2_error_t rv; - - p = src; - end = src + src_len; - b = dst; - b_left = dst_len; - control_data->parsed_bitmap = 0; - control_data->headers_len = 0; - control_data->content_len_header_index = ~0; - - while (p != end) - { - name = b; - rv = hpack_decode_header (&p, end, &b, &b_left, &name_len, &value_len, - dynamic_table); - if (rv != HTTP2_ERROR_NO_ERROR) - { - HTTP_DBG (1, "hpack_decode_header: %U", format_http2_error, rv); - return rv; - } - value = name + name_len; - - /* pseudo header */ - if (name[0] == ':') - { - /* all pseudo-headers must be before regular headers */ - if (regular_header_parsed) - { - HTTP_DBG (1, "pseudo-headers after regular header"); - return HTTP2_ERROR_PROTOCOL_ERROR; - } - rv = hpack_parse_req_pseudo_header (name, name_len, value, value_len, - control_data); - if (rv != HTTP2_ERROR_NO_ERROR) - { - HTTP_DBG (1, "hpack_parse_req_pseudo_header: %U", - format_http2_error, rv); - return rv; - } - continue; - } - else - { - if (!hpack_header_name_is_valid (name, name_len)) - return HTTP2_ERROR_PROTOCOL_ERROR; - if (!regular_header_parsed) - { - regular_header_parsed = 1; - control_data->headers = name; - } - } - if (!hpack_header_value_is_valid (value, value_len)) - return HTTP2_ERROR_PROTOCOL_ERROR; - vec_add2 (*headers, header, 1); - HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, - format_http_bytes, value, value_len); - header->name_offset = name - control_data->headers; - header->name_len = name_len; - header->value_offset = value - control_data->headers; - header->value_len = value_len; - control_data->headers_len += name_len; - control_data->headers_len += value_len; - if (regular_header_parsed) - { - rv = hpack_preprocess_header ( - name, name_len, value, value_len, header - *headers, - &control_data->content_len_header_index); - if (rv != HTTP2_ERROR_NO_ERROR) - { - HTTP_DBG (1, "connection-specific header present"); - return rv; - } - } - } - control_data->control_data_len = dst_len - b_left; - HTTP_DBG (2, "%U", format_hpack_dynamic_table, dynamic_table); - return HTTP2_ERROR_NO_ERROR; + hpack_error_t rv; + rv = hpack_decode_request (src, src + src_len, dst, dst_len, control_data, + headers, (void *) dynamic_table, + hpack_decode_header); + HTTP_DBG (3, "%U", format_hpack_dynamic_table, dynamic_table); + return hpack_error_to_http2_error[rv]; } __clib_export http2_error_t @@ -1061,88 +477,12 @@ hpack_parse_response (u8 *src, u32 src_len, u8 *dst, u32 dst_len, http_field_line_t **headers, hpack_dynamic_table_t *dynamic_table) { - u8 *p, *end, *b, *name, *value; - u8 regular_header_parsed = 0; - u32 name_len, value_len; - uword b_left; - http_field_line_t *header; - http2_error_t rv; - - p = src; - end = src + src_len; - b = dst; - b_left = dst_len; - control_data->parsed_bitmap = 0; - control_data->headers_len = 0; - control_data->content_len_header_index = ~0; - - while (p != end) - { - name = b; - rv = hpack_decode_header (&p, end, &b, &b_left, &name_len, &value_len, - dynamic_table); - if (rv != HTTP2_ERROR_NO_ERROR) - { - HTTP_DBG (1, "hpack_decode_header: %U", format_http2_error, rv); - return rv; - } - value = name + name_len; - - /* pseudo header */ - if (name[0] == ':') - { - /* all pseudo-headers must be before regular headers */ - if (regular_header_parsed) - { - HTTP_DBG (1, "pseudo-headers after regular header"); - return HTTP2_ERROR_PROTOCOL_ERROR; - } - rv = hpack_parse_resp_pseudo_header (name, name_len, value, - value_len, control_data); - if (rv != HTTP2_ERROR_NO_ERROR) - { - HTTP_DBG (1, "hpack_parse_resp_pseudo_header: %U", - format_http2_error, rv); - return rv; - } - continue; - } - else - { - if (!hpack_header_name_is_valid (name, name_len)) - return HTTP2_ERROR_PROTOCOL_ERROR; - if (!regular_header_parsed) - { - regular_header_parsed = 1; - control_data->headers = name; - } - } - if (!hpack_header_value_is_valid (value, value_len)) - return HTTP2_ERROR_PROTOCOL_ERROR; - vec_add2 (*headers, header, 1); - HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, - format_http_bytes, value, value_len); - header->name_offset = name - control_data->headers; - header->name_len = name_len; - header->value_offset = value - control_data->headers; - header->value_len = value_len; - control_data->headers_len += name_len; - control_data->headers_len += value_len; - if (regular_header_parsed) - { - rv = hpack_preprocess_header ( - name, name_len, value, value_len, header - *headers, - &control_data->content_len_header_index); - if (rv != HTTP2_ERROR_NO_ERROR) - { - HTTP_DBG (1, "connection-specific header present"); - return rv; - } - } - } - control_data->control_data_len = dst_len - b_left; - HTTP_DBG (2, "%U", format_hpack_dynamic_table, dynamic_table); - return HTTP2_ERROR_NO_ERROR; + hpack_error_t rv; + rv = hpack_decode_response (src, src + src_len, dst, dst_len, control_data, + headers, (void *) dynamic_table, + hpack_decode_header); + HTTP_DBG (3, "%U", format_hpack_dynamic_table, dynamic_table); + return hpack_error_to_http2_error[rv]; } static inline u8 * diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h index 72b9f2b4bee..2a84c555048 100644 --- a/src/plugins/http/http2/hpack.h +++ b/src/plugins/http/http2/hpack.h @@ -80,69 +80,6 @@ typedef struct u32 control_data_len; } hpack_response_control_data_t; -/** - * Decode unsigned variable-length integer (RFC7541 section 5.1) - * - * @param src Pointer to source buffer which will be advanced - * @param end End of the source buffer - * @param prefix_len Number of bits of the prefix (between 1 and 8) - * - * @return Decoded integer or @c HPACK_INVALID_INT in case of error - */ -uword hpack_decode_int (u8 **src, u8 *end, u8 prefix_len); - -/** - * Encode given value as unsigned variable-length integer (RFC7541 section 5.1) - * - * @param dst Pointer to destination buffer, should have enough space - * @param value Integer value to encode (up to @c CLIB_WORD_MAX) - * @param prefix_len Number of bits of the prefix (between 1 and 8) - * - * @return Advanced pointer to the destination buffer - * - * @note Encoded integer will take maximum @c HPACK_ENCODED_INT_MAX_LEN bytes - */ -u8 *hpack_encode_int (u8 *dst, uword value, u8 prefix_len); - -/** - * Decode - * - * @param src Pointer to source buffer which will be advanced - * @param end End of the source buffer - * @param buf Pointer to the buffer where string is decoded which will be - * advanced by number of written bytes - * @param buf_len Length the buffer, will be decreased - * - * @return @c HTTP2_ERROR_NO_ERROR on success - * - * @note Caller is responsible to check if there is somthing left in source - * buffer first - */ -http2_error_t hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, - uword *buf_len); - -/** - * Encode given string in Huffman codes. - * - * @param dst Pointer to destination buffer, should have enough space - * @param value String to encode - * @param value_len Length of the string - * - * @return Advanced pointer to the destination buffer - */ -u8 *hpack_encode_huffman (u8 *dst, const u8 *value, uword value_len); - -/** - * Number of bytes required to encode given string in Huffman codes - * - * @param value Pointer to buffer with string to encode - * @param value_len Length of the string - * - * @return number of bytes required to encode string in Huffman codes, round up - * to byte boundary - */ -uword hpack_huffman_encoded_len (const u8 *value, uword value_len); - /** * Initialize HPACK dynamic table * diff --git a/src/plugins/http/http2/hpack_inlines.h b/src/plugins/http/http2/hpack_inlines.h new file mode 100644 index 00000000000..50a0825e777 --- /dev/null +++ b/src/plugins/http/http2/hpack_inlines.h @@ -0,0 +1,765 @@ +/* SPDX-License-Identifier: Apache-2.0 + * Copyright(c) 2025 Cisco Systems, Inc. + */ + +#ifndef SRC_PLUGINS_HTTP_HPACK_INLINES_H_ +#define SRC_PLUGINS_HTTP_HPACK_INLINES_H_ + +#include +#include +#include +#include + +typedef enum +{ + HPACK_ERROR_NONE, + HPACK_ERROR_COMPRESSION, + HPACK_ERROR_PROTOCOL, + HPACK_ERROR_UNKNOWN, +} hpack_error_t; + +typedef hpack_error_t (hpack_header_decoder_fn) (u8 **src, u8 *end, u8 **buf, + uword *buf_len, u32 *name_len, + u32 *value_len, + void *decoder_ctx); + +/** + * Decode unsigned variable-length integer (RFC7541 section 5.1) + * + * @param src Pointer to source buffer which will be advanced + * @param end End of the source buffer + * @param prefix_len Number of bits of the prefix (between 1 and 8) + * + * @return Decoded integer or @c HPACK_INVALID_INT in case of error + */ +always_inline uword +hpack_decode_int (u8 **src, u8 *end, u8 prefix_len) +{ + uword value, new_value; + u8 *p, shift = 0, byte; + u16 prefix_max; + + ASSERT (*src < end); + ASSERT (prefix_len >= 1 && prefix_len <= 8); + + p = *src; + prefix_max = (1 << prefix_len) - 1; + value = *p & (u8) prefix_max; + p++; + /* if integer value is less than 2^prefix_len-1 it's encoded within prefix */ + if (value != prefix_max) + { + *src = p; + return value; + } + + while (p != end) + { + byte = *p; + p++; + new_value = value + ((uword) (byte & 0x7F) << shift); + shift += 7; + /* check for overflow */ + if (new_value < value) + return HPACK_INVALID_INT; + value = new_value; + /* MSB of the last byte is zero */ + if ((byte & 0x80) == 0) + { + *src = p; + return value; + } + } + + return HPACK_INVALID_INT; +} + +/** + * Encode given value as unsigned variable-length integer (RFC7541 section 5.1) + * + * @param dst Pointer to destination buffer, should have enough space + * @param value Integer value to encode (up to @c CLIB_WORD_MAX) + * @param prefix_len Number of bits of the prefix (between 1 and 8) + * + * @return Advanced pointer to the destination buffer + * + * @note Encoded integer will take maximum @c HPACK_ENCODED_INT_MAX_LEN bytes + */ +always_inline u8 * +hpack_encode_int (u8 *dst, uword value, u8 prefix_len) +{ + u16 prefix_max; + + ASSERT (prefix_len >= 1 && prefix_len <= 8); + + prefix_max = (1 << prefix_len) - 1; + + /* if integer value is less than 2^prefix_len-1 it's encoded within prefix */ + if (value < prefix_max) + { + *dst++ |= (u8) value; + return dst; + } + + /* otherwise all bits of the prefix are set to 1 */ + *dst++ |= (u8) prefix_max; + /* and the value is decreased by 2^prefix_len-1 */ + value -= prefix_max; + /* MSB of each byte is used as continuation flag */ + for (; value >= 0x80; value >>= 7) + *dst++ = 0x80 | (value & 0x7F); + /* except for the last byte */ + *dst++ = (u8) value; + + return dst; +} + +/** + * Decode + * + * @param src Pointer to source buffer which will be advanced + * @param end End of the source buffer + * @param buf Pointer to the buffer where string is decoded which will be + * advanced by number of written bytes + * @param buf_len Length the buffer, will be decreased + * + * @return @c HPACK_ERROR_NONE on success + * + * @note Caller is responsible to check if there is somthing left in source + * buffer first + */ +always_inline hpack_error_t +hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len) +{ + u64 accumulator = 0; + u8 accumulator_len = 0; + u8 *p; + hpack_huffman_code_t *code; + + p = *src; + while (1) + { + /* out of space? */ + if (*buf_len == 0) + return HPACK_ERROR_UNKNOWN; + /* refill */ + while (p < end && accumulator_len <= 56) + { + accumulator <<= 8; + accumulator_len += 8; + accumulator |= (u64) *p++; + } + /* first try short codes (5 - 8 bits) */ + code = + &huff_code_table_fast[(u8) (accumulator >> (accumulator_len - 8))]; + /* zero code length mean no luck */ + if (PREDICT_TRUE (code->code_len)) + { + **buf = code->symbol; + (*buf)++; + (*buf_len)--; + accumulator_len -= code->code_len; + } + else + { + /* slow path / long codes (10 - 30 bits) */ + u32 tmp; + /* group boundaries are aligned to 32 bits */ + if (accumulator_len < 32) + tmp = accumulator << (32 - accumulator_len); + else + tmp = accumulator >> (accumulator_len - 32); + /* figure out which interval code falls into, this is possible + * because HPACK use canonical Huffman codes + * see Schwartz, E. and B. Kallick, “Generating a canonical prefix + * encoding” + */ + hpack_huffman_group_t *hg = hpack_huffman_get_group (tmp); + /* this might happen with invalid EOS (longer than 7 bits) */ + if (hg->code_len > accumulator_len) + return HPACK_ERROR_COMPRESSION; + /* trim code to correct length */ + u32 code = (accumulator >> (accumulator_len - hg->code_len)) & + ((1 << hg->code_len) - 1); + if (!code) + return HPACK_ERROR_COMPRESSION; + /* find symbol in the list */ + **buf = hg->symbols[code - hg->first_code]; + (*buf)++; + (*buf_len)--; + accumulator_len -= hg->code_len; + } + /* all done */ + if (p == end && accumulator_len < 8) + { + /* there might be one more symbol encoded with short code */ + if (accumulator_len >= 5) + { + /* first check EOS case */ + if (((1 << accumulator_len) - 1) == + (accumulator & ((1 << accumulator_len) - 1))) + break; + + /* out of space? */ + if (*buf_len == 0) + return HPACK_ERROR_UNKNOWN; + + /* if bogus EOF check bellow will fail */ + code = &huff_code_table_fast[(u8) (accumulator + << (8 - accumulator_len))]; + **buf = code->symbol; + (*buf)++; + (*buf_len)--; + accumulator_len -= code->code_len; + /* end at byte boundary? */ + if (accumulator_len == 0) + break; + } + /* we must end with EOS here */ + if (((1 << accumulator_len) - 1) != + (accumulator & ((1 << accumulator_len) - 1))) + return HPACK_ERROR_COMPRESSION; + break; + } + } + return HPACK_ERROR_NONE; +} + +/** + * Number of bytes required to encode given string in Huffman codes + * + * @param value Pointer to buffer with string to encode + * @param value_len Length of the string + * + * @return number of bytes required to encode string in Huffman codes, round up + * to byte boundary + */ +always_inline uword +hpack_huffman_encoded_len (const u8 *value, uword value_len) +{ + uword len = 0; + u8 *end; + hpack_huffman_symbol_t *sym; + + end = (u8 *) value + value_len; + while (value != end) + { + sym = &huff_sym_table[*value++]; + len += sym->code_len; + } + /* round up to byte boundary */ + return (len + 7) / 8; +} + +/** + * Encode given string in Huffman codes. + * + * @param dst Pointer to destination buffer, should have enough space + * @param value String to encode + * @param value_len Length of the string + * + * @return Advanced pointer to the destination buffer + */ +always_inline u8 * +hpack_encode_huffman (u8 *dst, const u8 *value, uword value_len) +{ + u8 *end; + hpack_huffman_symbol_t *sym; + u8 accumulator_len = 40; /* leftover (1 byte) + max code_len (4 bytes) */ + u64 accumulator = 0; /* to fit leftover and current code */ + + end = (u8 *) value + value_len; + + while (value != end) + { + sym = &huff_sym_table[*value++]; + /* add current code to leftover of previous one */ + accumulator |= (u64) sym->code << (accumulator_len - sym->code_len); + accumulator_len -= sym->code_len; + /* write only fully occupied bytes (max 4) */ + switch (accumulator_len) + { + case 1 ... 8: +#define WRITE_BYTE() \ + *dst = (u8) (accumulator >> 32); \ + accumulator_len += 8; \ + accumulator <<= 8; \ + dst++; + WRITE_BYTE (); + case 9 ... 16: + WRITE_BYTE (); + case 17 ... 24: + WRITE_BYTE (); + case 25 ... 32: + WRITE_BYTE (); + default: + break; + } + } + + /* padding (0-7 bits)*/ + ASSERT (accumulator_len > 32 && accumulator_len <= 40); + if (accumulator_len != 40) + { + accumulator |= (u64) 0x7F << (accumulator_len - 7); + *dst = (u8) (accumulator >> 32); + dst++; + } + return dst; +} + +always_inline u8 +hpack_header_name_is_valid (u8 *name, u32 name_len) +{ + u32 i; + static uword tchar[4] = { + /* !#$%'*+-.0123456789 */ + 0x03ff6cba00000000, + /* ^_`abcdefghijklmnopqrstuvwxyz|~ */ + 0x57ffffffc0000000, + 0x0000000000000000, + 0x0000000000000000, + }; + for (i = 0; i < name_len; i++) + { + if (!clib_bitmap_get_no_check (tchar, name[i])) + return 0; + } + return 1; +} + +always_inline u8 +hpack_header_value_is_valid (u8 *value, u32 value_len) +{ + u32 i; + /* VCHAR / SP / HTAB / %x80-FF */ + static uword tchar[4] = { + 0xffffffff00000200, + 0x7fffffffffffffff, + 0xffffffffffffffff, + 0xffffffffffffffff, + }; + + if (value_len == 0) + return 1; + + /* must not start or end with SP or HTAB */ + if ((value[0] == 0x20 || value[0] == 0x09 || value[value_len - 1] == 0x20 || + value[value_len - 1] == 0x09)) + return 0; + + for (i = 0; i < value_len; i++) + { + if (!clib_bitmap_get_no_check (tchar, value[i])) + return 0; + } + return 1; +} + +always_inline http_req_method_t +hpack_parse_method (u8 *value, u32 value_len) +{ + switch (value_len) + { + case 3: + if (!memcmp (value, "GET", 3)) + return HTTP_REQ_GET; + break; + case 4: + if (!memcmp (value, "POST", 4)) + return HTTP_REQ_POST; + break; + case 7: + if (!memcmp (value, "CONNECT", 7)) + return HTTP_REQ_CONNECT; + break; + default: + break; + } + /* HPACK should return only connection errors, this one is stream error */ + return HTTP_REQ_UNKNOWN; +} + +always_inline http_url_scheme_t +hpack_parse_scheme (u8 *value, u32 value_len) +{ + switch (value_len) + { + case 4: + if (!memcmp (value, "http", 4)) + return HTTP_URL_SCHEME_HTTP; + break; + case 5: + if (!memcmp (value, "https", 5)) + return HTTP_URL_SCHEME_HTTPS; + break; + default: + break; + } + /* HPACK should return only connection errors, this one is stream error */ + return HTTP_URL_SCHEME_UNKNOWN; +} + +always_inline hpack_error_t +hpack_parse_status_code (u8 *value, u32 value_len, http_status_code_t *sc) +{ + u16 status_code = 0; + u8 *p; + + if (value_len != 3) + return HPACK_ERROR_PROTOCOL; + + p = value; + parse_int (status_code, 100); + parse_int (status_code, 10); + parse_int (status_code, 1); + if (status_code < 100 || status_code > 599) + { + HTTP_DBG (1, "invalid status code %d", status_code); + return HPACK_ERROR_PROTOCOL; + } + HTTP_DBG (1, "status code: %d", status_code); + *sc = http_sc_by_u16 (status_code); + + return HPACK_ERROR_NONE; +} + +always_inline hpack_error_t +hpack_parse_req_pseudo_header (u8 *name, u32 name_len, u8 *value, + u32 value_len, + hpack_request_control_data_t *control_data) +{ + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, format_http_bytes, + value, value_len); + switch (name_len) + { + case 5: + if (!memcmp (name + 1, "path", 4)) + { + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_PATH_PARSED || + value_len == 0) + return HPACK_ERROR_PROTOCOL; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_PATH_PARSED; + control_data->path = value; + control_data->path_len = value_len; + break; + } + return HPACK_ERROR_PROTOCOL; + case 7: + switch (name[1]) + { + case 'm': + if (!memcmp (name + 2, "ethod", 5)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_METHOD_PARSED) + return HPACK_ERROR_PROTOCOL; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_METHOD_PARSED; + control_data->method = hpack_parse_method (value, value_len); + break; + } + return HPACK_ERROR_PROTOCOL; + case 's': + if (!memcmp (name + 2, "cheme", 5)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_SCHEME_PARSED) + return HPACK_ERROR_PROTOCOL; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_SCHEME_PARSED; + control_data->scheme = hpack_parse_scheme (value, value_len); + break; + } + return HPACK_ERROR_PROTOCOL; + default: + return HPACK_ERROR_PROTOCOL; + } + break; + case 9: + if (!memcmp (name + 1, "protocol", 8)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_PROTOCOL_PARSED) + return HPACK_ERROR_PROTOCOL; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_PROTOCOL_PARSED; + control_data->protocol = value; + control_data->protocol_len = value_len; + break; + } + break; + case 10: + if (!memcmp (name + 1, "authority", 9)) + { + if (control_data->parsed_bitmap & + HPACK_PSEUDO_HEADER_AUTHORITY_PARSED) + return HPACK_ERROR_PROTOCOL; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_AUTHORITY_PARSED; + control_data->authority = value; + control_data->authority_len = value_len; + break; + } + return HPACK_ERROR_PROTOCOL; + default: + return HPACK_ERROR_PROTOCOL; + } + + return HPACK_ERROR_NONE; +} + +always_inline hpack_error_t +hpack_parse_resp_pseudo_header (u8 *name, u32 name_len, u8 *value, + u32 value_len, + hpack_response_control_data_t *control_data) +{ + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, format_http_bytes, + value, value_len); + switch (name_len) + { + case 7: + if (!memcmp (name + 1, "status", 6)) + { + if (control_data->parsed_bitmap & HPACK_PSEUDO_HEADER_STATUS_PARSED) + return HPACK_ERROR_PROTOCOL; + control_data->parsed_bitmap |= HPACK_PSEUDO_HEADER_STATUS_PARSED; + return hpack_parse_status_code (value, value_len, &control_data->sc); + } + break; + default: + return HPACK_ERROR_PROTOCOL; + } + return HPACK_ERROR_NONE; +} + +/* Special treatment for headers like: + * + * RFC9113 8.2.2: any message containing connection-specific header + * fields MUST be treated as malformed (connection, upgrade, keep-alive, + * proxy-connection, transfer-encoding), TE header MUST NOT contain any value + * other than "trailers" + * + * find headers that will be used later in preprocessing (content-length) + */ +always_inline hpack_error_t +hpack_preprocess_header (u8 *name, u32 name_len, u8 *value, u32 value_len, + uword index, uword *content_len_header_index) +{ + switch (name_len) + { + case 2: + if (name[0] == 't' && name[1] == 'e' && + !http_token_is_case ((const char *) value, value_len, + http_token_lit ("trailers"))) + return HPACK_ERROR_PROTOCOL; + break; + case 7: + if (!memcmp (name, "upgrade", 7)) + return HPACK_ERROR_PROTOCOL; + break; + case 10: + switch (name[0]) + { + case 'c': + if (!memcmp (name + 1, "onnection", 9)) + return HPACK_ERROR_PROTOCOL; + break; + case 'k': + if (!memcmp (name + 1, "eep-alive", 9)) + return HPACK_ERROR_PROTOCOL; + break; + default: + break; + } + break; + case 14: + if (!memcmp (name, "content-length", 14) && + *content_len_header_index == ~0) + *content_len_header_index = index; + break; + case 16: + if (!memcmp (name, "proxy-connection", 16)) + return HPACK_ERROR_PROTOCOL; + break; + case 17: + if (!memcmp (name, "transfer-encoding", 17)) + return HPACK_ERROR_PROTOCOL; + break; + default: + break; + } + return HPACK_ERROR_NONE; +} + +always_inline hpack_error_t +hpack_decode_request (u8 *src, u8 *end, u8 *dst, u32 dst_len, + hpack_request_control_data_t *control_data, + http_field_line_t **headers, void *decoder_ctx, + hpack_header_decoder_fn *decoder_fn) +{ + u8 *p, *b, *name, *value; + u8 regular_header_parsed = 0; + u32 name_len, value_len; + uword b_left; + http_field_line_t *header; + hpack_error_t rv; + + p = src; + b = dst; + b_left = dst_len; + control_data->parsed_bitmap = 0; + control_data->headers_len = 0; + control_data->content_len_header_index = ~0; + + while (p != end) + { + name = b; + rv = + decoder_fn (&p, end, &b, &b_left, &name_len, &value_len, decoder_ctx); + if (rv) + { + HTTP_DBG (1, "decode_header: %d", rv); + return rv; + } + value = name + name_len; + + /* pseudo header */ + if (name[0] == ':') + { + /* all pseudo-headers must be before regular headers */ + if (regular_header_parsed) + { + HTTP_DBG (1, "pseudo-headers after regular header"); + return HPACK_ERROR_PROTOCOL; + } + rv = hpack_parse_req_pseudo_header (name, name_len, value, value_len, + control_data); + if (rv) + { + HTTP_DBG (1, "hpack_parse_req_pseudo_header: %d", rv); + return rv; + } + continue; + } + else + { + if (!hpack_header_name_is_valid (name, name_len)) + return HPACK_ERROR_PROTOCOL; + if (!regular_header_parsed) + { + regular_header_parsed = 1; + control_data->headers = name; + } + } + if (!hpack_header_value_is_valid (value, value_len)) + return HPACK_ERROR_PROTOCOL; + vec_add2 (*headers, header, 1); + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, + format_http_bytes, value, value_len); + header->name_offset = name - control_data->headers; + header->name_len = name_len; + header->value_offset = value - control_data->headers; + header->value_len = value_len; + control_data->headers_len += name_len; + control_data->headers_len += value_len; + if (regular_header_parsed) + { + rv = hpack_preprocess_header ( + name, name_len, value, value_len, header - *headers, + &control_data->content_len_header_index); + if (rv) + { + HTTP_DBG (1, "connection-specific header present"); + return rv; + } + } + } + control_data->control_data_len = dst_len - b_left; + return HPACK_ERROR_NONE; +} + +always_inline hpack_error_t +hpack_decode_response (u8 *src, u8 *end, u8 *dst, u32 dst_len, + hpack_response_control_data_t *control_data, + http_field_line_t **headers, void *decoder_ctx, + hpack_header_decoder_fn *decoder_fn) +{ + u8 *p, *b, *name, *value; + u8 regular_header_parsed = 0; + u32 name_len, value_len; + uword b_left; + http_field_line_t *header; + hpack_error_t rv; + + p = src; + b = dst; + b_left = dst_len; + control_data->parsed_bitmap = 0; + control_data->headers_len = 0; + control_data->content_len_header_index = ~0; + + while (p != end) + { + name = b; + rv = + decoder_fn (&p, end, &b, &b_left, &name_len, &value_len, decoder_ctx); + if (rv) + { + HTTP_DBG (1, "decode_header: %d", rv); + return rv; + } + value = name + name_len; + + /* pseudo header */ + if (name[0] == ':') + { + /* all pseudo-headers must be before regular headers */ + if (regular_header_parsed) + { + HTTP_DBG (1, "pseudo-headers after regular header"); + return HPACK_ERROR_PROTOCOL; + } + rv = hpack_parse_resp_pseudo_header (name, name_len, value, + value_len, control_data); + if (rv) + { + HTTP_DBG (1, "hpack_parse_resp_pseudo_header: %d", rv); + return rv; + } + continue; + } + else + { + if (!hpack_header_name_is_valid (name, name_len)) + return HPACK_ERROR_PROTOCOL; + if (!regular_header_parsed) + { + regular_header_parsed = 1; + control_data->headers = name; + } + } + if (!hpack_header_value_is_valid (value, value_len)) + return HPACK_ERROR_PROTOCOL; + vec_add2 (*headers, header, 1); + HTTP_DBG (2, "%U: %U", format_http_bytes, name, name_len, + format_http_bytes, value, value_len); + header->name_offset = name - control_data->headers; + header->name_len = name_len; + header->value_offset = value - control_data->headers; + header->value_len = value_len; + control_data->headers_len += name_len; + control_data->headers_len += value_len; + if (regular_header_parsed) + { + rv = hpack_preprocess_header ( + name, name_len, value, value_len, header - *headers, + &control_data->content_len_header_index); + if (rv) + { + HTTP_DBG (1, "connection-specific header present"); + return rv; + } + } + } + control_data->control_data_len = dst_len - b_left; + return HPACK_ERROR_NONE; +} + +#endif /* SRC_PLUGINS_HTTP_HPACK_INLINES_H_ */ diff --git a/src/plugins/http/http3/qpack.c b/src/plugins/http/http3/qpack.c index 6118c7b6547..ae7b4427e2d 100644 --- a/src/plugins/http/http3/qpack.c +++ b/src/plugins/http/http3/qpack.c @@ -3,7 +3,7 @@ */ #include -#include +#include typedef struct { @@ -130,12 +130,12 @@ static qpack_static_table_entry_t qpack_static_table[] = { STATIC_ASSERT (QPACK_STATIC_TABLE_SIZE == 99, "static table must have 99 entries"); -static http3_error_t +static hpack_error_t qpack_get_static_table_entry (uword index, http_token_t *name, http_token_t *value, u8 value_is_indexed) { if (index >= QPACK_STATIC_TABLE_SIZE) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; qpack_static_table_entry_t *e = &qpack_static_table[index]; name->base = e->name; @@ -143,25 +143,24 @@ qpack_get_static_table_entry (uword index, http_token_t *name, if (value_is_indexed) { if (e->value_len == 0) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; value->base = e->value; value->len = e->value_len; } - return HTTP3_ERROR_NO_ERROR; + return HPACK_ERROR_NONE; } -static http3_error_t +static hpack_error_t qpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len, u8 prefix_len) { u8 *p, is_huffman; uword len; - int rv; ASSERT (prefix_len >= 2 && prefix_len <= 8); if (*src == end) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; p = *src; /* first bit for H flag */ @@ -170,48 +169,46 @@ qpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len, /* length is integer with (N-1) bit prefix */ len = hpack_decode_int (&p, end, prefix_len - 1); if (PREDICT_FALSE (len == HPACK_INVALID_INT)) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; /* do we have everything? */ if (len > (end - p)) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; if (is_huffman) { *src = (p + len); - rv = hpack_decode_huffman (&p, p + len, buf, buf_len); - return rv ? HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED : - HTTP3_ERROR_NO_ERROR; + return hpack_decode_huffman (&p, p + len, buf, buf_len); } else { /* enough space? */ if (len > *buf_len) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_UNKNOWN; clib_memcpy (*buf, p, len); *buf_len -= len; *buf += len; *src = (p + len); - return HTTP3_ERROR_NO_ERROR; + return HPACK_ERROR_NONE; } } -__clib_export http3_error_t +__clib_export hpack_error_t qpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, u32 *name_len, u32 *value_len, void *decoder_ctx) { u8 *p; uword index, old_len; http_token_t name, value; - http3_error_t rv; + hpack_error_t rv; ASSERT (*src < end); p = *src; #define COPY_TOKEN(_token) \ if (_token.len > *buf_len) \ - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; \ + return HPACK_ERROR_COMPRESSION; \ clib_memcpy (*buf, _token.base, _token.len); \ *buf_len -= _token.len; \ *buf += _token.len; @@ -222,9 +219,9 @@ qpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, /* indexed field line, static table */ index = hpack_decode_int (&p, end, 6); if (index == HPACK_INVALID_INT) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; rv = qpack_get_static_table_entry (index, &name, &value, 1); - if (rv != HTTP3_ERROR_NO_ERROR) + if (rv) return rv; COPY_TOKEN (name); *name_len = name.len; @@ -233,53 +230,53 @@ qpack_decode_header (u8 **src, u8 *end, u8 **buf, uword *buf_len, break; case 8 ... 11: /* TODO: indexed field line, dynamic table */ - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; case 7: case 5: /* literal field line with name reference, static table */ index = hpack_decode_int (&p, end, 4); if (index == HPACK_INVALID_INT) - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; rv = qpack_get_static_table_entry (index, &name, &value, 0); - if (rv != HTTP3_ERROR_NO_ERROR) + if (rv) return rv; COPY_TOKEN (name); *name_len = name.len; old_len = *buf_len; rv = qpack_decode_string (&p, end, buf, buf_len, 8); - if (rv != HTTP3_ERROR_NO_ERROR) + if (rv) return rv; *value_len = old_len - *buf_len; break; case 6: case 4: /* TODO: literal field line with name reference, dynamic table */ - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; case 3: case 2: /* literal field line with literal name */ old_len = *buf_len; rv = qpack_decode_string (&p, end, buf, buf_len, 4); - if (rv != HTTP3_ERROR_NO_ERROR) + if (rv) return rv; *name_len = old_len - *buf_len; old_len = *buf_len; rv = qpack_decode_string (&p, end, buf, buf_len, 8); - if (rv != HTTP3_ERROR_NO_ERROR) + if (rv) return rv; *value_len = old_len - *buf_len; break; case 1: /* TODO: indexed field line with post-base index */ - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; case 0: /* TODO: literal field line with post-base name reference */ - return HTTP3_ERROR_QPACK_DECOMPRESSION_FAILED; + return HPACK_ERROR_COMPRESSION; default: ASSERT (0); break; } *src = p; - return HTTP3_ERROR_NO_ERROR; + return HPACK_ERROR_NONE; } diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index a4ecc7ac13f..406662dbe9a 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -846,10 +847,6 @@ http_test_hpack (vlib_main_t *vm) { vlib_cli_output (vm, "hpack_decode_int"); - static uword (*_hpack_decode_int) (u8 * *pos, u8 * end, u8 prefix_len); - _hpack_decode_int = - vlib_get_plugin_symbol ("http_plugin.so", "hpack_decode_int"); - u8 *pos, *end, *input = 0; uword value; #define TEST(i, pl, e) \ @@ -857,7 +854,7 @@ http_test_hpack (vlib_main_t *vm) memcpy (input, i, sizeof (i) - 1); \ pos = input; \ end = vec_end (input); \ - value = _hpack_decode_int (&pos, end, (u8) pl); \ + value = hpack_decode_int (&pos, end, (u8) pl); \ HTTP_TEST ((value == (uword) e && pos == end), \ "%U with prefix length %u is %llu", format_hex_bytes, input, \ vec_len (input), (u8) pl, value); \ @@ -880,7 +877,7 @@ http_test_hpack (vlib_main_t *vm) memcpy (input, i, sizeof (i) - 1); \ pos = input; \ end = vec_end (input); \ - value = _hpack_decode_int (&pos, end, (u8) pl); \ + value = hpack_decode_int (&pos, end, (u8) pl); \ HTTP_TEST ((value == HPACK_INVALID_INT), \ "%U with prefix length %u should be invalid", format_hex_bytes, \ input, vec_len (input), (u8) pl); \ @@ -897,16 +894,12 @@ http_test_hpack (vlib_main_t *vm) vlib_cli_output (vm, "hpack_encode_int"); - static u8 *(*_hpack_encode_int) (u8 * dst, uword value, u8 prefix_len); - _hpack_encode_int = - vlib_get_plugin_symbol ("http_plugin.so", "hpack_encode_int"); - u8 *buf = 0; u8 *p; #define TEST(v, pl, e) \ vec_validate_init_empty (buf, 15, 0); \ - p = _hpack_encode_int (buf, v, (u8) pl); \ + p = hpack_encode_int (buf, v, (u8) pl); \ HTTP_TEST (((p - buf) == (sizeof (e) - 1) && !memcmp (buf, e, p - buf)), \ "%llu with prefix length %u is encoded as %U", v, (u8) pl, \ format_hex_bytes, buf, p - buf); \ @@ -924,14 +917,14 @@ http_test_hpack (vlib_main_t *vm) vlib_cli_output (vm, "hpack_decode_string"); - static http2_error_t (*_hpack_decode_string) (u8 * *src, u8 * end, u8 * *buf, + static hpack_error_t (*_hpack_decode_string) (u8 * *src, u8 * end, u8 * *buf, uword * buf_len); _hpack_decode_string = vlib_get_plugin_symbol ("http_plugin.so", "hpack_decode_string"); u8 *bp; uword blen, len; - http2_error_t rv; + hpack_error_t rv; #define TEST(i, e) \ vec_validate (input, sizeof (i) - 2); \ @@ -944,7 +937,7 @@ http_test_hpack (vlib_main_t *vm) len = vec_len (buf) - blen; \ HTTP_TEST ((len == strlen (e) && !memcmp (buf, e, len) && \ pos == vec_end (input) && bp == buf + len && \ - rv == HTTP2_ERROR_NO_ERROR), \ + rv == HPACK_ERROR_NONE), \ "%U is decoded as %U", format_hex_bytes, input, vec_len (input), \ format_http_bytes, buf, len); \ vec_free (input); \ @@ -989,20 +982,20 @@ http_test_hpack (vlib_main_t *vm) vec_free (buf); /* incomplete */ - N_TEST ("\x87", HTTP2_ERROR_COMPRESSION_ERROR); - N_TEST ("\x07priv", HTTP2_ERROR_COMPRESSION_ERROR); + N_TEST ("\x87", HPACK_ERROR_COMPRESSION); + N_TEST ("\x07priv", HPACK_ERROR_COMPRESSION); /* invalid length */ - N_TEST ("\x7Fprivate", HTTP2_ERROR_COMPRESSION_ERROR); + N_TEST ("\x7Fprivate", HPACK_ERROR_COMPRESSION); /* invalid EOF */ - N_TEST ("\x81\x8C", HTTP2_ERROR_COMPRESSION_ERROR); + N_TEST ("\x81\x8C", HPACK_ERROR_COMPRESSION); N_TEST ("\x98\xDC\x53\xFF\xFF\xFF\xDF\xFF\xFF\xFF\x14\xFF\xFF\xFF\xF7\xFF" "\xFF\xFF\xC5\x3F\xFF\xFF\xFD\xFF\xFF", - HTTP2_ERROR_COMPRESSION_ERROR); + HPACK_ERROR_COMPRESSION); /* not enough space for decoding */ N_TEST ( "\x96\xD0\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04\x0B\x81\x66" "\xE0\x82\xA6\x2D\x1B\xFF", - HTTP2_ERROR_INTERNAL_ERROR); + HPACK_ERROR_UNKNOWN); #undef N_TEST vlib_cli_output (vm, "hpack_encode_string"); @@ -1043,7 +1036,7 @@ http_test_hpack (vlib_main_t *vm) vlib_cli_output (vm, "hpack_decode_header"); - static http2_error_t (*_hpack_decode_header) ( + static hpack_error_t (*_hpack_decode_header) ( u8 * *src, u8 * end, u8 * *buf, uword * buf_len, u32 * name_len, u32 * value_len, hpack_dynamic_table_t * dt); @@ -1076,7 +1069,7 @@ http_test_hpack (vlib_main_t *vm) rv = _hpack_decode_header (&pos, vec_end (input), &bp, &blen, &name_len, \ &value_len, &table); \ len = vec_len (buf) - blen; \ - HTTP_TEST ((rv == HTTP2_ERROR_NO_ERROR && table.used == dt_size && \ + HTTP_TEST ((rv == HPACK_ERROR_NONE && table.used == dt_size && \ name_len == strlen (e_name) && value_len == strlen (e_value) && \ !memcmp (buf, e_name, name_len) && \ !memcmp (buf + name_len, e_value, value_len) && \ @@ -1623,7 +1616,7 @@ http_test_qpack (vlib_main_t *vm) { vlib_cli_output (vm, "qpack_decode_header"); - static http3_error_t (*_qpack_decode_header) ( + static hpack_error_t (*_qpack_decode_header) ( u8 * *src, u8 * end, u8 * *buf, uword * buf_len, u32 * name_len, u32 * value_len); @@ -1633,7 +1626,7 @@ http_test_qpack (vlib_main_t *vm) u8 *pos, *bp, *buf = 0, *input = 0; uword blen; u32 name_len, value_len; - http3_error_t rv; + hpack_error_t rv; #define TEST(i, e_name, e_value) \ vec_validate (input, sizeof (i) - 2); \ @@ -1644,7 +1637,7 @@ http_test_qpack (vlib_main_t *vm) blen = vec_len (buf); \ rv = _qpack_decode_header (&pos, vec_end (input), &bp, &blen, &name_len, \ &value_len); \ - HTTP_TEST ((rv == HTTP3_ERROR_NO_ERROR && name_len == strlen (e_name) && \ + HTTP_TEST ((rv == HPACK_ERROR_NONE && name_len == strlen (e_name) && \ value_len == strlen (e_value) && \ !memcmp (buf, e_name, name_len) && \ !memcmp (buf + name_len, e_value, value_len) && \ -- 2.16.6