aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatus Fabian <matfabia@cisco.com>2025-02-18 04:29:10 -0500
committerFlorin Coras <florin.coras@gmail.com>2025-02-25 17:47:07 +0000
commit58b6c4e6bdfba7c2a652e121c1c4e907df685780 (patch)
tree92636fd3b69277589ed1ebbb5621114cb22cbf1f
parent3979037173d4054bdff3be236cd33514c809ff85 (diff)
http: hpack headers decoding
1) parsing of binary format (RFC7541 section 6) 2) simple dynamic table implementation 3) parsing of request header block Type: feature Change-Id: If43e175a0643f9731c15efc412a82345d9e33cee Signed-off-by: Matus Fabian <matfabia@cisco.com>
-rw-r--r--src/plugins/http/http.h3
-rw-r--r--src/plugins/http/http2/hpack.c618
-rw-r--r--src/plugins/http/http2/hpack.h85
-rw-r--r--src/plugins/http/http2/http2.h58
-rw-r--r--src/plugins/http/test/http_test.c248
5 files changed, 989 insertions, 23 deletions
diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h
index 178bdd14881..0245c6e696b 100644
--- a/src/plugins/http/http.h
+++ b/src/plugins/http/http.h
@@ -56,6 +56,7 @@ typedef enum http_req_method_
HTTP_REQ_GET = 0,
HTTP_REQ_POST,
HTTP_REQ_CONNECT,
+ HTTP_REQ_UNKNOWN, /* for internal use */
} http_req_method_t;
typedef enum http_msg_type_
@@ -278,6 +279,7 @@ typedef enum http_status_code_
_ (PROXY_STATUS, "Proxy-Status") \
_ (RANGE, "Range") \
_ (REFERER, "Referer") \
+ _ (REFRESH, "Refresh") \
_ (REPR_DIGEST, "Repr-Digest") \
_ (SET_COOKIE, "Set-Cookie") \
_ (SIGNATURE, "Signature") \
@@ -337,6 +339,7 @@ typedef enum http_url_scheme_
{
HTTP_URL_SCHEME_HTTP,
HTTP_URL_SCHEME_HTTPS,
+ HTTP_URL_SCHEME_UNKNOWN, /* for internal use */
} http_url_scheme_t;
typedef struct http_msg_data_
diff --git a/src/plugins/http/http2/hpack.c b/src/plugins/http/http2/hpack.c
index f279bd9f1b8..917f32e79a0 100644
--- a/src/plugins/http/http2/hpack.c
+++ b/src/plugins/http/http2/hpack.c
@@ -3,9 +3,89 @@
*/
#include <vppinfra/error.h>
+#include <vppinfra/ring.h>
+#include <http/http.h>
#include <http/http2/hpack.h>
#include <http/http2/huffman_table.h>
+#define HPACK_STATIC_TABLE_SIZE 61
+
+typedef struct
+{
+ char *name;
+ uword name_len;
+ char *value;
+ uword value_len;
+} hpack_static_table_entry_t;
+
+#define name_val_token_lit(name, value) \
+ (name), sizeof (name) - 1, (value), sizeof (value) - 1
+
+static hpack_static_table_entry_t
+ hpack_static_table[HPACK_STATIC_TABLE_SIZE] = {
+ { name_val_token_lit (":authority", "") },
+ { name_val_token_lit (":method", "GET") },
+ { name_val_token_lit (":method", "POST") },
+ { name_val_token_lit (":path", "/") },
+ { name_val_token_lit (":path", "/index.html") },
+ { name_val_token_lit (":scheme", "http") },
+ { name_val_token_lit (":scheme", "https") },
+ { name_val_token_lit (":status", "200") },
+ { name_val_token_lit (":status", "204") },
+ { name_val_token_lit (":status", "206") },
+ { name_val_token_lit (":status", "304") },
+ { name_val_token_lit (":status", "400") },
+ { name_val_token_lit (":status", "404") },
+ { name_val_token_lit (":status", "500") },
+ { name_val_token_lit ("accept-charset", "") },
+ { name_val_token_lit ("accept-encoding", "gzip, deflate") },
+ { name_val_token_lit ("accept-language", "") },
+ { name_val_token_lit ("accept-ranges", "") },
+ { name_val_token_lit ("accept", "") },
+ { name_val_token_lit ("access-control-allow-origin", "") },
+ { name_val_token_lit ("age", "") },
+ { name_val_token_lit ("allow", "") },
+ { name_val_token_lit ("authorization", "") },
+ { name_val_token_lit ("cache-control", "") },
+ { name_val_token_lit ("content-disposition", "") },
+ { name_val_token_lit ("content-encoding", "") },
+ { name_val_token_lit ("content-language", "") },
+ { name_val_token_lit ("content-length", "") },
+ { name_val_token_lit ("content-location", "") },
+ { name_val_token_lit ("content-range", "") },
+ { name_val_token_lit ("content-type", "") },
+ { name_val_token_lit ("cookie", "") },
+ { name_val_token_lit ("date", "") },
+ { name_val_token_lit ("etag", "") },
+ { name_val_token_lit ("etag", "") },
+ { name_val_token_lit ("expires", "") },
+ { name_val_token_lit ("from", "") },
+ { name_val_token_lit ("host", "") },
+ { name_val_token_lit ("if-match", "") },
+ { name_val_token_lit ("if-modified-since", "") },
+ { name_val_token_lit ("if-none-match", "") },
+ { name_val_token_lit ("if-range", "") },
+ { name_val_token_lit ("if-unmodified-since", "") },
+ { name_val_token_lit ("last-modified", "") },
+ { name_val_token_lit ("link", "") },
+ { name_val_token_lit ("location", "") },
+ { name_val_token_lit ("max-forwards", "") },
+ { name_val_token_lit ("proxy-authenticate", "") },
+ { name_val_token_lit ("proxy-authorization", "") },
+ { name_val_token_lit ("range", "") },
+ { name_val_token_lit ("referer", "") },
+ { name_val_token_lit ("refresh", "") },
+ { name_val_token_lit ("retry-after", "") },
+ { name_val_token_lit ("server", "") },
+ { name_val_token_lit ("set-cookie", "") },
+ { name_val_token_lit ("strict-transport-security", "") },
+ { name_val_token_lit ("transfer-encoding", "") },
+ { name_val_token_lit ("user-agent", "") },
+ { name_val_token_lit ("vary", "") },
+ { name_val_token_lit ("via", "") },
+ { name_val_token_lit ("www-authenticate", "") },
+ };
+
__clib_export uword
hpack_decode_int (u8 **src, u8 *end, u8 prefix_len)
{
@@ -48,7 +128,7 @@ hpack_decode_int (u8 **src, u8 *end, u8 prefix_len)
return HPACK_INVALID_INT;
}
-int
+http2_error_t
hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len)
{
u64 accumulator = 0;
@@ -61,7 +141,7 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len)
{
/* out of space? */
if (*buf_len == 0)
- return -1;
+ return HTTP2_ERROR_INTERNAL_ERROR;
/* refill */
while (p < end && accumulator_len <= 56)
{
@@ -117,7 +197,7 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len)
/* out of space? */
if (*buf_len == 0)
- return -1;
+ return HTTP2_ERROR_INTERNAL_ERROR;
/* if bogus EOF check bellow will fail */
code = &huff_code_table_fast[(u8) (accumulator
@@ -133,14 +213,14 @@ hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len)
/* we must end with EOF here */
if (((1 << accumulator_len) - 1) !=
(accumulator & ((1 << accumulator_len) - 1)))
- return -1;
+ return HTTP2_ERROR_COMPRESSION_ERROR;
break;
}
}
- return 0;
+ return HTTP2_ERROR_NO_ERROR;
}
-__clib_export int
+__clib_export http2_error_t
hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len)
{
u8 *p, is_huffman;
@@ -155,11 +235,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 -1;
+ return HTTP2_ERROR_COMPRESSION_ERROR;
/* do we have everything? */
if (len > (end - p))
- return -1;
+ return HTTP2_ERROR_COMPRESSION_ERROR;
if (is_huffman)
{
@@ -170,13 +250,13 @@ hpack_decode_string (u8 **src, u8 *end, u8 **buf, uword *buf_len)
{
/* enough space? */
if (len > *buf_len)
- return -1;
+ return HTTP2_ERROR_INTERNAL_ERROR;
clib_memcpy (*buf, p, len);
*buf_len -= len;
*buf += len;
*src = (p + len);
- return 0;
+ return HTTP2_ERROR_NO_ERROR;
}
}
@@ -295,3 +375,521 @@ hpack_encode_string (u8 *dst, const u8 *value, uword value_len)
return dst;
}
+
+__clib_export void
+hpack_dynamic_table_init (hpack_dynamic_table_t *table, u32 max_size)
+{
+ table->max_size = max_size;
+ table->size = max_size;
+ table->used = 0;
+ clib_ring_new (table->entries,
+ max_size / HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD);
+}
+
+__clib_export void
+hpack_dynamic_table_free (hpack_dynamic_table_t *table)
+{
+ hpack_dynamic_table_entry_t *e;
+
+ while ((e = clib_ring_deq (table->entries)) != 0)
+ vec_free (e->buf);
+
+ clib_ring_free (table->entries);
+}
+
+#define hpack_dynamic_table_entry_value_base(e) \
+ ((char *) ((e)->buf + (e)->name_len))
+#define hpack_dynamic_table_entry_value_len(e) \
+ (vec_len ((e)->buf) - (e)->name_len)
+
+always_inline hpack_dynamic_table_entry_t *
+hpack_dynamic_table_get (hpack_dynamic_table_t *table, uword index)
+{
+ if (index > clib_ring_n_enq (table->entries))
+ return 0;
+
+ hpack_dynamic_table_entry_t *first = clib_ring_get_first (table->entries);
+ u32 first_index = first - table->entries;
+ u32 entry_index =
+ (first_index + (clib_ring_n_enq (table->entries) - 1 - (u32) index)) %
+ vec_len (table->entries);
+ return table->entries + entry_index;
+}
+
+__clib_export u8 *
+format_hpack_dynamic_table (u8 *s, va_list *args)
+{
+ hpack_dynamic_table_t *table = va_arg (*args, hpack_dynamic_table_t *);
+ u32 i;
+ hpack_dynamic_table_entry_t *e;
+
+ s = format (s, "HPACK dynamic table:\n");
+ for (i = 0; i < clib_ring_n_enq (table->entries); i++)
+ {
+ e = hpack_dynamic_table_get (table, i);
+ s = format (s, "\t[%u] %U: %U\n", i, format_http_bytes, e->buf,
+ e->name_len, format_http_bytes,
+ hpack_dynamic_table_entry_value_base (e),
+ hpack_dynamic_table_entry_value_len (e));
+ }
+ return s;
+}
+
+static inline void
+hpack_dynamic_table_evict_one (hpack_dynamic_table_t *table)
+{
+ u32 entry_size;
+ hpack_dynamic_table_entry_t *e;
+
+ e = clib_ring_deq (table->entries);
+ ASSERT (e);
+ HTTP_DBG (2, "%U: %U", format_http_bytes, e->buf, e->name_len,
+ format_http_bytes, hpack_dynamic_table_entry_value_base (e),
+ hpack_dynamic_table_entry_value_len (e));
+ entry_size = vec_len (e->buf) + HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD;
+ table->used -= entry_size;
+ vec_reset_length (e->buf);
+}
+
+static void
+hpack_dynamic_table_add (hpack_dynamic_table_t *table, http_token_t *name,
+ http_token_t *value)
+{
+ u32 entry_size;
+ hpack_dynamic_table_entry_t *e;
+
+ entry_size = name->len + value->len + HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD;
+
+ /* make space or evict all */
+ while (clib_ring_n_enq (table->entries) &&
+ (table->used + entry_size > table->size))
+ hpack_dynamic_table_evict_one (table);
+
+ /* attempt to add entry larger than the maximum size is not error */
+ if (entry_size > table->size)
+ return;
+
+ e = clib_ring_enq (table->entries);
+ ASSERT (e);
+ vec_validate (e->buf, name->len + value->len - 1);
+ clib_memcpy (e->buf, name->base, name->len);
+ clib_memcpy (e->buf + name->len, value->base, value->len);
+ e->name_len = name->len;
+ table->used += entry_size;
+
+ HTTP_DBG (2, "%U: %U", format_http_bytes, e->buf, e->name_len,
+ format_http_bytes, hpack_dynamic_table_entry_value_base (e),
+ hpack_dynamic_table_entry_value_len (e));
+}
+
+static http2_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)
+{
+ if (index <= HPACK_STATIC_TABLE_SIZE)
+ {
+ hpack_static_table_entry_t *e = &hpack_static_table[index - 1];
+ name->base = e->name;
+ name->len = e->name_len;
+ if (value_is_indexed)
+ {
+ if (PREDICT_FALSE (e->value_len == 0))
+ {
+ HTTP_DBG (1, "static table entry [%llu] without value", index);
+ return HTTP2_ERROR_COMPRESSION_ERROR;
+ }
+ value->base = e->value;
+ value->len = e->value_len;
+ }
+ 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;
+ }
+ else
+ {
+ hpack_dynamic_table_entry_t *e =
+ hpack_dynamic_table_get (dt, index - HPACK_STATIC_TABLE_SIZE - 1);
+ if (PREDICT_FALSE (!e))
+ {
+ HTTP_DBG (1, "index %llu not in dynamic table", index);
+ return HTTP2_ERROR_COMPRESSION_ERROR;
+ }
+ name->base = (char *) e->buf;
+ name->len = e->name_len;
+ value->base = hpack_dynamic_table_entry_value_base (e);
+ 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;
+ }
+}
+
+__clib_export http2_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)
+{
+ 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;
+
+ ASSERT (*src < end);
+ p = *src;
+
+ /* dynamic table size update */
+ while ((*p & 0xE0) == 0x20)
+ {
+ new_max = hpack_decode_int (&p, end, 5);
+ if (p == end || new_max > (uword) dt->max_size)
+ {
+ HTTP_DBG (1, "invalid dynamic table size update");
+ return HTTP2_ERROR_COMPRESSION_ERROR;
+ }
+ while (clib_ring_n_enq (dt->entries) && new_max > dt->used)
+ hpack_dynamic_table_evict_one (dt);
+ dt->size = (u32) new_max;
+ }
+
+ if (*p & 0x80) /* indexed header field */
+ {
+ index = hpack_decode_int (&p, end, 7);
+ /* index value of 0 is not used */
+ if (index == 0 || index == HPACK_INVALID_INT)
+ {
+ HTTP_DBG (1, "invalid index");
+ return HTTP2_ERROR_COMPRESSION_ERROR;
+ }
+ value_is_indexed = 1;
+ }
+ else if (*p > 0x40) /* incremental indexing - indexed name */
+ {
+ index = hpack_decode_int (&p, end, 6);
+ /* index value of 0 is not used */
+ if (index == 0 || index == HPACK_INVALID_INT)
+ {
+ HTTP_DBG (1, "invalid index");
+ return HTTP2_ERROR_COMPRESSION_ERROR;
+ }
+ add_new_entry = 1;
+ }
+ else if (*p == 0x40) /* incremental indexing - new name */
+ {
+ add_new_entry = 1;
+ p++;
+ }
+ else /* without indexing / never indexed */
+ {
+ if ((*p & 0x0F) == 0) /* new name */
+ p++;
+ else /* indexed name */
+ {
+ index = hpack_decode_int (&p, end, 4);
+ /* index value of 0 is not used */
+ if (index == 0 || index == HPACK_INVALID_INT)
+ {
+ HTTP_DBG (1, "invalid index");
+ return HTTP2_ERROR_COMPRESSION_ERROR;
+ }
+ }
+ }
+
+ if (index)
+ {
+ rv = hpack_get_table_entry (index, &name, &value, value_is_indexed, dt);
+ if (rv != HTTP2_ERROR_NO_ERROR)
+ {
+ 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;
+ }
+ clib_memcpy (*buf, name.base, name.len);
+ *buf_len -= name.len;
+ *buf += name.len;
+ *name_len = name.len;
+ if (value_is_indexed)
+ {
+ if (value.len > *buf_len)
+ {
+ HTTP_DBG (1, "not enough space");
+ return HTTP2_ERROR_INTERNAL_ERROR;
+ }
+ clib_memcpy (*buf, value.base, value.len);
+ *buf_len -= value.len;
+ *buf += value.len;
+ *value_len = value.len;
+ }
+ }
+ else
+ {
+ old_len = *buf_len;
+ name.base = (char *) *buf;
+ rv = hpack_decode_string (&p, end, buf, buf_len);
+ if (rv != HTTP2_ERROR_NO_ERROR)
+ {
+ HTTP_DBG (1, "invalid header name");
+ return rv;
+ }
+ *name_len = old_len - *buf_len;
+ name.len = *name_len;
+ }
+
+ if (!value_is_indexed)
+ {
+ old_len = *buf_len;
+ value.base = (char *) *buf;
+ rv = hpack_decode_string (&p, end, buf, buf_len);
+ if (rv != HTTP2_ERROR_NO_ERROR)
+ {
+ HTTP_DBG (1, "invalid header value");
+ return rv;
+ }
+ *value_len = old_len - *buf_len;
+ value.len = *value_len;
+ }
+
+ if (add_new_entry)
+ hpack_dynamic_table_add (dt, &name, &value);
+
+ *src = p;
+ return HTTP2_ERROR_NO_ERROR;
+}
+
+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,
+ };
+
+ /* 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 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)
+ 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 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;
+}
+
+__clib_export http2_error_t
+hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len,
+ hpack_request_control_data_t *control_data,
+ 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;
+
+ 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;
+ }
+
+ HTTP_DBG (2, "%U", format_hpack_dynamic_table, dynamic_table);
+ return HTTP2_ERROR_NO_ERROR;
+} \ No newline at end of file
diff --git a/src/plugins/http/http2/hpack.h b/src/plugins/http/http2/hpack.h
index 6bd6a3c25f1..9dda1d6d49e 100644
--- a/src/plugins/http/http2/hpack.h
+++ b/src/plugins/http/http2/hpack.h
@@ -6,6 +6,7 @@
#define SRC_PLUGINS_HTTP_HPACK_H_
#include <vppinfra/types.h>
+#include <http/http2/http2.h>
#define HPACK_INVALID_INT CLIB_UWORD_MAX
#if uword_bits == 64
@@ -14,6 +15,47 @@
#define HPACK_ENCODED_INT_MAX_LEN 6
#endif
+#define HPACK_DEFAULT_HEADER_TABLE_SIZE 4096
+#define HPACK_DYNAMIC_TABLE_ENTRY_OVERHEAD 32
+
+typedef struct
+{
+ u8 *buf;
+ uword name_len;
+} hpack_dynamic_table_entry_t;
+
+typedef struct
+{
+ /* SETTINGS_HEADER_TABLE_SIZE */
+ u32 max_size;
+ /* dynamic table size update */
+ u32 size;
+ /* current usage (each entry = 32 + name len + value len) */
+ u32 used;
+ /* ring buffer */
+ hpack_dynamic_table_entry_t *entries;
+} hpack_dynamic_table_t;
+
+enum
+{
+#define _(bit, name, str) HPACK_PSEUDO_HEADER_##name##_PARSED = (1 << bit),
+ foreach_http2_pseudo_header
+#undef _
+};
+
+typedef struct
+{
+ http_req_method_t method;
+ http_url_scheme_t scheme;
+ u8 *authority;
+ u32 authority_len;
+ u8 *path;
+ u32 path_len;
+ u8 *headers;
+ u32 headers_len;
+ u16 parsed_bitmap;
+} hpack_request_control_data_t;
+
/**
* Decode unsigned variable-length integer (RFC7541 section 5.1)
*
@@ -47,9 +89,13 @@ u8 *hpack_encode_int (u8 *dst, uword value, u8 prefix_len);
* advanced by number of written bytes
* @param buf_len Length the buffer, will be decreased
*
- * @return @c 0 on success.
+ * @return @c HTTP2_ERROR_NO_ERROR on success
+ *
+ * @note Caller is responsible to check if there is somthing left in source
+ * buffer first
*/
-int hpack_decode_huffman (u8 **src, u8 *end, u8 **buf, uword *buf_len);
+http2_error_t hpack_decode_huffman (u8 **src, u8 *end, u8 **buf,
+ uword *buf_len);
/**
* Encode given string in Huffman codes.
@@ -73,4 +119,39 @@ u8 *hpack_encode_huffman (u8 *dst, const u8 *value, uword value_len);
*/
uword hpack_huffman_encoded_len (const u8 *value, uword value_len);
+/**
+ * Initialize HPACK dynamic table
+ *
+ * @param table Dynamic table to initialize
+ * @param max_size Maximum table size (SETTINGS_HEADER_TABLE_SIZE)
+ */
+void hpack_dynamic_table_init (hpack_dynamic_table_t *table, u32 max_size);
+
+/**
+ * Free HPACK dynamic table
+ *
+ * @param table Dynamic table to free
+ */
+void hpack_dynamic_table_free (hpack_dynamic_table_t *table);
+
+u8 *format_hpack_dynamic_table (u8 *s, va_list *args);
+
+/**
+ * Request parser
+ *
+ * @param src Header block to parse
+ * @param src_len Length of header block
+ * @param dst Buffer where headers will be decoded
+ * @param dst_len Length of buffer for decoded headers
+ * @param control_data Preparsed pseudo-headers
+ * @param headers List of regular headers
+ * @param dynamic_table Decoder dynamic table
+ *
+ * @return @c HTTP2_ERROR_NO_ERROR on success, connection error otherwise
+ */
+http2_error_t hpack_parse_request (u8 *src, u32 src_len, u8 *dst, u32 dst_len,
+ hpack_request_control_data_t *control_data,
+ http_field_line_t **headers,
+ hpack_dynamic_table_t *dynamic_table);
+
#endif /* SRC_PLUGINS_HTTP_HPACK_H_ */
diff --git a/src/plugins/http/http2/http2.h b/src/plugins/http/http2/http2.h
new file mode 100644
index 00000000000..82403a46fc3
--- /dev/null
+++ b/src/plugins/http/http2/http2.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef SRC_PLUGINS_HTTP_HTTP2_H_
+#define SRC_PLUGINS_HTTP_HTTP2_H_
+
+/* RFC9113 section 7 */
+#define foreach_http2_error \
+ _ (NO_ERROR, "NO_ERROR") \
+ _ (PROTOCOL_ERROR, "PROTOCOL_ERROR") \
+ _ (INTERNAL_ERROR, "INTERNAL_ERROR") \
+ _ (FLOW_CONTROL_ERROR, "FLOW_CONTROL_ERROR") \
+ _ (SETTINGS_TIMEOUT, "SETTINGS_TIMEOUT") \
+ _ (STREAM_CLOSED, "STREAM_CLOSED") \
+ _ (FRAME_SIZE_ERROR, "FRAME_SIZE_ERROR") \
+ _ (REFUSED_STREAM, "REFUSED_STREAM") \
+ _ (CANCEL, "CANCEL") \
+ _ (COMPRESSION_ERROR, "COMPRESSION_ERROR") \
+ _ (CONNECT_ERROR, "CONNECT_ERROR") \
+ _ (ENHANCE_YOUR_CALM, "ENHANCE_YOUR_CALM") \
+ _ (INADEQUATE_SECURITY, "INADEQUATE_SECURITY") \
+ _ (HTTP_1_1_REQUIRED, "HTTP_1_1_REQUIRED")
+
+typedef enum http2_error_
+{
+#define _(s, str) HTTP2_ERROR_##s,
+ foreach_http2_error
+#undef _
+} http2_error_t;
+
+static inline u8 *
+format_http2_error (u8 *s, va_list *va)
+{
+ http2_error_t e = va_arg (*va, http2_error_t);
+ u8 *t = 0;
+
+ switch (e)
+ {
+#define _(s, str) \
+ case HTTP2_ERROR_##s: \
+ t = (u8 *) str; \
+ break;
+ foreach_http2_error
+#undef _
+ default : return format (s, "BUG: unknown");
+ }
+ return format (s, "%s", t);
+}
+
+#define foreach_http2_pseudo_header \
+ _ (0, METHOD, "method") \
+ _ (1, SCHEME, "scheme") \
+ _ (2, AUTHORITY, "authority") \
+ _ (3, PATH, "path") \
+ _ (4, STATUS, "status")
+
+#endif /* SRC_PLUGINS_HTTP_HTTP2_H_ */
diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c
index 04db580cb59..54d5a07855d 100644
--- a/src/plugins/http/test/http_test.c
+++ b/src/plugins/http/test/http_test.c
@@ -535,6 +535,106 @@ http_test_http_header_table (vlib_main_t *vm)
}
static int
+http_test_parse_request (const char *first_req, uword first_req_len,
+ const char *second_req, uword second_req_len,
+ const char *third_req, uword third_req_len,
+ hpack_dynamic_table_t *dynamic_table)
+{
+ http2_error_t rv;
+ u8 *buf = 0;
+ hpack_request_control_data_t control_data;
+ http_field_line_t *headers = 0;
+ u16 parsed_bitmap = 0;
+
+ static http2_error_t (*_hpack_parse_request) (
+ u8 * src, u32 src_len, u8 * dst, u32 dst_len,
+ hpack_request_control_data_t * control_data, http_field_line_t * *headers,
+ hpack_dynamic_table_t * dynamic_table);
+
+ _hpack_parse_request =
+ vlib_get_plugin_symbol ("http_plugin.so", "hpack_parse_request");
+
+ parsed_bitmap =
+ HPACK_PSEUDO_HEADER_METHOD_PARSED | HPACK_PSEUDO_HEADER_SCHEME_PARSED |
+ HPACK_PSEUDO_HEADER_PATH_PARSED | HPACK_PSEUDO_HEADER_AUTHORITY_PARSED;
+
+ /* first request */
+ vec_validate_init_empty (buf, 254, 0);
+ memset (&control_data, 0, sizeof (control_data));
+ rv = _hpack_parse_request ((u8 *) first_req, (u32) first_req_len, buf, 254,
+ &control_data, &headers, dynamic_table);
+ if (rv != HTTP2_ERROR_NO_ERROR ||
+ control_data.parsed_bitmap != parsed_bitmap ||
+ control_data.method != HTTP_REQ_GET ||
+ control_data.scheme != HTTP_URL_SCHEME_HTTP ||
+ control_data.path_len != 1 || control_data.authority_len != 15 ||
+ dynamic_table->used != 57 || vec_len (headers) != 0)
+ return 1;
+ if (memcmp (control_data.path, "/", 1))
+ return 1;
+ if (memcmp (control_data.authority, "www.example.com", 15))
+ return 1;
+ vec_free (headers);
+ vec_free (buf);
+
+ /* second request */
+ vec_validate_init_empty (buf, 254, 0);
+ memset (&control_data, 0, sizeof (control_data));
+ rv = _hpack_parse_request ((u8 *) second_req, (u32) second_req_len, buf, 254,
+ &control_data, &headers, dynamic_table);
+ if (rv != HTTP2_ERROR_NO_ERROR ||
+ control_data.parsed_bitmap != parsed_bitmap ||
+ control_data.method != HTTP_REQ_GET ||
+ control_data.scheme != HTTP_URL_SCHEME_HTTP ||
+ control_data.path_len != 1 || control_data.authority_len != 15 ||
+ dynamic_table->used != 110 || vec_len (headers) != 1 ||
+ control_data.headers_len != 21)
+ return 2;
+ if (memcmp (control_data.path, "/", 1))
+ return 2;
+ if (memcmp (control_data.authority, "www.example.com", 15))
+ return 2;
+ if (headers[0].name_len != 13 || headers[0].value_len != 8)
+ return 2;
+ if (memcmp (control_data.headers + headers[0].name_offset, "cache-control",
+ 13))
+ return 2;
+ if (memcmp (control_data.headers + headers[0].value_offset, "no-cache", 8))
+ return 2;
+ vec_free (headers);
+ vec_free (buf);
+
+ /* third request */
+ vec_validate_init_empty (buf, 254, 0);
+ memset (&control_data, 0, sizeof (control_data));
+ rv = _hpack_parse_request ((u8 *) third_req, (u32) third_req_len, buf, 254,
+ &control_data, &headers, dynamic_table);
+ if (rv != HTTP2_ERROR_NO_ERROR ||
+ control_data.parsed_bitmap != parsed_bitmap ||
+ control_data.method != HTTP_REQ_GET ||
+ control_data.scheme != HTTP_URL_SCHEME_HTTPS ||
+ control_data.path_len != 11 || control_data.authority_len != 15 ||
+ dynamic_table->used != 164 || vec_len (headers) != 1 ||
+ control_data.headers_len != 22)
+ return 3;
+ if (memcmp (control_data.path, "/index.html", 11))
+ return 3;
+ if (memcmp (control_data.authority, "www.example.com", 15))
+ return 3;
+ if (headers[0].name_len != 10 || headers[0].value_len != 12)
+ return 3;
+ if (memcmp (control_data.headers + headers[0].name_offset, "custom-key", 10))
+ return 3;
+ if (memcmp (control_data.headers + headers[0].value_offset, "custom-value",
+ 12))
+ return 3;
+ vec_free (headers);
+ vec_free (buf);
+
+ return 0;
+}
+
+static int
http_test_hpack (vlib_main_t *vm)
{
vlib_cli_output (vm, "hpack_decode_int");
@@ -617,14 +717,14 @@ http_test_hpack (vlib_main_t *vm)
vlib_cli_output (vm, "hpack_decode_string");
- static int (*_hpack_decode_string) (u8 * *src, u8 * end, u8 * *buf,
- uword * buf_len);
+ static http2_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;
- int rv;
+ http2_error_t rv;
#define TEST(i, e) \
vec_validate (input, sizeof (i) - 2); \
@@ -636,7 +736,8 @@ http_test_hpack (vlib_main_t *vm)
rv = _hpack_decode_string (&pos, vec_end (input), &bp, &blen); \
len = vec_len (buf) - blen; \
HTTP_TEST ((len == strlen (e) && !memcmp (buf, e, len) && \
- pos == vec_end (input) && bp == buf + len && rv == 0), \
+ pos == vec_end (input) && bp == buf + len && \
+ rv == HTTP2_ERROR_NO_ERROR), \
"%U is decoded as %U", format_hex_bytes, input, vec_len (input), \
format_http_bytes, buf, len); \
vec_free (input); \
@@ -667,7 +768,7 @@ http_test_hpack (vlib_main_t *vm)
"\x61\xF9\x61\x61\x61\x61\x61\x30\x30\x61\x61\x61\x61\x61\x61\x61")
#undef TEST
-#define N_TEST(i) \
+#define N_TEST(i, e) \
vec_validate (input, sizeof (i) - 2); \
memcpy (input, i, sizeof (i) - 1); \
pos = input; \
@@ -675,21 +776,23 @@ http_test_hpack (vlib_main_t *vm)
bp = buf; \
blen = vec_len (buf); \
rv = _hpack_decode_string (&pos, vec_end (input), &bp, &blen); \
- HTTP_TEST ((rv != 0), "%U should be invalid", format_hex_bytes, input, \
- vec_len (input)); \
+ HTTP_TEST ((rv == e), "%U should be invalid (%U)", format_hex_bytes, input, \
+ vec_len (input), format_http2_error, rv); \
vec_free (input); \
vec_free (buf);
/* incomplete */
- N_TEST ("\x07priv");
+ N_TEST ("\x87", HTTP2_ERROR_COMPRESSION_ERROR);
+ N_TEST ("\x07priv", HTTP2_ERROR_COMPRESSION_ERROR);
/* invalid length */
- N_TEST ("\x7Fprivate");
+ N_TEST ("\x7Fprivate", HTTP2_ERROR_COMPRESSION_ERROR);
/* invalid EOF */
- N_TEST ("\x81\x8C");
+ N_TEST ("\x81\x8C", HTTP2_ERROR_COMPRESSION_ERROR);
/* 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");
+ "\xE0\x82\xA6\x2D\x1B\xFF",
+ HTTP2_ERROR_INTERNAL_ERROR);
#undef N_TEST
vlib_cli_output (vm, "hpack_encode_string");
@@ -728,6 +831,129 @@ http_test_hpack (vlib_main_t *vm)
TEST ("[XZ]", "\x4[XZ]");
#undef TEST
+ vlib_cli_output (vm, "hpack_decode_header");
+
+ static http2_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);
+
+ _hpack_decode_header =
+ vlib_get_plugin_symbol ("http_plugin.so", "hpack_decode_header");
+
+ static void (*_hpack_dynamic_table_init) (hpack_dynamic_table_t * table,
+ u32 max_size);
+
+ _hpack_dynamic_table_init =
+ vlib_get_plugin_symbol ("http_plugin.so", "hpack_dynamic_table_init");
+
+ static void (*_hpack_dynamic_table_free) (hpack_dynamic_table_t * table);
+
+ _hpack_dynamic_table_free =
+ vlib_get_plugin_symbol ("http_plugin.so", "hpack_dynamic_table_free");
+
+ u32 name_len, value_len;
+ hpack_dynamic_table_t table;
+
+ _hpack_dynamic_table_init (&table, 128);
+
+#define TEST(i, e_name, e_value, dt_size) \
+ vec_validate (input, sizeof (i) - 2); \
+ memcpy (input, i, sizeof (i) - 1); \
+ pos = input; \
+ vec_validate_init_empty (buf, 63, 0); \
+ bp = buf; \
+ blen = vec_len (buf); \
+ 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 && \
+ name_len == strlen (e_name) && value_len == strlen (e_value) && \
+ !memcmp (buf, e_name, name_len) && \
+ !memcmp (buf + name_len, e_value, value_len) && \
+ vec_len (buf) == (blen + name_len + value_len) && \
+ pos == vec_end (input) && bp == buf + name_len + value_len), \
+ "%U is decoded as '%U: %U'", format_hex_bytes, input, \
+ vec_len (input), format_http_bytes, buf, name_len, \
+ format_http_bytes, buf + name_len, value_len); \
+ vec_free (input); \
+ vec_free (buf);
+
+ /* C.2.1. Literal Header Field with Indexing */
+ TEST ("\x40\x0A\x63\x75\x73\x74\x6F\x6D\x2D\x6B\x65\x79\x0D\x63\x75\x73\x74"
+ "\x6F\x6D\x2D\x68\x65\x61\x64\x65\x72",
+ "custom-key", "custom-header", 55);
+ /* C.2.2. Literal Header Field without Indexing */
+ TEST ("\x04\x0C\x2F\x73\x61\x6D\x70\x6C\x65\x2F\x70\x61\x74\x68", ":path",
+ "/sample/path", 55);
+ /* C.2.3. Literal Header Field Never Indexed */
+ TEST ("\x10\x08\x70\x61\x73\x73\x77\x6F\x72\x64\x06\x73\x65\x63\x72\x65\x74",
+ "password", "secret", 55);
+ /* C.2.4. Indexed Header Field */
+ TEST ("\x82", ":method", "GET", 55);
+ TEST ("\xBE", "custom-key", "custom-header", 55);
+ /* Literal Header Field with Indexing - enough space in dynamic table */
+ TEST ("\x41\x0F\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C\x65\x2E\x63\x6F\x6D",
+ ":authority", "www.example.com", 112);
+ /* verification */
+ TEST ("\xBE", ":authority", "www.example.com", 112);
+ TEST ("\xBF", "custom-key", "custom-header", 112);
+ /* Literal Header Field with Indexing - eviction */
+ TEST ("\x58\x08\x6E\x6F\x2D\x63\x61\x63\x68\x65", "cache-control",
+ "no-cache", 110);
+ /* verification */
+ TEST ("\xBE", "cache-control", "no-cache", 110);
+ TEST ("\xBF", ":authority", "www.example.com", 110);
+ /* Literal Header Field with Indexing - eviction */
+ TEST ("\x40\x0A\x63\x75\x73\x74\x6F\x6D\x2D\x6B\x65\x79\x0D\x63\x75\x73\x74"
+ "\x6F\x6D\x2D\x68\x65\x61\x64\x65\x72",
+ "custom-key", "custom-header", 108);
+ /* verification */
+ TEST ("\xBE", "custom-key", "custom-header", 108);
+ TEST ("\xBF", "cache-control", "no-cache", 108);
+ /* Literal Header Field with Indexing - eviction */
+ TEST ("\x41\x0F\x77\x77\x77\x2E\x65\x78\x61\x6D\x70\x6C\x65\x2E\x63\x6F\x6D",
+ ":authority", "www.example.com", 112);
+ /* verification */
+ TEST ("\xBE", ":authority", "www.example.com", 112);
+ TEST ("\xBF", "custom-key", "custom-header", 112);
+ /* Literal Header Field with Indexing - eviction with reference */
+ TEST ("\x7F\x00\x0C\x63\x75\x73\x74\x6F\x6D\x2D\x76\x61\x6C\x75\x65",
+ "custom-key", "custom-value", 111);
+ /* verification */
+ TEST ("\xBE", "custom-key", "custom-value", 111);
+ TEST ("\xBF", ":authority", "www.example.com", 111);
+#undef TEST
+
+ _hpack_dynamic_table_free (&table);
+
+ vlib_cli_output (vm, "hpack_parse_request");
+
+ int result;
+ /* C.3. Request Examples without Huffman Coding */
+ _hpack_dynamic_table_init (&table, HPACK_DEFAULT_HEADER_TABLE_SIZE);
+ result = http_test_parse_request (
+ http_token_lit ("\x82\x86\x84\x41\x0F\x77\x77\x77\x2E\x65\x78\x61"
+ "\x6D\x70\x6C\x65\x2E\x63\x6F\x6D"),
+ http_token_lit (
+ "\x82\x86\x84\xBE\x58\x08\x6E\x6F\x2D\x63\x61\x63\x68\x65"),
+ http_token_lit (
+ "\x82\x87\x85\xBF\x40\x0A\x63\x75\x73\x74\x6F\x6D\x2D\x6B"
+ "\x65\x79\x0C\x63\x75\x73\x74\x6F\x6D\x2D\x76\x61\x6C\x75\x65"),
+ &table);
+ _hpack_dynamic_table_free (&table);
+ HTTP_TEST ((result == 0), "request without Huffman Coding (result=%d)",
+ result);
+ /* C.4. Request Examples with Huffman Coding */
+ _hpack_dynamic_table_init (&table, HPACK_DEFAULT_HEADER_TABLE_SIZE);
+ result = http_test_parse_request (
+ http_token_lit (
+ "\x82\x86\x84\x41\x8C\xF1\xE3\xC2\xE5\xF2\x3A\x6B\xA0\xAB\x90\xF4\xFF"),
+ http_token_lit ("\x82\x86\x84\xBE\x58\x86\xA8\xEB\x10\x64\x9C\xBF"),
+ http_token_lit ("\x82\x87\x85\xBF\x40\x88\x25\xA8\x49\xE9\x5B\xA9\x7D\x7F"
+ "\x89\x25\xA8\x49\xE9\x5B\xB8\xE8\xB4\xBF"),
+ &table);
+ _hpack_dynamic_table_free (&table);
+ HTTP_TEST ((result == 0), "request with Huffman Coding (result=%d)", result);
return 0;
}