DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
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 quic_frame.cpp
6 * @brief QUIC frame parsing and building - implementation. See quic_frame.h.
7 */
8
10
11#if DETWS_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 (!quic_varint_decode(buf + *pos, len - *pos, v, &c))
23 return false;
24 *pos += c;
25 return true;
26}
27} // namespace
28
29size_t quic_frame_parse(const uint8_t *buf, size_t len, QuicFrame *out)
30{
31 size_t pos = 0;
32 uint64_t type = 0;
33 if (!rd(buf, len, &pos, &type))
34 return 0;
35 out->type = type;
36
37 if (type == QuicFrameType::QUIC_FT_PADDING || type == QuicFrameType::QUIC_FT_PING ||
38 type == QuicFrameType::QUIC_FT_HANDSHAKE_DONE)
39 return pos;
40
41 if (type == QuicFrameType::QUIC_FT_ACK || type == QuicFrameType::QUIC_FT_ACK_ECN)
42 {
43 if (!rd(buf, len, &pos, &out->ack.largest) || !rd(buf, len, &pos, &out->ack.delay) ||
44 !rd(buf, len, &pos, &out->ack.range_count) || !rd(buf, len, &pos, &out->ack.first_range))
45 return 0;
46 for (uint64_t i = 0; i < out->ack.range_count; i++) // skip Gap + ACK Range Length pairs
47 {
48 uint64_t tmp = 0;
49 if (!rd(buf, len, &pos, &tmp) || !rd(buf, len, &pos, &tmp))
50 return 0;
51 }
52 if (type == QuicFrameType::QUIC_FT_ACK_ECN) // skip the three ECN counts
53 {
54 uint64_t tmp = 0;
55 if (!rd(buf, len, &pos, &tmp) || !rd(buf, len, &pos, &tmp) || !rd(buf, len, &pos, &tmp))
56 return 0;
57 }
58 return pos;
59 }
60
61 if (type == QuicFrameType::QUIC_FT_CRYPTO)
62 {
63 if (!rd(buf, len, &pos, &out->crypto.offset) || !rd(buf, len, &pos, &out->crypto.length))
64 return 0;
65 if (pos + out->crypto.length > len)
66 return 0;
67 out->crypto.data = buf + pos;
68 pos += out->crypto.length;
69 return pos;
70 }
71
72 if (type >= QuicFrameType::QUIC_FT_STREAM && type <= 0x0f)
73 {
74 if (!rd(buf, len, &pos, &out->stream.id))
75 return 0;
76 out->stream.offset = 0;
77 if (type & QuicStreamFlag::QUIC_STREAM_OFF)
78 if (!rd(buf, len, &pos, &out->stream.offset))
79 return 0;
80 if (type & QuicStreamFlag::QUIC_STREAM_LEN)
81 {
82 if (!rd(buf, len, &pos, &out->stream.length))
83 return 0;
84 }
85 else
86 {
87 out->stream.length = len - pos; // absent Length -> Stream Data runs to the packet end
88 }
89 if (pos + out->stream.length > len)
90 return 0;
91 out->stream.data = buf + pos;
92 out->stream.fin = (uint8_t)((type & QuicStreamFlag::QUIC_STREAM_FIN) ? 1 : 0);
93 pos += out->stream.length;
94 return pos;
95 }
96
97 if (type == QuicFrameType::QUIC_FT_MAX_DATA)
98 {
99 if (!rd(buf, len, &pos, &out->max_data.max))
100 return 0;
101 return pos;
102 }
103
104 if (type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE || type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE_APP)
105 {
106 out->close.app = (uint8_t)((type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE_APP) ? 1 : 0);
107 out->close.frame_type = 0;
108 if (!rd(buf, len, &pos, &out->close.error_code))
109 return 0;
110 if (type == QuicFrameType::QUIC_FT_CONNECTION_CLOSE) // the transport variant carries the triggering frame type
111 if (!rd(buf, len, &pos, &out->close.frame_type))
112 return 0;
113 if (!rd(buf, len, &pos, &out->close.reason_len))
114 return 0;
115 if (pos + out->close.reason_len > len)
116 return 0;
117 out->close.reason = buf + pos;
118 pos += out->close.reason_len;
119 return pos;
120 }
121
122 // Frames the server does not act on but MUST still parse so a well-formed frame from a real client is
123 // not rejected as FRAME_ENCODING_ERROR (RFC 9000 sec 12.4: parse the whole grammar even to ignore it).
124 // A real client sends these right after the handshake alongside the first request (MAX_STREAMS,
125 // NEW_CONNECTION_ID, MAX_STREAM_DATA, ...). Consume each frame's fields by its wire shape; the
126 // dispatcher then ignores the type. out->type is already set above.
127 if (type == QuicFrameType::QUIC_FT_MAX_STREAMS_BIDI || type == QuicFrameType::QUIC_FT_MAX_STREAMS_UNI ||
128 type == QuicFrameType::QUIC_FT_DATA_BLOCKED || type == QuicFrameType::QUIC_FT_STREAMS_BLOCKED_BIDI ||
129 type == QuicFrameType::QUIC_FT_STREAMS_BLOCKED_UNI || type == QuicFrameType::QUIC_FT_RETIRE_CONNECTION_ID)
130 {
131 uint64_t v = 0; // one varint
132 if (!rd(buf, len, &pos, &v))
133 return 0;
134 return pos;
135 }
136 if (type == QuicFrameType::QUIC_FT_STOP_SENDING || type == QuicFrameType::QUIC_FT_MAX_STREAM_DATA ||
137 type == QuicFrameType::QUIC_FT_STREAM_DATA_BLOCKED)
138 {
139 uint64_t v = 0; // two varints
140 if (!rd(buf, len, &pos, &v) || !rd(buf, len, &pos, &v))
141 return 0;
142 return pos;
143 }
144 if (type == QuicFrameType::QUIC_FT_RESET_STREAM)
145 {
146 uint64_t v = 0; // stream id, app error code, final size
147 if (!rd(buf, len, &pos, &v) || !rd(buf, len, &pos, &v) || !rd(buf, len, &pos, &v))
148 return 0;
149 return pos;
150 }
151 if (type == QuicFrameType::QUIC_FT_NEW_TOKEN)
152 {
153 uint64_t tlen = 0; // token length + token bytes
154 if (!rd(buf, len, &pos, &tlen) || pos + tlen > len)
155 return 0;
156 pos += tlen;
157 return pos;
158 }
159 if (type == QuicFrameType::QUIC_FT_NEW_CONNECTION_ID)
160 {
161 uint64_t seq = 0; // sequence number
162 uint64_t retire = 0; // retire-prior-to, then a 1-byte CID length follows
163 if (!rd(buf, len, &pos, &seq) || !rd(buf, len, &pos, &retire) || pos >= len)
164 return 0;
165 uint8_t cidlen = buf[pos++];
166 if (pos + (size_t)cidlen + 16 > len) // connection id + 16-byte stateless reset token
167 return 0;
168 pos += (size_t)cidlen + 16;
169 return pos;
170 }
171 if (type == QuicFrameType::QUIC_FT_PATH_CHALLENGE || type == QuicFrameType::QUIC_FT_PATH_RESPONSE)
172 {
173 if (pos + 8 > len) // 8 bytes of opaque data
174 return 0;
175 pos += 8;
176 return pos;
177 }
178
179 return 0; // a genuinely unknown / reserved frame type
180}
181
182size_t quic_build_padding(uint8_t *out, size_t cap, size_t n)
183{
184 if (n > cap)
185 return 0;
186 memset(out, 0, n);
187 return n;
188}
189
190size_t quic_build_ping(uint8_t *out, size_t cap)
191{
192 if (cap < 1)
193 return 0;
194 out[0] = QuicFrameType::QUIC_FT_PING;
195 return 1;
196}
197
198size_t quic_build_handshake_done(uint8_t *out, size_t cap)
199{
200 if (cap < 1)
201 return 0;
202 out[0] = QuicFrameType::QUIC_FT_HANDSHAKE_DONE;
203 return 1;
204}
205
206namespace
207{
208// Append a varint; returns false on overflow.
209bool wr(uint8_t *out, size_t cap, size_t *pos, uint64_t v)
210{
211 size_t c = quic_varint_encode(out + *pos, cap - *pos, v);
212 if (!c)
213 return false;
214 *pos += c;
215 return true;
216}
217} // namespace
218
219size_t quic_build_ack(uint8_t *out, size_t cap, uint64_t largest, uint64_t delay, uint64_t first_range)
220{
221 size_t pos = 0;
222 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_ACK) || !wr(out, cap, &pos, largest) || !wr(out, cap, &pos, delay) ||
223 !wr(out, cap, &pos, 0) /* ACK Range Count */ || !wr(out, cap, &pos, first_range))
224 return 0;
225 return pos;
226}
227
228size_t quic_build_crypto(uint8_t *out, size_t cap, uint64_t offset, const uint8_t *data, size_t len)
229{
230 size_t pos = 0;
231 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_CRYPTO) || !wr(out, cap, &pos, offset) || !wr(out, cap, &pos, len))
232 return 0;
233 if (pos + len > cap)
234 return 0;
235 if (len)
236 memcpy(out + pos, data, len);
237 return pos + len;
238}
239
240size_t quic_build_stream(uint8_t *out, size_t cap, uint64_t id, uint64_t offset, const uint8_t *data, size_t len,
241 bool fin)
242{
243 uint64_t type = QuicFrameType::QUIC_FT_STREAM | QuicStreamFlag::QUIC_STREAM_LEN |
244 (offset ? QuicStreamFlag::QUIC_STREAM_OFF : 0) | (fin ? QuicStreamFlag::QUIC_STREAM_FIN : 0);
245 size_t pos = 0;
246 if (!wr(out, cap, &pos, type) || !wr(out, cap, &pos, id))
247 return 0;
248 if (offset && !wr(out, cap, &pos, offset))
249 return 0;
250 if (!wr(out, cap, &pos, len))
251 return 0;
252 if (pos + len > cap)
253 return 0;
254 if (len)
255 memcpy(out + pos, data, len);
256 return pos + len;
257}
258
259size_t quic_build_max_data(uint8_t *out, size_t cap, uint64_t max)
260{
261 size_t pos = 0;
262 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_MAX_DATA) || !wr(out, cap, &pos, max))
263 return 0;
264 return pos;
265}
266
267size_t quic_build_connection_close(uint8_t *out, size_t cap, uint64_t error_code, uint64_t frame_type,
268 const char *reason, size_t reason_len)
269{
270 size_t pos = 0;
271 if (!wr(out, cap, &pos, QuicFrameType::QUIC_FT_CONNECTION_CLOSE) || !wr(out, cap, &pos, error_code) ||
272 !wr(out, cap, &pos, frame_type) || !wr(out, cap, &pos, reason_len))
273 return 0;
274 if (pos + reason_len > cap)
275 return 0;
276 if (reason_len)
277 memcpy(out + pos, reason, reason_len);
278 return pos + reason_len;
279}
280
281#endif // DETWS_ENABLE_HTTP3
QUIC frame parsing and building (RFC 9000 sec 19).
QUIC variable-length integer coding (RFC 9000 sec 16).