ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
quic_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_quic_frame.cpp
6 * @brief QUIC frame parsing and building - implementation. See pc_quic_frame.h.
7 */
8
10
11#if PC_ENABLE_HTTP3
12
14#include <string.h>
15
16namespace
17{
18// Decode a varint at buf[*pos], advancing *pos. Returns false on truncation.
19bool rd(const uint8_t *buf, size_t len, size_t *pos, uint64_t *v)
20{
21 size_t c = 0;
22 if (!pc_quic_varint_decode(buf + *pos, len - *pos, v, &c))
23 {
24 return false;
25 }
26 *pos += c;
27 return true;
28}
29} // namespace
30
31size_t pc_quic_frame_parse(const uint8_t *buf, size_t len, QuicFrame *out)
32{
33 size_t pos = 0;
34 uint64_t type = 0;
35 if (!rd(buf, len, &pos, &type))
36 {
37 return 0;
38 }
39 out->type = type;
40
41 if (type == QuicFrameType::QUIC_FT_PADDING || type == QuicFrameType::QUIC_FT_PING ||
42 type == QuicFrameType::QUIC_FT_HANDSHAKE_DONE)
43 {
44 return pos;
45 }
46
47 if (type == QuicFrameType::QUIC_FT_ACK || type == QuicFrameType::QUIC_FT_ACK_ECN)
48 {
49 if (!rd(buf, len, &pos, &out->ack.largest) || !rd(buf, len, &pos, &out->ack.delay) ||
50 !rd(buf, len, &pos, &out->ack.range_count) || !rd(buf, len, &pos, &out->ack.first_range))
51 {
52 return 0;
53 }
54 for (uint64_t i = 0; i < out->ack.range_count; i++) // skip Gap + ACK Range Length pairs
55 {
56 uint64_t tmp = 0;
57 if (!rd(buf, len, &pos, &tmp) || !rd(buf, len, &pos, &tmp))
58 {
59 return 0;
60 }
61 }
62 if (type == QuicFrameType::QUIC_FT_ACK_ECN) // skip the three ECN counts
63 {
64 uint64_t tmp = 0;
65 if (!rd(buf, len, &pos, &tmp) || !rd(buf, len, &pos, &tmp) || !rd(buf, len, &pos, &tmp))
66 {
67 return 0;
68 }
69 }
70 return pos;
71 }
72
73 if (type == QuicFrameType::QUIC_FT_CRYPTO)
74 {
75 if (!rd(buf, len, &pos, &out->crypto.offset) || !rd(buf, len, &pos, &out->crypto.length))
76 {
77 return 0;
78 }
79 if (pos + out->crypto.length > len)
80 {
81 return 0;
82 }
83 out->crypto.data = buf + pos;
84 pos += out->crypto.length;
85 return pos;
86 }
87
88 if (type >= QuicFrameType::QUIC_FT_STREAM && type <= 0x0f)
89 {
90 if (!rd(buf, len, &pos, &out->stream.id))
91 {
92 return 0;
93 }
94 out->stream.offset = 0;
95 if (type & QuicStreamFlag::QUIC_STREAM_OFF)
96 {
97 if (!rd(buf, len, &pos, &out->stream.offset))
98 {
99 return 0;
100 }
101 }
102 if (type & QuicStreamFlag::QUIC_STREAM_LEN)
103 {
104 if (!rd(buf, len, &pos, &out->stream.length))
105 {
106 return 0;
107 }
108 }
109 else
110 {
111 out->stream.length = len - pos; // absent Length -> Stream Data runs to the packet end
112 }
113 if (pos + out->stream.length > len)
114 {
115 return 0;
116 }
117 out->stream.data = buf + pos;
118 out->stream.fin = (uint8_t)((type & QuicStreamFlag::QUIC_STREAM_FIN) ? 1 : 0);
119 pos += out->stream.length;
120 return pos;
121 }
122
123 if (type == QuicFrameType::QUIC_FT_MAX_DATA)
124 {
125 if (!rd(buf, len, &pos, &out->max_data.max))
126 {
127 return 0;
128 }
129 return pos;
130 }
131
132 if (type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE || type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE_APP)
133 {
134 out->close.app = (uint8_t)((type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE_APP) ? 1 : 0);
135 out->close.frame_type = 0;
136 if (!rd(buf, len, &pos, &out->close.error_code))
137 {
138 return 0;
139 }
140 if (type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE) // the transport variant carries the triggering frame type
141 {
142 if (!rd(buf, len, &pos, &out->close.frame_type))
143 {
144 return 0;
145 }
146 }
147 if (!rd(buf, len, &pos, &out->close.reason_len))
148 {
149 return 0;
150 }
151 if (pos + out->close.reason_len > len)
152 {
153 return 0;
154 }
155 out->close.reason = buf + pos;
156 pos += out->close.reason_len;
157 return pos;
158 }
159
160 // Frames the server does not act on but MUST still parse so a well-formed frame from a real client is
161 // not rejected as FRAME_ENCODING_ERROR (RFC 9000 sec 12.4: parse the whole grammar even to ignore it).
162 // A real client sends these right after the handshake alongside the first request (MAX_STREAMS,
163 // NEW_CONNECTION_ID, MAX_STREAM_DATA, ...). Consume each frame's fields by its wire shape; the
164 // dispatcher then ignores the type. out->type is already set above.
165 if (type == QuicFrameType::QUIC_FT_MAX_STREAMS_BIDI || type == QuicFrameType::QUIC_FT_MAX_STREAMS_UNI ||
166 type == QuicFrameType::QUIC_FT_DATA_BLOCKED || type == QuicFrameType::QUIC_FT_STREAMS_BLOCKED_BIDI ||
167 type == QuicFrameType::QUIC_FT_STREAMS_BLOCKED_UNI || type == QuicFrameType::QUIC_FT_RETIRE_CONNECTION_ID)
168 {
169 uint64_t v = 0; // one varint
170 if (!rd(buf, len, &pos, &v))
171 {
172 return 0;
173 }
174 return pos;
175 }
176 if (type == QuicFrameType::QUIC_FT_STOP_SENDING || type == QuicFrameType::QUIC_FT_MAX_STREAM_DATA ||
177 type == QuicFrameType::QUIC_FT_STREAM_DATA_BLOCKED)
178 {
179 uint64_t v = 0; // two varints
180 if (!rd(buf, len, &pos, &v) || !rd(buf, len, &pos, &v))
181 {
182 return 0;
183 }
184 return pos;
185 }
186 if (type == QuicFrameType::QUIC_FT_RESET_STREAM)
187 {
188 uint64_t v = 0; // stream id, app error code, final size
189 if (!rd(buf, len, &pos, &v) || !rd(buf, len, &pos, &v) || !rd(buf, len, &pos, &v))
190 {
191 return 0;
192 }
193 return pos;
194 }
195 if (type == QuicFrameType::QUIC_FT_NEW_TOKEN)
196 {
197 uint64_t tlen = 0; // token length + token bytes
198 if (!rd(buf, len, &pos, &tlen) || pos + tlen > len)
199 {
200 return 0;
201 }
202 pos += tlen;
203 return pos;
204 }
205 if (type == QuicFrameType::QUIC_FT_NEW_CONNECTION_ID)
206 {
207 uint64_t seq = 0; // sequence number
208 uint64_t retire = 0; // retire-prior-to, then a 1-byte CID length follows
209 if (!rd(buf, len, &pos, &seq) || !rd(buf, len, &pos, &retire) || pos >= len)
210 {
211 return 0;
212 }
213 uint8_t cidlen = buf[pos++];
214 if (pos + (size_t)cidlen + 16 > len) // connection id + 16-byte stateless reset token
215 {
216 return 0;
217 }
218 pos += (size_t)cidlen + 16;
219 return pos;
220 }
221 if (type == QuicFrameType::QUIC_FT_PATH_CHALLENGE || type == QuicFrameType::QUIC_FT_PATH_RESPONSE)
222 {
223 if (pos + 8 > len) // 8 bytes of opaque data
224 {
225 return 0;
226 }
227 pos += 8;
228 return pos;
229 }
230
231 return 0; // a genuinely unknown / reserved frame type
232}
233
234size_t pc_quic_build_padding(uint8_t *out, size_t cap, size_t n)
235{
236 if (n > cap)
237 {
238 return 0;
239 }
240 memset(out, 0, n);
241 return n;
242}
243
244size_t pc_quic_build_ping(uint8_t *out, size_t cap)
245{
246 if (cap < 1)
247 {
248 return 0;
249 }
250 out[0] = QuicFrameType::QUIC_FT_PING;
251 return 1;
252}
253
254size_t pc_quic_build_handshake_done(uint8_t *out, size_t cap)
255{
256 if (cap < 1)
257 {
258 return 0;
259 }
260 out[0] = QuicFrameType::QUIC_FT_HANDSHAKE_DONE;
261 return 1;
262}
263
264namespace
265{
266// Append a varint; returns false on overflow.
267bool wr(uint8_t *out, size_t cap, size_t *pos, uint64_t v)
268{
269 size_t c = pc_quic_varint_encode(out + *pos, cap - *pos, v);
270 if (!c)
271 {
272 return false;
273 }
274 *pos += c;
275 return true;
276}
277} // namespace
278
279size_t pc_quic_build_ack(uint8_t *out, size_t cap, uint64_t largest, uint64_t delay, uint64_t first_range)
280{
281 size_t pos = 0;
282 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_ACK) || !wr(out, cap, &pos, largest) || !wr(out, cap, &pos, delay) ||
283 !wr(out, cap, &pos, 0) /* ACK Range Count */ || !wr(out, cap, &pos, first_range))
284 {
285 return 0;
286 }
287 return pos;
288}
289
290size_t pc_quic_build_crypto(uint8_t *out, size_t cap, uint64_t offset, const uint8_t *data, size_t len)
291{
292 size_t pos = 0;
293 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_CRYPTO) || !wr(out, cap, &pos, offset) || !wr(out, cap, &pos, len))
294 {
295 return 0;
296 }
297 if (pos + len > cap)
298 {
299 return 0;
300 }
301 if (len)
302 {
303 memcpy(out + pos, data, len);
304 }
305 return pos + len;
306}
307
308size_t pc_quic_build_stream(uint8_t *out, size_t cap, uint64_t id, uint64_t offset, const uint8_t *data, size_t len,
309 bool fin)
310{
311 uint64_t type = QuicFrameType::QUIC_FT_STREAM | QuicStreamFlag::QUIC_STREAM_LEN |
312 (offset ? QuicStreamFlag::QUIC_STREAM_OFF : 0) | (fin ? QuicStreamFlag::QUIC_STREAM_FIN : 0);
313 size_t pos = 0;
314 if (!wr(out, cap, &pos, type) || !wr(out, cap, &pos, id))
315 {
316 return 0;
317 }
318 if (offset && !wr(out, cap, &pos, offset))
319 {
320 return 0;
321 }
322 if (!wr(out, cap, &pos, len))
323 {
324 return 0;
325 }
326 if (pos + len > cap)
327 {
328 return 0;
329 }
330 if (len)
331 {
332 memcpy(out + pos, data, len);
333 }
334 return pos + len;
335}
336
337size_t pc_quic_build_max_data(uint8_t *out, size_t cap, uint64_t max)
338{
339 size_t pos = 0;
340 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_MAX_DATA) || !wr(out, cap, &pos, max))
341 {
342 return 0;
343 }
344 return pos;
345}
346
347size_t pc_quic_build_connection_close(uint8_t *out, size_t cap, uint64_t error_code, uint64_t frame_type,
348 const char *reason, size_t reason_len)
349{
350 size_t pos = 0;
351 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_CONNECTION_CLOSE) || !wr(out, cap, &pos, error_code) ||
352 !wr(out, cap, &pos, frame_type) || !wr(out, cap, &pos, reason_len))
353 {
354 return 0;
355 }
356 if (pos + reason_len > cap)
357 {
358 return 0;
359 }
360 if (reason_len)
361 {
362 memcpy(out + pos, reason, reason_len);
363 }
364 return pos + reason_len;
365}
366
367#endif // PC_ENABLE_HTTP3