ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
h2_frame.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file protocore_h2_frame.h
6 * @brief HTTP/2 binary framing (RFC 9113 sec 4 + sec 6).
7 *
8 * Every HTTP/2 frame is a 9-byte header (24-bit length, 8-bit type, 8-bit flags, 1 reserved bit
9 * + 31-bit stream id) followed by a type-specific payload. This module parses that header and
10 * builds the frames the server sends (SETTINGS + its ACK, WINDOW_UPDATE, RST_STREAM, GOAWAY,
11 * PING ACK, HEADERS, DATA) and reads a SETTINGS payload. Pure and host-tested; no I/O.
12 *
13 * @author Douglas Quigg (dstroy0)
14 * @date 2026
15 */
16
17#ifndef PROTOCORE_H2_FRAME_H
18#define PROTOCORE_H2_FRAME_H
19
20#include "protocore_config.h" // the entry point: the enable gate below, and the widths
21
22#if PROTOCORE_ENABLE_HTTP2
23
25
26/** @brief The client connection preface that opens every HTTP/2 connection (RFC 9113 sec 3.4). */
27#define H2_PREFACE "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
28#define H2_PREFACE_LEN 24
29#define H2_FRAME_HEADER_LEN 9
30
31/** @brief Frame types (RFC 9113 sec 6). */
32// Frame type octet (RFC 9113 sec 6): wire values compared against a parsed type byte, so integer
33// constants in a namespacing struct.
34#define H2_DATA 0x0
35#define H2_HEADERS 0x1
36#define H2_PRIORITY 0x2
37#define H2_RST_STREAM 0x3
38#define H2_SETTINGS 0x4
39#define H2_PUSH_PROMISE 0x5
40#define H2_PING 0x6
41#define H2_GOAWAY 0x7
42#define H2_WINDOW_UPDATE 0x8
43#define H2_CONTINUATION 0x9
44
45/** @brief Frame flags (meaning is per-type; RFC 9113 sec 6). */
46#define H2_FLAG_END_STREAM 0x01 ///< DATA / HEADERS
47#define H2_FLAG_ACK 0x01 ///< SETTINGS / PING
48#define H2_FLAG_END_HEADERS 0x04 ///< HEADERS / CONTINUATION / PUSH_PROMISE
49#define H2_FLAG_PADDED 0x08 ///< DATA / HEADERS / PUSH_PROMISE
50#define H2_FLAG_PRIORITY 0x20 ///< HEADERS
51
52/** @brief SETTINGS parameter identifiers (RFC 9113 sec 6.5.2; the 16-bit wire id). */
53#define H2_SETTINGS_HEADER_TABLE_SIZE 0x1
54#define H2_SETTINGS_ENABLE_PUSH 0x2
55#define H2_SETTINGS_MAX_CONCURRENT_STREAMS 0x3
56#define H2_SETTINGS_INITIAL_WINDOW_SIZE 0x4
57#define H2_SETTINGS_MAX_FRAME_SIZE 0x5
58#define H2_SETTINGS_MAX_HEADER_LIST_SIZE 0x6
59
60/** @brief Error codes (RFC 9113 sec 7; the 32-bit wire code). */
61#define H2_NO_ERROR 0x0
62#define H2_PROTOCOL_ERROR 0x1
63#define H2_INTERNAL_ERROR 0x2
64#define H2_FLOW_CONTROL_ERROR 0x3
65#define H2_SETTINGS_TIMEOUT 0x4
66#define H2_STREAM_CLOSED 0x5
67#define H2_FRAME_SIZE_ERROR 0x6
68#define H2_REFUSED_STREAM 0x7
69#define H2_CANCEL 0x8
70#define H2_COMPRESSION_ERROR 0x9
71#define H2_CONNECT_ERROR 0xa
72#define H2_ENHANCE_YOUR_CALM 0xb
73#define H2_INADEQUATE_SECURITY 0xc
74#define H2_HTTP_1_1_REQUIRED 0xd
75
76/** @brief A parsed frame header. */
77typedef struct
78{
79 uint32_t length; ///< payload length (24-bit)
80 uint8_t type; ///< frame type
81 uint8_t flags; ///< frame flags
82 uint32_t stream_id; ///< stream identifier (reserved bit cleared)
83} H2FrameHeader;
84
85/** @brief The six settings we track, with RFC defaults after ::H2FrameNs::settings_defaults. */
86typedef struct
87{
88 uint32_t header_table_size; ///< default 4096
89 uint32_t enable_push; ///< default 1
90 uint32_t max_concurrent_streams; ///< default "unlimited" (0xFFFFFFFF here)
91 uint32_t initial_window_size; ///< default 65535
92 uint32_t max_frame_size; ///< default 16384
93 uint32_t max_header_list_size; ///< default "unlimited" (0xFFFFFFFF here)
94} H2Settings;
95
96/** @brief RFC 9113 sec 4.1: the 9-octet header a parse reads. */
97typedef struct
98{
99 const uint8_t *buf; ///< the bytes to read
100 size_t len; ///< how many are available
101} H2FrameParseArgs;
102
103/** @brief RFC 9113 sec 4.1: the 9-octet header a write emits. */
104typedef struct
105{
106 uint8_t *buf; ///< where the header is written
107 size_t cap; ///< how much room it has
108 uint32_t length; ///< the payload length it declares
109 uint8_t type; ///< the frame type
110 uint8_t flags; ///< the frame flags
111 uint32_t stream_id; ///< the stream it belongs to
112} H2FrameWriteArgs;
113
114/** @brief The settings block a defaults fill or a parse applies to. */
115typedef struct
116{
117 const uint8_t *payload; ///< the SETTINGS payload to apply; unused by a defaults fill
118 size_t len; ///< how many bytes it carries
119 H2Settings *s; ///< the block written into
120} H2FrameSettingsArgs;
121
122/** @brief RFC 9113 sec 6.5: the (id, value) pairs a SETTINGS frame carries. */
123typedef struct
124{
125 uint8_t *buf; ///< where the frame is written
126 size_t cap; ///< how much room it has
127 const uint16_t *ids; ///< the parameter ids
128 const uint32_t *vals; ///< their values
129 size_t n; ///< how many pairs
130} H2FrameBuildSettingsArgs;
131
132/** @brief Where a frame that carries nothing else is written. */
133typedef struct
134{
135 uint8_t *buf; ///< where the frame is written
136 size_t cap; ///< how much room it has
137} H2FrameAckArgs;
138
139/** @brief RFC 9113 sec 6.9: the increment a WINDOW_UPDATE carries. */
140typedef struct
141{
142 uint8_t *buf; ///< where the frame is written
143 size_t cap; ///< how much room it has
144 uint32_t stream_id; ///< the stream it credits
145 uint32_t increment; ///< the 31-bit credit
146} H2FrameWindowArgs;
147
148/** @brief RFC 9113 sec 6.4: the error a RST_STREAM carries. */
149typedef struct
150{
151 uint8_t *buf; ///< where the frame is written
152 size_t cap; ///< how much room it has
153 uint32_t stream_id; ///< the stream it resets
154 uint32_t error; ///< the code it reports
155} H2FrameRstArgs;
156
157/** @brief RFC 9113 sec 6.8: what a GOAWAY reports. */
158typedef struct
159{
160 uint8_t *buf; ///< where the frame is written
161 size_t cap; ///< how much room it has
162 uint32_t last_stream_id; ///< the highest stream that will be processed
163 uint32_t error; ///< the code it reports
164} H2FrameGoawayArgs;
165
166/** @brief RFC 9113 sec 6.7: the opaque data a PING ACK echoes. */
167typedef struct
168{
169 uint8_t *buf; ///< where the frame is written
170 size_t cap; ///< how much room it has
171 const uint8_t *opaque; ///< the 8 octets echoed back
172} H2FramePingArgs;
173
174/** @brief RFC 9113 sec 6.2: the HPACK block a HEADERS frame carries. */
175typedef struct
176{
177 uint8_t *buf; ///< where the frame is written
178 size_t cap; ///< how much room it has
179 uint32_t stream_id; ///< the stream it opens
180 const uint8_t *block; ///< the HPACK block
181 size_t block_len; ///< how many bytes
182 proto_bool end_stream; ///< whether it closes the stream
183} H2FrameHeadersArgs;
184
185/** @brief RFC 9113 sec 6.1: the payload a DATA frame carries. */
186typedef struct
187{
188 uint8_t *buf; ///< where the frame is written
189 size_t cap; ///< how much room it has
190 uint32_t stream_id; ///< the stream it belongs to
191 const uint8_t *data; ///< the payload
192 size_t data_len; ///< how many bytes
193 proto_bool end_stream; ///< whether it closes the stream
194} H2FrameDataArgs;
195
196/**
197 * @brief HTTP/2 frame headers, settings and builders (RFC 9113 sec 4 and 6).
198 *
199 * A caller sets the members a call takes, invokes it through ::H2Frame, and reads the outcome off
200 * the same handle.
201 *
202 * @var H2FrameNs::parse_args the bytes a header parse reads
203 * @var H2FrameNs::write_args what a header write emits
204 * @var H2FrameNs::settings_args the block a defaults fill or a settings parse writes
205 * @var H2FrameNs::build_settings_args the pairs a SETTINGS frame carries
206 * @var H2FrameNs::ack_args where a frame carrying nothing else is written
207 * @var H2FrameNs::window_args the increment a WINDOW_UPDATE carries
208 * @var H2FrameNs::rst_args the error a RST_STREAM carries
209 * @var H2FrameNs::goaway_args what a GOAWAY reports
210 * @var H2FrameNs::ping_args the opaque data a PING ACK echoes
211 * @var H2FrameNs::headers_args the HPACK block a HEADERS frame carries
212 * @var H2FrameNs::data_args the payload a DATA frame carries
213 * @var H2FrameNs::ok whether a parse read a well-formed frame
214 * @var H2FrameNs::n bytes a build wrote, or 0 on overflow
215 * @var H2FrameNs::header the header a parse read
216 * @var H2FrameNs::parse_header read the 9-octet header
217 * @var H2FrameNs::write_header write the 9-octet header
218 * @var H2FrameNs::settings_defaults fill a block with the RFC defaults
219 * @var H2FrameNs::parse_settings apply a SETTINGS payload to a block
220 * @var H2FrameNs::build_settings a SETTINGS frame from (id, value) pairs
221 * @var H2FrameNs::build_settings_ack an empty SETTINGS with the ACK flag
222 * @var H2FrameNs::build_window_update a WINDOW_UPDATE
223 * @var H2FrameNs::build_rst_stream a RST_STREAM
224 * @var H2FrameNs::build_goaway a GOAWAY
225 * @var H2FrameNs::build_ping_ack a PING with the ACK flag
226 * @var H2FrameNs::build_headers a HEADERS frame
227 * @var H2FrameNs::build_data a DATA frame
228 *
229 * Every entry takes a borrow to keep one calling convention across the tree; this module reads and
230 * writes only the caller's buffers, so nothing is read through it.
231 */
232typedef struct
233{
234 H2FrameParseArgs parse_args; ///< the members ::H2FrameNs::parse_header takes
235 H2FrameWriteArgs write_args; ///< the members ::H2FrameNs::write_header takes
236 H2FrameSettingsArgs settings_args; ///< the members the settings calls take
237 H2FrameBuildSettingsArgs build_settings_args; ///< the members ::H2FrameNs::build_settings takes
238 H2FrameAckArgs ack_args; ///< the members ::H2FrameNs::build_settings_ack takes
239 H2FrameWindowArgs window_args; ///< the members ::H2FrameNs::build_window_update takes
240 H2FrameRstArgs rst_args; ///< the members ::H2FrameNs::build_rst_stream takes
241 H2FrameGoawayArgs goaway_args; ///< the members ::H2FrameNs::build_goaway takes
242 H2FramePingArgs ping_args; ///< the members ::H2FrameNs::build_ping_ack takes
243 H2FrameHeadersArgs headers_args; ///< the members ::H2FrameNs::build_headers takes
244 H2FrameDataArgs data_args; ///< the members ::H2FrameNs::build_data takes
245 proto_bool ok; ///< whether a parse read a well-formed frame
246 size_t n; ///< bytes a build wrote, or 0 on overflow
247 H2FrameHeader header; ///< the header a parse read
248} H2FrameVars;
249
250/** @brief The operands and the outcome. */
251extern H2FrameVars H2FrameV;
252
253/** @brief The entries. */
254typedef struct
255{
256 void (*const parse_header)(uint8_t *work);
257 void (*const write_header)(uint8_t *work);
258 void (*const settings_defaults)(uint8_t *work);
259 void (*const parse_settings)(uint8_t *work);
260 void (*const build_settings)(uint8_t *work);
261 void (*const build_settings_ack)(uint8_t *work);
262 void (*const build_window_update)(uint8_t *work);
263 void (*const build_rst_stream)(uint8_t *work);
264 void (*const build_goaway)(uint8_t *work);
265 void (*const build_ping_ack)(uint8_t *work);
266 void (*const build_headers)(uint8_t *work);
267 void (*const build_data)(uint8_t *work);
268} H2FrameNs;
269
270// What the table binds, defined once in the .c and taking one parameter each: everything
271// else an entry needs is an operand in H2FrameV or a region of the borrow at a fixed offset.
272void protocore_h2_frame_parse_header(uint8_t *work);
273void protocore_h2_frame_write_header(uint8_t *work);
274void protocore_h2_frame_settings_defaults(uint8_t *work);
275void protocore_h2_frame_parse_settings(uint8_t *work);
276void protocore_h2_frame_build_settings(uint8_t *work);
277void protocore_h2_frame_build_settings_ack(uint8_t *work);
278void protocore_h2_frame_build_window_update(uint8_t *work);
279void protocore_h2_frame_build_rst_stream(uint8_t *work);
280void protocore_h2_frame_build_goaway(uint8_t *work);
281void protocore_h2_frame_build_ping_ack(uint8_t *work);
282void protocore_h2_frame_build_headers(uint8_t *work);
283void protocore_h2_frame_build_data(uint8_t *work);
284
285// `static const`, initialised HERE rather than `extern` against a definition in the .c: a
286// const object whose initializer every translation unit can see is a COMPILE-TIME FACT, so
287// `H2Frame.parse_header(work)` resolves to a named function and becomes a DIRECT call. An extern table
288// leaves the call indirect and the symbol live at every level, -O2 -flto included.
289static const H2FrameNs H2Frame __attribute__((unused)) = {
290 .parse_header = protocore_h2_frame_parse_header,
291 .write_header = protocore_h2_frame_write_header,
292 .settings_defaults = protocore_h2_frame_settings_defaults,
293 .parse_settings = protocore_h2_frame_parse_settings,
294 .build_settings = protocore_h2_frame_build_settings,
295 .build_settings_ack = protocore_h2_frame_build_settings_ack,
296 .build_window_update = protocore_h2_frame_build_window_update,
297 .build_rst_stream = protocore_h2_frame_build_rst_stream,
298 .build_goaway = protocore_h2_frame_build_goaway,
299 .build_ping_ack = protocore_h2_frame_build_ping_ack,
300 .build_headers = protocore_h2_frame_build_headers,
301 .build_data = protocore_h2_frame_build_data,
302};
303
305
306#endif // PROTOCORE_ENABLE_HTTP2
307
308#endif // PROTOCORE_H2_FRAME_H
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97