* Copyright(c) 2025 Cisco Systems, Inc.
*/
-#include <vppinfra/error.h>
#include <vppinfra/ring.h>
#include <http/http2/hpack.h>
-#include <http/http2/huffman_table.h>
#include <http/http_status_codes.h>
#include <http/http_private.h>
+#include <http/http2/hpack_inlines.h>
#define HPACK_STATIC_TABLE_SIZE 61
#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 */
/* 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)
{
{
/* 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 *
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)
{
}
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
{
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;
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;
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);
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;
}
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;
}
if (index == 0 || index == HPACK_INVALID_INT)
{
HTTP_DBG (1, "invalid index");
- return HTTP2_ERROR_COMPRESSION_ERROR;
+ return HPACK_ERROR_COMPRESSION;
}
}
}
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;
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;
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;
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;
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;
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,
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
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 *
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
*
--- /dev/null
+/* 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 <vppinfra/error.h>
+#include <http/http_private.h>
+#include <http/http2/hpack.h>
+#include <http/http2/huffman_table.h>
+
+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_ */
*/
#include <http/http3/qpack.h>
-#include <http/http2/hpack.h>
+#include <http/http2/hpack_inlines.h>
typedef struct
{
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;
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 */
/* 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;
/* 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;
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;
}
#include <vpp/app/version.h>
#include <http/http.h>
#include <http/http_header_names.h>
+#include <http/http2/hpack_inlines.h>
#include <http/http2/hpack.h>
#include <http/http2/frame.h>
#include <http/http3/qpack.h>
{
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) \
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); \
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); \
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); \
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); \
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); \
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");
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);
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) && \
{
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);
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); \
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) && \