http: QPACK encoding response 92/43792/3
authorMatus Fabian <[email protected]>
Mon, 29 Sep 2025 16:05:07 +0000 (12:05 -0400)
committerFlorin Coras <[email protected]>
Tue, 30 Sep 2025 19:23:27 +0000 (19:23 +0000)
Type: feature

Change-Id: I238227966b9bb1056f371241b5d8e0667993a967
Signed-off-by: Matus Fabian <[email protected]>
src/plugins/http/http.h
src/plugins/http/http3/qpack.c
src/plugins/http/http3/qpack.h
src/plugins/http/test/http_test.c

index 9eb3199..dca7069 100644 (file)
@@ -191,6 +191,8 @@ typedef enum http_content_type_
 #define foreach_http_status_code                                              \
   _ (100, CONTINUE, "100 Continue")                                           \
   _ (101, SWITCHING_PROTOCOLS, "101 Switching Protocols")                     \
+  _ (102, PROCESSING, "102 Processing")                                       \
+  _ (103, EARLY_HINTS, "103 Early Hints")                                     \
   _ (200, OK, "200 OK")                                                       \
   _ (201, CREATED, "201 Created")                                             \
   _ (202, ACCEPTED, "202 Accepted")                                           \
@@ -226,13 +228,26 @@ typedef enum http_content_type_
   _ (417, EXPECTATION_FAILED, "417 Expectation Failed")                       \
   _ (421, MISDIRECTED_REQUEST, "421 Misdirected Request")                     \
   _ (422, UNPROCESSABLE_CONTENT, "422 Unprocessable_Content")                 \
+  _ (423, LOCKED, "423 Locked")                                               \
+  _ (424, FAILED_DEPENDENCY, "424 Failed Dependency")                         \
+  _ (425, TOO_EARLY, "425 Too Early")                                         \
   _ (426, UPGRADE_REQUIRED, "426 Upgrade Required")                           \
+  _ (428, PRECONDITION_REQUIRED, "428 Precondition Required")                 \
+  _ (429, TOO_MANY_REQUESTS, "429 Too Many Requests")                         \
+  _ (431, REQUEST_HEADER_FIELDS_TOO_LARGE,                                    \
+     "431 Request Header Fields Too Large")                                   \
+  _ (451, UNAVAILABLE_FOR_LEGAL_REASONS, "451 Unavailable For Legal Reasons") \
   _ (500, INTERNAL_ERROR, "500 Internal Server Error")                        \
   _ (501, NOT_IMPLEMENTED, "501 Not Implemented")                             \
   _ (502, BAD_GATEWAY, "502 Bad Gateway")                                     \
   _ (503, SERVICE_UNAVAILABLE, "503 Service Unavailable")                     \
   _ (504, GATEWAY_TIMEOUT, "504 Gateway Timeout")                             \
-  _ (505, HTTP_VERSION_NOT_SUPPORTED, "505 HTTP Version Not Supported")
+  _ (505, HTTP_VERSION_NOT_SUPPORTED, "505 HTTP Version Not Supported")       \
+  _ (506, VARIANT_ALSO_NEGOTIATES, "506 Variant Also Negotiates")             \
+  _ (507, INSUFFICIENT_STORAGE, "507 Insufficient Storage")                   \
+  _ (508, LOOP_DETECTED, "508 Loop Detected")                                 \
+  _ (511, NETWORK_AUTHENTICATION_REQUIRED,                                    \
+     "511 Network Authentication Required")
 
 typedef enum http_status_code_
 {
index d2fa4ae..2fd5347 100644 (file)
@@ -4,6 +4,7 @@
 
 #include <http/http3/qpack.h>
 #include <http/http2/hpack_inlines.h>
+#include <http/http_status_codes.h>
 
 typedef struct
 {
@@ -404,6 +405,14 @@ qpack_lookup_content_encoding (const char *value, u32 value_len,
   return 42;
 }
 
+static int
+qpack_lookup_content_length (const char *value, u32 value_len, u8 *full_match)
+{
+  /* "content-length: 0" is encoded directly in qpack_encode_content_len */
+  *full_match = 0;
+  return 4;
+}
+
 static int
 qpack_lookup_content_security_policy (const char *value, u32 value_len,
                                      u8 *full_match)
@@ -776,7 +785,7 @@ static qpack_static_table_lookup_fn qpack_static_table_lookup[] = {
   [HTTP_HEADER_CONTENT_DISPOSITION] = qpack_lookup_content_disposition,
   [HTTP_HEADER_CONTENT_ENCODING] = qpack_lookup_content_encoding,
   [HTTP_HEADER_CONTENT_LANGUAGE] = qpack_lookup_no_match,
-  [HTTP_HEADER_CONTENT_LENGTH] = qpack_lookup_no_match,
+  [HTTP_HEADER_CONTENT_LENGTH] = qpack_lookup_content_length,
   [HTTP_HEADER_CONTENT_LOCATION] = qpack_lookup_no_match,
   [HTTP_HEADER_CONTENT_RANGE] = qpack_lookup_no_match,
   [HTTP_HEADER_CONTENT_SECURITY_POLICY] = qpack_lookup_content_security_policy,
@@ -1092,6 +1101,101 @@ static const http3_error_t hpack_error_to_http3_error[] = {
   [HPACK_ERROR_UNKNOWN] = HTTP3_ERROR_INTERNAL_ERROR,
 };
 
+#define encode_static_entry(_index)                                           \
+  vec_add2 (dst, a, 1);                                                       \
+  *a++ = 0xC0 | _index;
+
+static u8 *
+qpack_encode_status_code (u8 *dst, http_status_code_t sc)
+{
+  u32 orig_len, actual_size;
+  u8 *a, *b;
+
+  switch (sc)
+    {
+    case HTTP_STATUS_EARLY_HINTS:
+      encode_static_entry (24);
+      break;
+    case HTTP_STATUS_OK:
+      encode_static_entry (25);
+      break;
+    case HTTP_STATUS_NOT_MODIFIED:
+      encode_static_entry (26);
+      break;
+    case HTTP_STATUS_NOT_FOUND:
+      encode_static_entry (27);
+      break;
+    case HTTP_STATUS_SERVICE_UNAVAILABLE:
+      encode_static_entry (28);
+      break;
+    case HTTP_STATUS_CONTINUE:
+      encode_static_entry (63);
+      break;
+    case HTTP_STATUS_NO_CONTENT:
+      encode_static_entry (64);
+      break;
+    case HTTP_STATUS_PARTIAL_CONTENT:
+      encode_static_entry (65);
+      break;
+    case HTTP_STATUS_FOUND:
+      encode_static_entry (66);
+      break;
+    case HTTP_STATUS_BAD_REQUEST:
+      encode_static_entry (67);
+      break;
+    case HTTP_STATUS_FORBIDDEN:
+      encode_static_entry (68);
+      break;
+    case HTTP_STATUS_MISDIRECTED_REQUEST:
+      encode_static_entry (69);
+      break;
+    case HTTP_STATUS_TOO_EARLY:
+      encode_static_entry (70);
+      break;
+    case HTTP_STATUS_INTERNAL_ERROR:
+      encode_static_entry (71);
+      break;
+    default:
+      orig_len = vec_len (dst);
+      vec_add2 (dst, a, 5);
+      *a = 0x50;
+      b = hpack_encode_int (a, 24, 4);
+      b = qpack_encode_string (b, (const u8 *) http_status_code_str[sc], 3, 8);
+      actual_size = b - a;
+      vec_set_len (dst, orig_len + actual_size);
+      break;
+    }
+  return dst;
+}
+
+static u8 *
+qpack_encode_content_len (u8 *dst, u64 content_len)
+{
+  u8 digit_buffer[20];
+  u8 *d = digit_buffer + sizeof (digit_buffer);
+  u8 *a;
+
+  /* save some cycles and encode "content-length: 0" directly */
+  if (content_len == 0)
+    {
+      vec_add2 (dst, a, 1);
+      /* static table index 4 */
+      *a = 0xC4;
+      return dst;
+    }
+
+  do
+    {
+      *--d = '0' + content_len % 10;
+      content_len /= 10;
+    }
+  while (content_len);
+
+  dst = qpack_encode_header (dst, HTTP_HEADER_CONTENT_LENGTH, d,
+                            digit_buffer + sizeof (digit_buffer) - d);
+  return dst;
+}
+
 static inline hpack_error_t
 qpack_parse_headers_prefix (u8 **src, u8 *end, qpack_decoder_ctx_t *ctx)
 {
@@ -1162,3 +1266,67 @@ qpack_parse_response (u8 *src, u32 src_len, u8 *dst, u32 dst_len,
                              decoder_ctx, qpack_decode_header);
   return hpack_error_to_http3_error[rv];
 }
+
+__clib_export void
+qpack_serialize_response (u8 *app_headers, u32 app_headers_len,
+                         hpack_response_control_data_t *control_data,
+                         u8 **dst)
+{
+  u8 *a, *p, *end;
+
+  p = *dst;
+  /* encoded field section prefix, two zero bytes because we don't use dynamic
+   * table */
+  vec_add2 (p, a, 2);
+  a[0] = 0;
+  a[1] = 0;
+
+  /* status code must be first since it is pseudo-header */
+  p = qpack_encode_status_code (p, control_data->sc);
+
+  /* server name */
+  p = qpack_encode_header (p, HTTP_HEADER_SERVER, control_data->server_name,
+                          control_data->server_name_len);
+
+  /* date */
+  p = qpack_encode_header (p, HTTP_HEADER_DATE, control_data->date,
+                          control_data->date_len);
+
+  /* content length if any */
+  if (control_data->content_len != HPACK_ENCODER_SKIP_CONTENT_LEN)
+    p = qpack_encode_content_len (p, control_data->content_len);
+
+  if (!app_headers_len)
+    {
+      *dst = p;
+      return;
+    }
+
+  end = app_headers + app_headers_len;
+  while (app_headers < end)
+    {
+      /* custom header name? */
+      u32 *tmp = (u32 *) app_headers;
+      if (PREDICT_FALSE (*tmp & HTTP_CUSTOM_HEADER_NAME_BIT))
+       {
+         http_custom_token_t *name, *value;
+         name = (http_custom_token_t *) app_headers;
+         u32 name_len = name->len & ~HTTP_CUSTOM_HEADER_NAME_BIT;
+         app_headers += sizeof (http_custom_token_t) + name_len;
+         value = (http_custom_token_t *) app_headers;
+         app_headers += sizeof (http_custom_token_t) + value->len;
+         p = qpack_encode_custom_header (p, name->token, name_len,
+                                         value->token, value->len);
+       }
+      else
+       {
+         http_app_header_t *header;
+         header = (http_app_header_t *) app_headers;
+         app_headers += sizeof (http_app_header_t) + header->value.len;
+         p = qpack_encode_header (p, header->name, header->value.token,
+                                  header->value.len);
+       }
+    }
+
+  *dst = p;
+}
index 32c1cea..da36e46 100644 (file)
@@ -53,4 +53,16 @@ qpack_parse_response (u8 *src, u32 src_len, u8 *dst, u32 dst_len,
                      http_field_line_t **headers,
                      qpack_decoder_ctx_t *decoder_ctx);
 
+/**
+ * Serialize response
+ *
+ * @param app_headers     App header list
+ * @param app_headers_len App header list length
+ * @param control_data    Header values set by protocol layer
+ * @param dst             Vector where serialized headers will be added
+ */
+void qpack_serialize_response (u8 *app_headers, u32 app_headers_len,
+                              hpack_response_control_data_t *control_data,
+                              u8 **dst);
+
 #endif /* SRC_PLUGINS_HTTP_QPACK_H_ */
index a4a08c2..6ce3a15 100644 (file)
@@ -1775,6 +1775,63 @@ http_test_qpack (vlib_main_t *vm)
             vec_len (buf));
   vec_free (buf);
 
+  vlib_cli_output (vm, "qpack_serialize_response");
+
+  static void (*_qpack_serialize_response) (
+    u8 * app_headers, u32 app_headers_len,
+    hpack_response_control_data_t * control_data, u8 * *dst);
+  _qpack_serialize_response =
+    vlib_get_plugin_symbol ("http_plugin.so", "qpack_serialize_response");
+
+  u8 *server_name = format (0, "http unit tests");
+  u8 *date = format (0, "Mon, 21 Oct 2013 20:13:21 GMT");
+
+  memset (&resp_control_data, 0, sizeof (resp_control_data));
+  vec_validate_init_empty (buf, 127, 0xFF);
+  vec_reset_length (buf);
+  resp_control_data.sc = HTTP_STATUS_GATEWAY_TIMEOUT;
+  resp_control_data.server_name = server_name;
+  resp_control_data.server_name_len = vec_len (server_name);
+  resp_control_data.date = date;
+  resp_control_data.date_len = vec_len (date);
+  u8 expected4[] =
+    "\x00\x00\x5F\x09\x03\x35\x30\x34\x5F\x4D\x8B\x9D\x29\xAD\x4B\x6A\x32\x54"
+    "\x49\x50\x94\x7F\x56\x96\xD0\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95"
+    "\x04\x0B\x81\x66\xE0\x82\xA6\x2D\x1B\xFF\xC4";
+  _qpack_serialize_response (0, 0, &resp_control_data, &buf);
+  HTTP_TEST ((vec_len (buf) == (sizeof (expected4) - 1) &&
+             !memcmp (buf, expected4, vec_len (buf))),
+            "response encoded as: %U", format_hex_bytes, buf, vec_len (buf));
+  vec_free (buf);
+
+  resp_control_data.sc = HTTP_STATUS_OK;
+  resp_control_data.content_len = 1024;
+  http_headers_ctx_t headers_ctx;
+  u8 *headers_buf = 0;
+  vec_validate_init_empty (headers_buf, 127, 0xFF);
+  http_init_headers_ctx (&headers_ctx, headers_buf, vec_len (headers_buf));
+  http_add_header (&headers_ctx, HTTP_HEADER_CONTENT_TYPE,
+                  http_token_lit ("text/plain"));
+  http_add_header (&headers_ctx, HTTP_HEADER_CACHE_STATUS,
+                  http_token_lit ("ExampleCache; hit"));
+  http_add_custom_header (&headers_ctx, http_token_lit ("sandwich"),
+                         http_token_lit ("spam"));
+  u8 expected5[] =
+    "\x00\x00\xD9\x5F\x4D\x8B\x9D\x29\xAD\x4B\x6A\x32\x54\x49\x50\x94\x7F\x56"
+    "\x96\xD0\x7A\xBE\x94\x10\x54\xD4\x44\xA8\x20\x05\x95\x04\x0B\x81\x66\xE0"
+    "\x82\xA6\x2D\x1B\xFF\x54\x83\x08\x04\xD7\xF5\x2F\x01\x20\xC9\x39\x56\x42"
+    "\x46\x9B\x51\x8D\xC1\xE4\x74\xD7\x41\x6F\x0C\x93\x97\xED\x49\xCC\x9F\x2E"
+    "\x40\xEA\x93\xC1\x89\x3F\x83\x45\x63\xA7";
+  _qpack_serialize_response (headers_buf, headers_ctx.tail_offset,
+                            &resp_control_data, &buf);
+  HTTP_TEST ((vec_len (buf) == (sizeof (expected5) - 1) &&
+             !memcmp (buf, expected5, vec_len (buf))),
+            "response encoded as: %U", format_hex_bytes, buf, vec_len (buf));
+  vec_free (headers_buf);
+  vec_free (buf);
+  vec_free (server_name);
+  vec_free (date);
+
   return 0;
 }