http: hpack headers decoding 74/42374/8
authorMatus Fabian <[email protected]>
Tue, 18 Feb 2025 09:29:10 +0000 (04:29 -0500)
committerFlorin Coras <[email protected]>
Tue, 25 Feb 2025 17:47:07 +0000 (17:47 +0000)
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 <[email protected]>
src/plugins/http/http.h
src/plugins/http/http2/hpack.c
src/plugins/http/http2/hpack.h
src/plugins/http/http2/http2.h [new file with mode: 0644]
src/plugins/http/test/http_test.c

index 178bdd1..0245c6e 100644 (file)
@@ -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_
index f279bd9..917f32e 100644 (file)
@@ -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
index 6bd6a3c..9dda1d6 100644 (file)
@@ -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
 #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 (file)
index 0000000..82403a4
--- /dev/null
@@ -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_ */
index 04db580..54d5a07 100644 (file)
@@ -534,6 +534,106 @@ http_test_http_header_table (vlib_main_t *vm)
   return 0;
 }
 
+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)
 {
@@ -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;
 }