http2/hpack.c
http2/http2.c
http2/frame.c
+ http3/frame.c
http3/qpack.c
http.c
http_buffer.c
}
#define HTTP_INVALID_VARINT ((u64) ~0)
+#define HTTP_VARINT_MAX 0x3FFFFFFFFFFFFFFF
+#define HTTP_VARINT_MAX_LEN 8
#define HTTP_CAPSULE_HEADER_MAX_SIZE 8
#define HTTP_UDP_PROXY_DATAGRAM_CAPSULE_OVERHEAD 5
#define HTTP_UDP_PAYLOAD_MAX_LEN 65527
/* variable-length integer (RFC9000 section 16) */
always_inline u64
-_http_decode_varint (u8 **pos, u8 *end)
+http_decode_varint (u8 **pos, u8 *end)
{
u8 first_byte, bytes_left, *p;
u64 value;
}
always_inline u8 *
-_http_encode_varint (u8 *dst, u64 value)
+http_encode_varint (u8 *dst, u64 value)
{
- ASSERT (value <= 0x3FFFFFFFFFFFFFFF);
- if (value <= 0x3f)
+ ASSERT (value <= HTTP_VARINT_MAX);
+ if (value <= 0x3F)
{
*dst++ = (u8) value;
return dst;
}
}
+always_inline u8
+http_varint_len (u64 value)
+{
+ if (value > 0x3F)
+ {
+ if (value > 0x3FFF)
+ {
+ if (value > 0x3FFFFFFF)
+ return 8;
+ return 4;
+ }
+ return 2;
+ }
+ return 1;
+}
+
always_inline int
_http_parse_capsule (u8 *data, u64 len, u64 *type, u8 *value_offset,
u64 *value_len)
u8 *p = data;
u8 *end = data + len;
- capsule_type = _http_decode_varint (&p, end);
+ capsule_type = http_decode_varint (&p, end);
if (capsule_type == HTTP_INVALID_VARINT)
{
clib_warning ("failed to parse capsule type");
return -1;
}
- capsule_value_len = _http_decode_varint (&p, end);
+ capsule_value_len = http_decode_varint (&p, end);
if (capsule_value_len == HTTP_INVALID_VARINT)
{
clib_warning ("failed to parse capsule length");
}
/* context ID field should be zero (RFC9298 section 4) */
- context_id = _http_decode_varint (&p, end);
+ context_id = http_decode_varint (&p, end);
if (context_id != 0)
{
*payload_len = value_len + value_offset;
*buf++ = HTTP_CAPSULE_TYPE_DATAGRAM;
/* capsule length */
- buf = _http_encode_varint (buf, payload_len + 1);
+ buf = http_encode_varint (buf, payload_len + 1);
/* context ID */
*buf++ = 0;
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <http/http3/frame.h>
+
+/*
+ * RFC9114 section 7.1
+ *
+ * HTTP/3 Frame Format {
+ * Type (i),
+ * Length (i),
+ * Frame Payload (..),
+ * }
+ */
+
+__clib_export http3_error_t
+http3_frame_header_read (u8 *src, u64 src_len, http3_stream_type_t stream_type,
+ http3_frame_header_t *fh)
+{
+ u8 *p = src;
+ u8 *end = src + src_len;
+
+ /* parse frame header */
+ fh->type = http_decode_varint (&p, end);
+ if (fh->type == HTTP_INVALID_VARINT || p == end)
+ return HTTP3_ERROR_INCOMPLETE;
+ fh->length = http_decode_varint (&p, end);
+ if (fh->length == HTTP_INVALID_VARINT)
+ return HTTP3_ERROR_INCOMPLETE;
+ fh->payload = p;
+
+ /* validate if received on correct stream type */
+ switch (fh->type)
+ {
+#define _(value, label, ctrl_stream, req_stream, push_stream) \
+ case HTTP3_FRAME_TYPE_##label: \
+ { \
+ switch (stream_type) \
+ { \
+ case HTTP3_STREAM_TYPE_CONTROL: \
+ if (!ctrl_stream) \
+ return HTTP3_ERROR_FRAME_UNEXPECTED; \
+ break; \
+ case HTTP3_STREAM_TYPE_PUSH: \
+ if (!push_stream) \
+ return HTTP3_ERROR_FRAME_UNEXPECTED; \
+ break; \
+ case HTTP3_STREAM_TYPE_REQUEST: \
+ if (!req_stream) \
+ return HTTP3_ERROR_FRAME_UNEXPECTED; \
+ break; \
+ default: \
+ ASSERT (0); \
+ break; \
+ } \
+ } \
+ break;
+ foreach_http3_frame_type
+#undef _
+ default :
+ /* ignore unknown frame type */
+ break;
+ }
+ return HTTP3_ERROR_NO_ERROR;
+}
+
+__clib_export http3_error_t
+http3_frame_goaway_read (u8 *payload, u64 len, u64 *stream_or_push_id)
+{
+ u8 *p = payload;
+ u8 *end = payload + len;
+
+ if (len == 0)
+ return HTTP3_ERROR_FRAME_ERROR;
+
+ *stream_or_push_id = http_decode_varint (&p, end);
+ if (*stream_or_push_id == HTTP_INVALID_VARINT || p != end)
+ return HTTP3_ERROR_FRAME_ERROR;
+
+ return HTTP3_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http3_frame_goaway_write (u64 stream_or_push_id, u8 **dst)
+{
+ u8 *p;
+ u8 payload_len = http_varint_len (stream_or_push_id);
+
+ vec_add2 (*dst, p, 2 + payload_len);
+ *p++ = (u8) HTTP3_FRAME_TYPE_GOAWAY;
+ *p++ = payload_len;
+ p = http_encode_varint (p, stream_or_push_id);
+}
+
+__clib_export http3_error_t
+http3_frame_settings_read (u8 *payload, u64 len,
+ http3_conn_settings_t *settings)
+{
+ u8 *p = payload;
+ u8 *end = payload + len;
+ u64 identifier, value;
+
+ if (len == 0)
+ return HTTP3_ERROR_NO_ERROR;
+
+ while (p != end)
+ {
+ identifier = http_decode_varint (&p, end);
+ if (identifier == HTTP_INVALID_VARINT || p == end)
+ return HTTP3_ERROR_FRAME_ERROR;
+ value = http_decode_varint (&p, end);
+ if (value == HTTP_INVALID_VARINT)
+ return HTTP3_ERROR_FRAME_ERROR;
+ switch (identifier)
+ {
+#define _(v, label, member, min, max, default_value, server, client) \
+ case HTTP3_SETTINGS_##label: \
+ if (!(value >= min && value <= max)) \
+ return HTTP3_ERROR_SETTINGS_ERROR; \
+ settings->member = value; \
+ break;
+ foreach_http3_settings
+#undef _
+ default :
+ /* ignore unknown or unsupported identifier */
+ break;
+ }
+ }
+
+ return HTTP3_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http3_frame_settings_write (http3_conn_settings_t *settings, u8 **dst)
+{
+ u64 payload_len = 0;
+ u8 *p;
+
+#define _(v, label, member, min, max, default_value, server, client) \
+ if (settings->member != default_value) \
+ payload_len += http_varint_len (settings->member) + \
+ http_varint_len (HTTP3_SETTINGS_##label);
+ foreach_http3_settings
+#undef _
+
+ vec_add2 (*dst, p, 1 + http_varint_len (payload_len) + payload_len);
+ *p++ = (u8) HTTP3_FRAME_TYPE_SETTINGS;
+ p = http_encode_varint (p, payload_len);
+
+ if (payload_len == 0)
+ return;
+
+#define _(v, label, member, min, max, default_value, server, client) \
+ if (settings->member != default_value) \
+ { \
+ p = http_encode_varint (p, HTTP3_SETTINGS_##label); \
+ p = http_encode_varint (p, settings->member); \
+ }
+ foreach_http3_settings
+#undef _
+}
--- /dev/null
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#ifndef SRC_PLUGINS_HTTP_HTTP3_FRAME_H_
+#define SRC_PLUGINS_HTTP_HTTP3_FRAME_H_
+
+#include <http/http3/http3.h>
+
+#define HTTP3_FRAME_HEADER_MAX_LEN (HTTP_VARINT_MAX_LEN * 2)
+
+/* value, label, ctrl_stream, req_stream, push_stream */
+#define foreach_http3_frame_type \
+ _ (0x00, DATA, 0, 1, 1) \
+ _ (0x01, HEADERS, 0, 1, 1) \
+ _ (0x03, CANCEL_PUSH, 1, 0, 0) \
+ _ (0x04, SETTINGS, 1, 0, 0) \
+ _ (0x05, PUSH_PROMISE, 0, 1, 0) \
+ _ (0x07, GOAWAY, 1, 0, 0) \
+ _ (0x0D, MAX_PUSH_ID, 1, 0, 0)
+
+typedef enum
+{
+#define _(value, label, ctrl_stream, req_stream, push_stream) \
+ HTTP3_FRAME_TYPE_##label = value,
+ foreach_http3_frame_type
+#undef _
+} http3_frame_type_t;
+
+typedef struct
+{
+ u64 type;
+ u64 length;
+ u8 *payload;
+} http3_frame_header_t;
+
+/**
+ * Parse frame header
+ *
+ * @param src Pointer to the beginning of the frame
+ * @param src_len Length of data available for parsing
+ * @param stream_type Current stream type
+ * @param fh Parsed frame header
+ *
+ * @return @c HTTP3_ERROR_NO_ERROR on success
+ */
+http3_error_t http3_frame_header_read (u8 *src, u64 src_len,
+ http3_stream_type_t stream_type,
+ http3_frame_header_t *fh);
+
+/**
+ * Write frame header
+ * @param type Frame type
+ * @param length Frame payload length
+ * @param dst Buffer pointer where frame header will be written
+ *
+ * @return Frame header length
+ */
+always_inline u8
+http3_frame_header_write (http3_frame_type_t type, u64 length, u8 *dst)
+{
+ *dst++ = (u8) type;
+ http_encode_varint (dst, length);
+ return http_varint_len (length) + 1;
+}
+
+/**
+ * Parse GOAWAY frame payload
+ *
+ * @param payload Payload to parse
+ * @param len Payload length
+ * @param stream_or_push_id Parsed stream ID or push ID
+ *
+ * @return @c HTTP3_ERROR_NO_ERROR on success
+ */
+http3_error_t http3_frame_goaway_read (u8 *payload, u64 len,
+ u64 *stream_or_push_id);
+
+/**
+ * Write GOAWAY frame to the end of given vector
+ *
+ * @param stream_or_push_id Stream ID or push ID
+ * @param dst Vector where GOAWAY frame will be written
+ */
+void http3_frame_goaway_write (u64 stream_or_push_id, u8 **dst);
+
+/**
+ * Parse SETTINGS frame payload
+ *
+ * @param payload Payload to parse
+ * @param len Payload length
+ * @param settings HTTP/3 settings where parsed values are stored
+ *
+ * @return @c HTTP3_ERROR_NO_ERROR on success
+ */
+http3_error_t http3_frame_settings_read (u8 *payload, u64 len,
+ http3_conn_settings_t *settings);
+
+/**
+ * Write SETTINGS frame to the end of given vector
+ *
+ * @param settings HTTP/3 settings used for payload
+ * @param dst Vector where SETTINGS frame will be written
+ */
+void http3_frame_settings_write (http3_conn_settings_t *settings, u8 **dst);
+
+#endif /* SRC_PLUGINS_HTTP_HTTP3_FRAME_H_ */
#include <vppinfra/format.h>
#include <vppinfra/types.h>
+#include <http/http.h>
#define foreach_http3_errors \
_ (NO_ERROR, "NO_ERROR", 0x0100) \
_ (VERSION_FALLBACK, "VERSION_FALLBACK", 0x0110) \
_ (QPACK_DECOMPRESSION_FAILED, "QPACK_DECOMPRESSION_FAILED", 0x0200) \
_ (QPACK_ENCODER_STREAM_ERROR, "QPACK_ENCODER_STREAM_ERROR", 0x0201) \
- _ (QPACK_DECODER_STREAM_ERROR, "QPACK_DECODER_STREAM_ERROR", 0x0202)
+ _ (QPACK_DECODER_STREAM_ERROR, "QPACK_DECODER_STREAM_ERROR", 0x0202) \
+ _ (INCOMPLETE, "INCOMPLETE", -1)
+/* NOTE: negative values are for internal use only */
typedef enum
{
return format (s, "%s", t);
}
+#define HTTP3_SETTING_VALUE_UNLIMITED (((u64) 1) << 62)
+
+/* value, label, member, min, max, default_value, server, client */
+#define foreach_http3_settings \
+ _ (0x01, QPACK_MAX_TABLE_CAPACITY, qpack_max_table_capacity, 0, \
+ HTTP_VARINT_MAX, 0, 1, 1) \
+ _ (0x06, MAX_FIELD_SECTION_SIZE, max_field_section_size, 0, \
+ HTTP_VARINT_MAX, HTTP3_SETTING_VALUE_UNLIMITED, 1, 1) \
+ _ (0x07, QPACK_BLOCKED_STREAMS, qpack_blocked_streams, 0, HTTP_VARINT_MAX, \
+ 0, 1, 1) \
+ _ (0x08, ENABLE_CONNECT_PROTOCOL, enable_connect_protocol, 0, 1, 0, 1, 0) \
+ _ (0x33, H3_DATAGRAM, h3_datagram, 0, 1, 0, 1, 1)
+
+typedef enum
+{
+#define _(value, label, member, min, max, default_value, server, client) \
+ HTTP3_SETTINGS_##label = value,
+ foreach_http3_settings
+#undef _
+} http3_settings_t;
+
+typedef struct
+{
+#define _(value, label, member, min, max, default_value, server, client) \
+ u64 member;
+ foreach_http3_settings
+#undef _
+} http3_conn_settings_t;
+
+static const http3_conn_settings_t http3_default_conn_settings = {
+#define _(value, label, member, min, max, default_value, server, client) \
+ default_value,
+ foreach_http3_settings
+#undef _
+};
+
+typedef u64 http3_stream_type_t;
+
+#define HTTP3_STREAM_TYPE_CONTROL 0x00
+#define HTTP3_STREAM_TYPE_PUSH 0x01
+#define HTTP3_STREAM_TYPE_ENCODER 0x02
+#define HTTP3_STREAM_TYPE_DECODER 0x03
+#define HTTP3_STREAM_TYPE_REQUEST (((u64) 1) << 62) /* internal use only */
+
#endif /* SRC_PLUGINS_HTTP_HTTP3_H_ */
#include <http/http2/hpack.h>
#include <http/http2/frame.h>
#include <http/http3/qpack.h>
+#include <http/http3/frame.h>
#define HTTP_TEST_I(_cond, _comment, _args...) \
({ \
return 0;
}
+static int
+http_test_h3_frame (vlib_main_t *vm)
+{
+ vlib_cli_output (vm, "http3_frame_header_read");
+
+ static http3_error_t (*_http3_frame_header_read) (
+ u8 * src, u64 src_len, http3_stream_type_t stream_type,
+ http3_frame_header_t * fh);
+ _http3_frame_header_read =
+ vlib_get_plugin_symbol ("http_plugin.so", "http3_frame_header_read");
+
+ http3_error_t rv;
+ http3_frame_header_t fh = {};
+ u8 data[] = {
+ 0x00, 0x09, 0x6e, 0x6f, 0x74, 0x20, 0x66, 0x6f, 0x75, 0x6e, 0x64,
+ };
+ rv = _http3_frame_header_read (data, sizeof (data),
+ HTTP3_STREAM_TYPE_REQUEST, &fh);
+ HTTP_TEST ((rv == HTTP3_ERROR_NO_ERROR && fh.type == HTTP3_FRAME_TYPE_DATA &&
+ fh.length == 9 && fh.payload[0] == data[2]),
+ "frame identified as DATA");
+
+ rv = _http3_frame_header_read (data, 1, HTTP3_STREAM_TYPE_REQUEST, &fh);
+ HTTP_TEST ((rv == HTTP3_ERROR_INCOMPLETE), "frame is incomplete (rv=%d)",
+ rv);
+
+ rv = _http3_frame_header_read (data, sizeof (data),
+ HTTP3_STREAM_TYPE_CONTROL, &fh);
+ HTTP_TEST ((rv == HTTP3_ERROR_FRAME_UNEXPECTED),
+ "frame is on wrong stream (rv=0x%04x)", rv);
+
+ vlib_cli_output (vm, "http3_frame_header_write");
+
+ u8 header_buf[HTTP3_FRAME_HEADER_MAX_LEN] = {};
+ u8 header_len =
+ http3_frame_header_write (HTTP3_FRAME_TYPE_DATA, 9, header_buf);
+ HTTP_TEST ((header_len == 2 && !memcmp (header_buf, data, header_len)),
+ "DATA frame header written: %U", format_http_bytes, header_buf,
+ header_len);
+
+ vlib_cli_output (vm, "http3_frame_goaway_read");
+
+ static http3_error_t (*_http3_frame_goaway_read) (u8 * payload, u64 len,
+ u64 * stream_or_push_id);
+ _http3_frame_goaway_read =
+ vlib_get_plugin_symbol ("http_plugin.so", "http3_frame_goaway_read");
+
+ u8 goaway[] = {
+ 0x07, 0x02, 0x7B, 0xBD, 0xFE,
+ };
+ u64 stream_id;
+ rv = _http3_frame_header_read (goaway, 4, HTTP3_STREAM_TYPE_CONTROL, &fh);
+ HTTP_TEST ((rv == HTTP3_ERROR_NO_ERROR &&
+ fh.type == HTTP3_FRAME_TYPE_GOAWAY && fh.length == 2 &&
+ fh.payload[0] == goaway[2]),
+ "frame identified as GOAWAY");
+ rv = _http3_frame_goaway_read (fh.payload, fh.length, &stream_id);
+ HTTP_TEST ((rv == HTTP3_ERROR_NO_ERROR && stream_id == 15293),
+ "GOAWAY stream ID %lu (rv=0x%04x)", stream_id, rv);
+
+ rv = _http3_frame_goaway_read (fh.payload, 1, &stream_id);
+ HTTP_TEST ((rv == HTTP3_ERROR_FRAME_ERROR),
+ "GOAWAY frame payload is corrupted (rv=0x%04x)", rv);
+
+ rv = _http3_frame_goaway_read (fh.payload, 3, &stream_id);
+ HTTP_TEST ((rv == HTTP3_ERROR_FRAME_ERROR),
+ "GOAWAY frame payload has extra bytes (rv=0x%04x)", rv);
+
+ vlib_cli_output (vm, "http3_frame_goaway_write");
+ static void (*_http3_frame_goaway_write) (u64 stream_or_push_id, u8 * *dst);
+ _http3_frame_goaway_write =
+ vlib_get_plugin_symbol ("http_plugin.so", "http3_frame_goaway_write");
+
+ u8 *buf = 0;
+ _http3_frame_goaway_write (15293, &buf);
+ HTTP_TEST ((vec_len (buf) == 4 && !memcmp (buf, goaway, 4)),
+ "GOAWAY frame written: %U", format_hex_bytes, buf, vec_len (buf));
+ vec_reset_length (buf);
+
+ vlib_cli_output (vm, "http3_frame_settings_read");
+
+ static http3_error_t (*_http3_frame_settings_read) (
+ u8 * payload, u64 len, http3_conn_settings_t * settings);
+
+ _http3_frame_settings_read =
+ vlib_get_plugin_symbol ("http_plugin.so", "http3_frame_settings_read");
+
+ http3_conn_settings_t h3_settings = {};
+ u8 settings[] = {
+ 0x04, 0x04, 0x07, 0x05, 0x08, 0x01, 0x33, 0x02,
+ };
+ rv = _http3_frame_header_read (settings, 6, HTTP3_STREAM_TYPE_CONTROL, &fh);
+ HTTP_TEST ((rv == HTTP3_ERROR_NO_ERROR &&
+ fh.type == HTTP3_FRAME_TYPE_SETTINGS && fh.length == 4 &&
+ fh.payload[0] == settings[2]),
+ "frame identified as SETTINGS");
+ rv = _http3_frame_settings_read (fh.payload, fh.length, &h3_settings);
+ HTTP_TEST (
+ (rv == HTTP3_ERROR_NO_ERROR && h3_settings.qpack_blocked_streams == 5 &&
+ h3_settings.enable_connect_protocol == 1 &&
+ h3_settings.h3_datagram == 0 && h3_settings.max_field_section_size == 0 &&
+ h3_settings.qpack_max_table_capacity == 0),
+ "SETTINGS frame payload parsed (rv=0x%04x)", rv);
+ rv = _http3_frame_settings_read (fh.payload, 6, &h3_settings);
+ HTTP_TEST ((rv == HTTP3_ERROR_SETTINGS_ERROR),
+ "invalid setting value (rv=0x%04x)", rv);
+
+ vlib_cli_output (vm, "http3_frame_settings_write");
+
+ static void (*_http3_frame_settings_write) (http3_conn_settings_t * settings,
+ u8 * *dst);
+ _http3_frame_settings_write =
+ vlib_get_plugin_symbol ("http_plugin.so", "http3_frame_settings_write");
+ h3_settings = http3_default_conn_settings;
+ h3_settings.enable_connect_protocol = 1;
+ h3_settings.qpack_blocked_streams = 5;
+ _http3_frame_settings_write (&h3_settings, &buf);
+ HTTP_TEST ((vec_len (buf) == 6 && !memcmp (buf, settings, 6)),
+ "SETTINGS frame written: %U", format_hex_bytes, buf,
+ vec_len (buf));
+
+ vec_free (buf);
+ return 0;
+}
+
static clib_error_t *
test_http_command_fn (vlib_main_t *vm, unformat_input_t *input,
vlib_cli_command_t *cmd)
res = http_test_h2_frame (vm);
else if (unformat (input, "qpack"))
res = http_test_qpack (vm);
+ else if (unformat (input, "h3-frame"))
+ res = http_test_h3_frame (vm);
else if (unformat (input, "all"))
{
if ((res = http_test_parse_authority (vm)))
goto done;
if ((res = http_test_qpack (vm)))
goto done;
+ if ((res = http_test_h3_frame (vm)))
+ goto done;
}
else
break;