From: Matus Fabian Date: Mon, 29 Sep 2025 16:05:07 +0000 (-0400) Subject: http: QPACK encoding response X-Git-Url: https://gerrit.fd.io/r/gitweb?a=commitdiff_plain;h=refs%2Fchanges%2F92%2F43792%2F3;p=vpp.git http: QPACK encoding response Type: feature Change-Id: I238227966b9bb1056f371241b5d8e0667993a967 Signed-off-by: Matus Fabian --- diff --git a/src/plugins/http/http.h b/src/plugins/http/http.h index 9eb31990892..dca7069ce19 100644 --- a/src/plugins/http/http.h +++ b/src/plugins/http/http.h @@ -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_ { diff --git a/src/plugins/http/http3/qpack.c b/src/plugins/http/http3/qpack.c index d2fa4aedaba..2fd53473c48 100644 --- a/src/plugins/http/http3/qpack.c +++ b/src/plugins/http/http3/qpack.c @@ -4,6 +4,7 @@ #include #include +#include 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; +} diff --git a/src/plugins/http/http3/qpack.h b/src/plugins/http/http3/qpack.h index 32c1ceaff6a..da36e46429e 100644 --- a/src/plugins/http/http3/qpack.h +++ b/src/plugins/http/http3/qpack.h @@ -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_ */ diff --git a/src/plugins/http/test/http_test.c b/src/plugins/http/test/http_test.c index a4a08c2a020..6ce3a153785 100644 --- a/src/plugins/http/test/http_test.c +++ b/src/plugins/http/test/http_test.c @@ -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; }