ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
h2_frame.cpp
Go to the documentation of this file.
1// Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4/**
5 * @file pc_h2_frame.cpp
6 * @brief HTTP/2 binary framing - implementation. See pc_h2_frame.h.
7 */
8
10
11#if PC_ENABLE_HTTP2
12
13#include <string.h>
14
15namespace
16{
17void wr32(uint8_t *p, uint32_t v)
18{
19 p[0] = (uint8_t)(v >> 24);
20 p[1] = (uint8_t)(v >> 16);
21 p[2] = (uint8_t)(v >> 8);
22 p[3] = (uint8_t)v;
23}
24uint32_t rd32(const uint8_t *p)
25{
26 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
27}
28} // namespace
29
30bool pc_h2_parse_header(const uint8_t *buf, size_t len, H2FrameHeader *out)
31{
32 if (len < H2_FRAME_HEADER_LEN)
33 {
34 return false;
35 }
36 out->length = ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | buf[2];
37 out->type = buf[3];
38 out->flags = buf[4];
39 out->stream_id = ((uint32_t)(buf[5] & 0x7f) << 24) | ((uint32_t)buf[6] << 16) | ((uint32_t)buf[7] << 8) |
40 buf[8]; // clear the reserved bit
41 return true;
42}
43
44size_t pc_h2_write_header(uint8_t *out, size_t cap, uint32_t length, uint8_t type, uint8_t flags, uint32_t stream_id)
45{
46 if (cap < H2_FRAME_HEADER_LEN || length > 0xFFFFFF)
47 {
48 return 0;
49 }
50 out[0] = (uint8_t)(length >> 16);
51 out[1] = (uint8_t)(length >> 8);
52 out[2] = (uint8_t)length;
53 out[3] = type;
54 out[4] = flags;
55 out[5] = (uint8_t)((stream_id >> 24) & 0x7f); // reserved bit stays 0
56 out[6] = (uint8_t)(stream_id >> 16);
57 out[7] = (uint8_t)(stream_id >> 8);
58 out[8] = (uint8_t)stream_id;
59 return H2_FRAME_HEADER_LEN;
60}
61
62void pc_h2_settings_defaults(H2Settings *s)
63{
64 s->header_table_size = 4096;
65 s->enable_push = 1;
66 s->max_concurrent_streams = 0xFFFFFFFFu;
67 s->initial_window_size = 65535;
68 s->max_frame_size = 16384;
69 s->max_header_list_size = 0xFFFFFFFFu;
70}
71
72bool pc_h2_parse_settings(const uint8_t *payload, size_t len, H2Settings *s)
73{
74 if (len % 6 != 0) // each entry is 2-byte id + 4-byte value
75 {
76 return false;
77 }
78 for (size_t i = 0; i < len; i += 6)
79 {
80 uint16_t id = (uint16_t)(((uint16_t)payload[i] << 8) | payload[i + 1]);
81 uint32_t val = rd32(payload + i + 2);
82 switch (id)
83 {
84 case H2Setting::H2_SETTINGS_HEADER_TABLE_SIZE:
85 s->header_table_size = val;
86 break;
87 case H2Setting::H2_SETTINGS_ENABLE_PUSH:
88 if (val > 1)
89 {
90 return false; // RFC 9113 sec 6.5.2: must be 0 or 1
91 }
92 s->enable_push = val;
93 break;
94 case H2Setting::H2_SETTINGS_MAX_CONCURRENT_STREAMS:
95 s->max_concurrent_streams = val;
96 break;
97 case H2Setting::H2_SETTINGS_INITIAL_WINDOW_SIZE:
98 if (val > 0x7FFFFFFF)
99 {
100 return false; // must not exceed 2^31-1
101 }
102 s->initial_window_size = val;
103 break;
104 case H2Setting::H2_SETTINGS_MAX_FRAME_SIZE:
105 if (val < 16384 || val > 16777215)
106 {
107 return false; // must be in [2^14, 2^24-1]
108 }
109 s->max_frame_size = val;
110 break;
111 case H2Setting::H2_SETTINGS_MAX_HEADER_LIST_SIZE:
112 s->max_header_list_size = val;
113 break;
114 default:
115 break; // unknown settings are ignored
116 }
117 }
118 return true;
119}
120
121size_t pc_h2_build_settings(uint8_t *out, size_t cap, const uint16_t *ids, const uint32_t *vals, size_t n)
122{
123 size_t payload = n * 6;
124 if (cap < H2_FRAME_HEADER_LEN + payload)
125 {
126 return 0;
127 }
128 pc_h2_write_header(out, cap, (uint32_t)payload, H2FrameType::H2_SETTINGS, 0, 0);
129 uint8_t *p = out + H2_FRAME_HEADER_LEN;
130 for (size_t i = 0; i < n; i++)
131 {
132 p[0] = (uint8_t)(ids[i] >> 8);
133 p[1] = (uint8_t)ids[i];
134 wr32(p + 2, vals[i]);
135 p += 6;
136 }
137 return H2_FRAME_HEADER_LEN + payload;
138}
139
140size_t pc_h2_build_settings_ack(uint8_t *out, size_t cap)
141{
142 return pc_h2_write_header(out, cap, 0, H2FrameType::H2_SETTINGS, H2_FLAG_ACK, 0);
143}
144
145size_t pc_h2_build_window_update(uint8_t *out, size_t cap, uint32_t stream_id, uint32_t increment)
146{
147 if (cap < H2_FRAME_HEADER_LEN + 4)
148 {
149 return 0;
150 }
151 pc_h2_write_header(out, cap, 4, H2FrameType::H2_WINDOW_UPDATE, 0, stream_id);
152 wr32(out + H2_FRAME_HEADER_LEN, increment & 0x7FFFFFFF); // 31-bit, reserved bit 0
153 return H2_FRAME_HEADER_LEN + 4;
154}
155
156size_t pc_h2_build_rst_stream(uint8_t *out, size_t cap, uint32_t stream_id, uint32_t error)
157{
158 if (cap < H2_FRAME_HEADER_LEN + 4)
159 {
160 return 0;
161 }
162 pc_h2_write_header(out, cap, 4, H2FrameType::H2_RST_STREAM, 0, stream_id);
163 wr32(out + H2_FRAME_HEADER_LEN, error);
164 return H2_FRAME_HEADER_LEN + 4;
165}
166
167size_t pc_h2_build_goaway(uint8_t *out, size_t cap, uint32_t last_stream_id, uint32_t error)
168{
169 if (cap < H2_FRAME_HEADER_LEN + 8)
170 {
171 return 0;
172 }
173 pc_h2_write_header(out, cap, 8, H2FrameType::H2_GOAWAY, 0, 0);
174 wr32(out + H2_FRAME_HEADER_LEN, last_stream_id & 0x7FFFFFFF);
175 wr32(out + H2_FRAME_HEADER_LEN + 4, error);
176 return H2_FRAME_HEADER_LEN + 8;
177}
178
179size_t pc_h2_build_ping_ack(uint8_t *out, size_t cap, const uint8_t opaque[8])
180{
181 if (cap < H2_FRAME_HEADER_LEN + 8)
182 {
183 return 0;
184 }
185 pc_h2_write_header(out, cap, 8, H2FrameType::H2_PING, H2_FLAG_ACK, 0);
186 memcpy(out + H2_FRAME_HEADER_LEN, opaque, 8);
187 return H2_FRAME_HEADER_LEN + 8;
188}
189
190size_t pc_h2_build_headers(uint8_t *out, size_t cap, uint32_t stream_id, const uint8_t *block, size_t block_len,
191 bool end_stream)
192{
193 if (cap < H2_FRAME_HEADER_LEN + block_len)
194 {
195 return 0;
196 }
197 uint8_t flags = H2_FLAG_END_HEADERS | (end_stream ? H2_FLAG_END_STREAM : 0);
198 pc_h2_write_header(out, cap, (uint32_t)block_len, H2FrameType::H2_HEADERS, flags, stream_id);
199 memcpy(out + H2_FRAME_HEADER_LEN, block, block_len);
200 return H2_FRAME_HEADER_LEN + block_len;
201}
202
203size_t pc_h2_build_data(uint8_t *out, size_t cap, uint32_t stream_id, const uint8_t *data, size_t data_len,
204 bool end_stream)
205{
206 if (cap < H2_FRAME_HEADER_LEN + data_len)
207 {
208 return 0;
209 }
210 pc_h2_write_header(out, cap, (uint32_t)data_len, H2FrameType::H2_DATA, end_stream ? H2_FLAG_END_STREAM : 0,
211 stream_id);
212 if (data_len)
213 {
214 memcpy(out + H2_FRAME_HEADER_LEN, data, data_len);
215 }
216 return H2_FRAME_HEADER_LEN + data_len;
217}
218
219#endif // PC_ENABLE_HTTP2