aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/http/http2/frame.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/http/http2/frame.c')
-rw-r--r--src/plugins/http/http2/frame.c323
1 files changed, 323 insertions, 0 deletions
diff --git a/src/plugins/http/http2/frame.c b/src/plugins/http/http2/frame.c
new file mode 100644
index 00000000000..577bb6c1e3b
--- /dev/null
+++ b/src/plugins/http/http2/frame.c
@@ -0,0 +1,323 @@
+/* SPDX-License-Identifier: Apache-2.0
+ * Copyright(c) 2025 Cisco Systems, Inc.
+ */
+
+#include <vppinfra/string.h>
+#include <http/http2/frame.h>
+
+#define MAX_U24 0xFFFFFF
+
+static_always_inline u8 *
+http2_decode_u24 (u8 *src, u32 *value)
+{
+ *value = 0;
+ *value = (u32) (src[0] << 16) | (u32) (src[1] << 8) | (u32) src[2];
+ return src + 3;
+}
+
+static_always_inline u8 *
+http2_encode_u24 (u8 *dst, u32 value)
+{
+ ASSERT (value <= MAX_U24);
+ *dst++ = (value >> 16) & 0xFF;
+ *dst++ = (value >> 8) & 0xFF;
+ *dst++ = value & 0xFF;
+ return dst;
+}
+
+/*
+ * RFC9113 section 4.1
+ *
+ * HTTP Frame {
+ * Length (24),
+ * Type (8),
+ * Flags (8),
+ * Reserved (1),
+ * Stream Identifier (31),
+ * Frame Payload (..),
+ * }
+ */
+
+__clib_export void
+http2_frame_header_read (u8 *src, http2_frame_header_t *fh)
+{
+ u32 *stream_id;
+ src = http2_decode_u24 (src, &fh->length);
+ fh->type = *src++;
+ fh->flags = *src++;
+ stream_id = (u32 *) src;
+ fh->stream_id = clib_net_to_host_u32 (*stream_id) & 0x7FFFFFFF;
+}
+
+static void
+http2_frame_header_write (http2_frame_header_t *fh, u8 *dst)
+{
+ u32 stream_id;
+
+ dst = http2_encode_u24 (dst, fh->length);
+ *dst++ = fh->type;
+ *dst++ = fh->flags;
+ stream_id = clib_host_to_net_u32 (fh->stream_id);
+ clib_memcpy_fast (dst, &stream_id, sizeof (stream_id));
+}
+
+__clib_export http2_error_t
+http2_frame_read_settings (http2_conn_settings_t *settings, u8 *payload,
+ u32 payload_len)
+{
+ http2_settings_entry_t *entry;
+ u32 value;
+
+ while (payload_len >= sizeof (*entry))
+ {
+ entry = (http2_settings_entry_t *) payload;
+ switch (clib_net_to_host_u16 (entry->identifier))
+ {
+#define _(v, label, member, min, max, default_value, err_code) \
+ case HTTP2_SETTINGS_##label: \
+ value = clib_net_to_host_u32 (entry->value); \
+ if (!(value >= min && value <= max)) \
+ return err_code; \
+ settings->member = value; \
+ break;
+ foreach_http2_settings
+#undef _
+ /* ignore unknown or unsupported identifier */
+ default : break;
+ }
+ payload_len -= sizeof (*entry);
+ payload += sizeof (*entry);
+ }
+
+ if (payload_len != 0)
+ return HTTP2_ERROR_FRAME_SIZE_ERROR;
+
+ return HTTP2_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http2_frame_write_settings_ack (u8 **dst)
+{
+ http2_frame_header_t fh = { .flags = HTTP2_FRAME_FLAG_ACK,
+ .type = HTTP2_FRAME_TYPE_SETTINGS };
+ u8 *p = http2_frame_header_alloc (dst);
+ http2_frame_header_write (&fh, p);
+}
+
+__clib_export void
+http2_frame_write_settings (http2_settings_entry_t *settings, u8 **dst)
+{
+ u8 *p;
+ u32 length;
+ http2_settings_entry_t *entry, e;
+
+ ASSERT (settings);
+ ASSERT (vec_len (settings) > 0);
+
+ length = vec_len (settings) * sizeof (*entry);
+ http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_SETTINGS,
+ .length = length };
+ p = http2_frame_header_alloc (dst);
+ http2_frame_header_write (&fh, p);
+
+ vec_add2 (*dst, p, length);
+ vec_foreach (entry, settings)
+ {
+ e.identifier = clib_host_to_net_u16 (entry->identifier);
+ e.value = clib_host_to_net_u32 (entry->value);
+ clib_memcpy_fast (p, &e, sizeof (e));
+ p += sizeof (e);
+ }
+}
+
+#define WINDOW_UPDATE_LENGTH 4
+
+__clib_export http2_error_t
+http2_frame_read_window_update (u32 *increment, u8 *payload, u32 payload_len)
+{
+ u32 *value;
+
+ if (payload_len != WINDOW_UPDATE_LENGTH)
+ return HTTP2_ERROR_FRAME_SIZE_ERROR;
+
+ value = (u32 *) payload;
+
+ if (value == 0)
+ return HTTP2_ERROR_PROTOCOL_ERROR;
+
+ *increment = clib_net_to_host_u32 (*value) & 0x7FFFFFFF;
+ return HTTP2_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http2_frame_write_window_update (u32 increment, u32 stream_id, u8 **dst)
+{
+ u8 *p;
+ u32 value;
+
+ ASSERT (increment > 0 && increment <= 0x7FFFFFFF);
+
+ http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_WINDOW_UPDATE,
+ .length = WINDOW_UPDATE_LENGTH,
+ .stream_id = stream_id };
+ p = http2_frame_header_alloc (dst);
+ http2_frame_header_write (&fh, p);
+
+ vec_add2 (*dst, p, WINDOW_UPDATE_LENGTH);
+ value = clib_host_to_net_u32 (increment);
+ clib_memcpy_fast (p, &value, WINDOW_UPDATE_LENGTH);
+}
+
+#define RST_STREAM_LENGTH 4
+
+__clib_export http2_error_t
+http2_frame_read_rst_stream (u32 *error_code, u8 *payload, u32 payload_len)
+{
+ u32 *value;
+
+ if (payload_len != RST_STREAM_LENGTH)
+ return HTTP2_ERROR_FRAME_SIZE_ERROR;
+
+ value = (u32 *) payload;
+
+ *error_code = clib_net_to_host_u32 (*value);
+ return HTTP2_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http2_frame_write_rst_stream (http2_error_t error_code, u32 stream_id,
+ u8 **dst)
+{
+ u8 *p;
+ u32 value;
+
+ ASSERT (stream_id > 0 && stream_id <= 0x7FFFFFFF);
+
+ http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_RST_STREAM,
+ .length = RST_STREAM_LENGTH,
+ .stream_id = stream_id };
+ p = http2_frame_header_alloc (dst);
+ http2_frame_header_write (&fh, p);
+
+ vec_add2 (*dst, p, RST_STREAM_LENGTH);
+ value = clib_host_to_net_u32 ((u32) error_code);
+ clib_memcpy_fast (p, &value, RST_STREAM_LENGTH);
+}
+
+#define GOAWAY_MIN_SIZE 8
+
+__clib_export http2_error_t
+http2_frame_read_goaway (u32 *error_code, u32 *last_stream_id, u8 *payload,
+ u32 payload_len)
+{
+ u32 *value;
+
+ if (payload_len < GOAWAY_MIN_SIZE)
+ return HTTP2_ERROR_FRAME_SIZE_ERROR;
+
+ value = (u32 *) payload;
+ *last_stream_id = clib_net_to_host_u32 (*value) & 0x7FFFFFFF;
+ payload += 4;
+
+ value = (u32 *) payload;
+ *error_code = clib_net_to_host_u32 (*value);
+
+ /* TODO: Additional Debug Data */
+ return HTTP2_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http2_frame_write_goaway (http2_error_t error_code, u32 last_stream_id,
+ u8 **dst)
+{
+ u8 *p;
+ u32 value;
+
+ ASSERT (last_stream_id > 0 && last_stream_id <= 0x7FFFFFFF);
+
+ http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_GOAWAY,
+ .length = GOAWAY_MIN_SIZE };
+ p = http2_frame_header_alloc (dst);
+ http2_frame_header_write (&fh, p);
+
+ vec_add2 (*dst, p, GOAWAY_MIN_SIZE);
+ value = clib_host_to_net_u32 (last_stream_id);
+ clib_memcpy_fast (p, &value, 4);
+ p += 4;
+ value = clib_host_to_net_u32 ((u32) error_code);
+ clib_memcpy_fast (p, &value, 4);
+ /* TODO: Additional Debug Data */
+}
+
+#define PRIORITY_DATA_LEN 5
+
+__clib_export http2_error_t
+http2_frame_read_headers (u8 **headers, u32 *headers_len, u8 *payload,
+ u32 payload_len, u8 flags)
+{
+ *headers_len = payload_len;
+
+ if (flags & HTTP2_FRAME_FLAG_PADED)
+ {
+ u8 pad_len = *payload++;
+ if ((u32) pad_len >= payload_len)
+ return HTTP2_ERROR_PROTOCOL_ERROR;
+ *headers_len -= pad_len;
+ }
+
+ if (flags & HTTP2_FRAME_FLAG_PRIORITY)
+ {
+ if (*headers_len <= PRIORITY_DATA_LEN)
+ return HTTP2_ERROR_FRAME_SIZE_ERROR;
+ /* just skip, priority scheme defined in RFC7540 is deprecated */
+ *headers_len -= PRIORITY_DATA_LEN;
+ payload += PRIORITY_DATA_LEN;
+ }
+
+ *headers = payload;
+
+ return HTTP2_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http2_frame_write_headers_header (u32 headers_len, u32 stream_id, u8 flags,
+ u8 *dst)
+{
+ ASSERT (stream_id > 0 && stream_id <= 0x7FFFFFFF);
+
+ http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_HEADERS,
+ .length = headers_len,
+ .flags = flags,
+ .stream_id = stream_id };
+ http2_frame_header_write (&fh, dst);
+}
+
+__clib_export http2_error_t
+http2_frame_read_data (u8 **data, u32 *data_len, u8 *payload, u32 payload_len,
+ u8 flags)
+{
+ *data_len = payload_len;
+
+ if (flags & HTTP2_FRAME_FLAG_PADED)
+ {
+ u8 pad_len = *payload++;
+ if ((u32) pad_len >= payload_len)
+ return HTTP2_ERROR_PROTOCOL_ERROR;
+ *data_len -= pad_len;
+ }
+
+ *data = payload;
+ return HTTP2_ERROR_NO_ERROR;
+}
+
+__clib_export void
+http2_frame_write_data_header (u32 data_len, u32 stream_id, u8 flags, u8 *dst)
+{
+ ASSERT (stream_id > 0 && stream_id <= 0x7FFFFFFF);
+
+ http2_frame_header_t fh = { .type = HTTP2_FRAME_TYPE_DATA,
+ .length = data_len,
+ .flags = flags,
+ .stream_id = stream_id };
+ http2_frame_header_write (&fh, dst);
+}