DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
quic_conn.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_conn.cpp
6 * @brief Stateful QUIC v1 server connection engine (see quic_conn.h).
7 */
8
10
11#if DETWS_ENABLE_HTTP3
12
13#include "network_drivers/presentation/http3/quic_aead.h" // QUIC_AEAD_TAG_LEN
17#include <string.h>
18
19namespace
20{
21// The open (decrypt) keys for an encryption level: Initial keys come from the DCID, Handshake and
22// 1-RTT keys from the TLS handshake. Returns NULL if that level's keys are not available yet.
23const QuicPacketKeys *open_keys(QuicConn *qc, int level)
24{
25 if (level == QuicEnc::QUIC_ENC_INITIAL)
26 return &qc->initial.client;
27 return quic_tls_keys(&qc->tls, level, /*is_server=*/false);
28}
29const QuicPacketKeys *seal_keys(QuicConn *qc, int level)
30{
31 if (level == QuicEnc::QUIC_ENC_INITIAL)
32 return &qc->initial.server;
33 return quic_tls_keys(&qc->tls, level, /*is_server=*/true);
34}
35
36// Find a stream slot by id, or allocate one; NULL if the table is full.
37QuicStream *stream_get(QuicConn *qc, uint64_t id, bool create)
38{
39 QuicStream *free_slot = nullptr;
40 for (size_t i = 0; i < DETWS_QUIC_MAX_STREAMS; i++)
41 {
42 if (qc->streams[i].id == id && qc->streams[i].id != UINT64_MAX)
43 return &qc->streams[i];
44 if (!free_slot && qc->streams[i].id == UINT64_MAX)
45 free_slot = &qc->streams[i];
46 }
47 if (!create || !free_slot)
48 return nullptr;
49 memset(free_slot, 0, sizeof(*free_slot));
50 free_slot->id = id;
51 return free_slot;
52}
53} // namespace
54
55void quic_conn_init(QuicConn *qc, const QuicTlsConfig *cfg, const uint8_t *odcid, uint8_t odcid_len,
56 const uint8_t *peer_scid, uint8_t peer_scid_len, const uint8_t *our_scid, uint8_t our_scid_len,
57 const QuicConnCallbacks *cb)
58{
59 memset(qc, 0, sizeof(*qc));
60 memcpy(qc->odcid, odcid, odcid_len);
61 qc->odcid_len = odcid_len;
62 memcpy(qc->dcid, peer_scid, peer_scid_len);
63 qc->dcid_len = peer_scid_len;
64 memcpy(qc->scid, our_scid, our_scid_len);
65 qc->scid_len = our_scid_len;
66 if (cb)
67 qc->cb = *cb;
68
69 quic_derive_initial_secrets(odcid, odcid_len, &qc->initial);
70
71 for (int i = 0; i < 3; i++)
72 {
73 qc->space[i].largest_acked = -1;
74 qc->space[i].last_ae_pn = -1;
75 }
76 for (size_t i = 0; i < DETWS_QUIC_MAX_STREAMS; i++)
77 qc->streams[i].id = UINT64_MAX;
78
79 // The server's transport parameters carry the connection IDs the handshake must echo.
80 QuicTlsConfig c = *cfg;
81 c.params.has_original_dcid = true;
82 memcpy(c.params.original_dcid, odcid, odcid_len);
83 c.params.original_dcid_len = odcid_len;
84 c.params.has_initial_scid = true;
85 memcpy(c.params.initial_scid, our_scid, our_scid_len);
86 c.params.initial_scid_len = our_scid_len;
87 quic_tls_server_init(&qc->tls, &c);
88}
89
90// --- Frame handling --------------------------------------------------------------------------
91namespace
92{
93// Queue a transport CONNECTION_CLOSE for a fatal error at @p level; the first error wins (RFC 9000 sec
94// 10.2.3). Sending at the level the error was seen on guarantees the peer holds keys to read it.
95void queue_close(QuicConn *qc, uint64_t error_code, uint64_t frame_type, int level)
96{
97 if (qc->close_queued || qc->closed)
98 return;
99 qc->close_queued = true;
100 qc->close_error = error_code;
101 qc->close_frame_type = frame_type;
102 qc->close_level = (uint8_t)level;
103}
104
105void handle_crypto(QuicConn *qc, int level, const QuicFrame *f)
106{
107 QuicPnSpace *s = &qc->space[level];
108 uint64_t want = s->crypto_rx_off;
109 if (f->crypto.offset > want)
110 return; // out-of-order beyond our window; the peer will retransmit
111 if (f->crypto.offset + f->crypto.length <= want)
112 return; // wholly duplicate
113 size_t skip = (size_t)(want - f->crypto.offset);
114 const uint8_t *nd = f->crypto.data + skip;
115 size_t nl = (size_t)(f->crypto.length - skip);
116 if (s->crypto_rx_have + nl > sizeof(s->crypto_rx))
117 nl = sizeof(s->crypto_rx) - s->crypto_rx_have; // clamp to the reassembly window
118 memcpy(s->crypto_rx + s->crypto_rx_have, nd, nl);
119 s->crypto_rx_have += nl;
120 s->crypto_rx_off += nl;
121
122 size_t used = quic_tls_recv_crypto(&qc->tls, level, s->crypto_rx, s->crypto_rx_have);
123 if (used)
124 {
125 memmove(s->crypto_rx, s->crypto_rx + used, s->crypto_rx_have - used);
126 s->crypto_rx_have -= used;
127 }
128 // A fatal TLS error (bad Finished, unsupported handshake) becomes a QUIC CRYPTO_ERROR: report it
129 // to the client with the TLS alert in the low byte (RFC 9001 sec 4.8) instead of stalling.
130 if (qc->tls.state == QtlsState::QTLS_FAILED)
131 {
132 queue_close(qc, QuicErr::QUIC_ERR_CRYPTO_BASE + qc->tls.alert, QuicFrameType::QUIC_FT_CRYPTO, level);
133 return;
134 }
135 // Completing the handshake opens 1-RTT and lets us send HANDSHAKE_DONE. We keep the Handshake
136 // space live so the same outbound datagram still ACKs the client's Finished; HANDSHAKE_DONE (at
137 // 1-RTT) is what tells the client the handshake is confirmed (RFC 9001 sec 4.1.2).
138 if (qc->tls.state == QtlsState::QTLS_DONE && !qc->handshake_done_sent && !qc->handshake_done_queued)
139 {
140 qc->handshake_done_queued = true;
141 qc->address_validated = true;
142 if (qc->cb.on_handshake_done)
143 qc->cb.on_handshake_done(qc->cb.app, qc);
144 }
145}
146
147void handle_stream(QuicConn *qc, const QuicFrame *f)
148{
149 QuicStream *st = stream_get(qc, f->stream.id, true);
150 if (!st)
151 return;
152 uint64_t want = st->rx_off;
153 if (f->stream.offset > want)
154 return; // out-of-order beyond window
155 if (f->stream.offset + f->stream.length > want)
156 {
157 size_t skip = (size_t)(want - f->stream.offset);
158 const uint8_t *nd = f->stream.data + skip;
159 size_t nl = (size_t)(f->stream.length - skip);
160 if (nl > sizeof(st->rx)) // GCOVR_EXCL_LINE one STREAM frame's length <= datagram (<=1350) < STREAM_RX(2048)
161 nl = sizeof(st->rx); // GCOVR_EXCL_LINE so nl never exceeds the per-stream reassembly buffer
162 // Deliver in place (we hand the callback the contiguous new bytes directly).
163 st->rx_off += nl;
164 if (f->stream.fin)
165 st->rx_fin = true;
166 if (qc->cb.on_stream_data)
167 qc->cb.on_stream_data(qc->cb.app, qc, st->id, nd, nl, st->rx_fin);
168 return;
169 }
170 if (f->stream.fin && f->stream.offset + f->stream.length == want && !st->rx_fin)
171 {
172 st->rx_fin = true;
173 if (qc->cb.on_stream_data)
174 qc->cb.on_stream_data(qc->cb.app, qc, st->id, nullptr, 0, true);
175 }
176}
177
178// Process the frames in one decrypted packet. Returns false on a fatal connection error.
179bool process_frames(QuicConn *qc, int level, const uint8_t *p, size_t len, bool *ack_eliciting)
180{
181 size_t off = 0;
182 while (off < len)
183 {
184 if (p[off] == QuicFrameType::QUIC_FT_PADDING)
185 {
186 off++;
187 continue;
188 }
189 QuicFrame f;
190 size_t n = quic_frame_parse(p + off, len - off, &f);
191 if (!n)
192 {
193 // Undecodable frame: a transport FRAME_ENCODING_ERROR (RFC 9000 sec 20.1). Report it.
194 queue_close(qc, QuicErr::QUIC_ERR_FRAME_ENCODING, 0, level);
195 return false;
196 }
197 off += n;
198
199 if (f.type != QuicFrameType::QUIC_FT_ACK && f.type != QuicFrameType::QUIC_FT_ACK_ECN &&
200 f.type != QuicFrameType::QUIC_FT_CONNECTION_CLOSE && f.type != QuicFrameType::QUIC_FT_CONNECTION_CLOSE_APP)
201 *ack_eliciting = true;
202
203 switch (f.type)
204 {
205 case QuicFrameType::QUIC_FT_CRYPTO:
206 handle_crypto(qc, level, &f);
207 break;
208 case QuicFrameType::QUIC_FT_ACK:
209 case QuicFrameType::QUIC_FT_ACK_ECN:
210 if ((int64_t)f.ack.largest > qc->space[level].largest_acked)
211 {
212 qc->space[level].largest_acked = (int64_t)f.ack.largest;
213 // Forward progress: reset the PTO backoff and re-evaluate the timer (RFC 9002 sec 6.2).
214 qc->pto_count = 0;
215 qc->pto_armed = false;
216 }
217 break;
218 case QuicFrameType::QUIC_FT_CONNECTION_CLOSE:
219 case QuicFrameType::QUIC_FT_CONNECTION_CLOSE_APP:
220 qc->draining = true;
221 qc->closed = true;
222 break;
223 case QuicFrameType::QUIC_FT_HANDSHAKE_DONE:
224 break; // server-only frame; ignore if a peer sends it
225 case QuicFrameType::QUIC_FT_MAX_DATA:
226 case QuicFrameType::QUIC_FT_PING:
227 break; // no per-frame state to keep for a minimal server
228 default:
229 if (f.type >= QuicFrameType::QUIC_FT_STREAM && f.type <= QuicFrameType::QUIC_FT_STREAM + 7)
230 handle_stream(qc, &f);
231 break;
232 }
233 }
234 return true;
235}
236
237// Skip an Initial packet's Token field (RFC 9000 sec 17.2.2), advancing *off. False if malformed.
238bool skip_initial_token(const uint8_t *dg, size_t len, size_t *off)
239{
240 uint64_t tok_len = 0;
241 size_t c = 0;
242 if (!quic_varint_decode(dg + *off, len - *off, &tok_len, &c))
243 return false;
244 *off += c + (size_t)tok_len;
245 return *off <= len;
246}
247
248// Parse one packet's (long or short) header, filling the fields needed to locate + unprotect it.
249// Returns false on a malformed header or an unsupported type/version (drop the packet).
250bool parse_packet_header(const QuicConn *qc, const uint8_t *dg, size_t len, bool is_long, int *level, size_t *pn_offset,
251 size_t *pkt_len, uint64_t *payload_length)
252{
253 if (!is_long)
254 {
255 // Short header: DCID length is our locally chosen scid_len; the packet runs to datagram end.
256 *level = QuicEnc::QUIC_ENC_APP;
257 *pn_offset = 1 + qc->scid_len;
258 if (*pn_offset >= len)
259 return false;
260 *payload_length = len - *pn_offset;
261 *pkt_len = len;
262 return true;
263 }
264
265 QuicLongHeader h;
266 if (!quic_parse_long_header(dg, len, &h))
267 return false;
268 if (h.version == 0 || h.version != QUIC_VERSION_1)
269 return false; // Version Negotiation is a client concern; unknown versions are dropped
270 if (h.type == QuicLongPacket::QUIC_LP_INITIAL)
271 *level = QuicEnc::QUIC_ENC_INITIAL;
272 else if (h.type == QuicLongPacket::QUIC_LP_HANDSHAKE)
273 *level = QuicEnc::QUIC_ENC_HANDSHAKE;
274 else
275 return false; // 0-RTT / Retry not supported
276
277 size_t off = h.hdr_len;
278 if (*level == QuicEnc::QUIC_ENC_INITIAL && !skip_initial_token(dg, len, &off))
279 return false;
280 size_t c = 0;
281 if (!quic_varint_decode(dg + off, len - off, payload_length, &c))
282 return false;
283 off += c;
284 *pn_offset = off;
285 *pkt_len = *pn_offset + (size_t)*payload_length;
286 return *pkt_len <= len;
287}
288
289// Decrypt and process one packet at datagram offset; returns bytes consumed (0 to stop the datagram).
290size_t recv_packet(QuicConn *qc, const uint8_t *dg, size_t len)
291{
292 if (len < 1)
293 return 0; // GCOVR_EXCL_LINE quic_conn_recv's loop only calls this with off<len, so len-off>=1
294 bool is_long = quic_is_long_header(dg[0]);
295
296 int level = 0;
297 size_t pn_offset = 0;
298 size_t pkt_len = 0; // total on-wire bytes of this packet
299 uint64_t payload_length = 0;
300 if (!parse_packet_header(qc, dg, len, is_long, &level, &pn_offset, &pkt_len, &payload_length))
301 return 0;
302
303 const QuicPacketKeys *keys = open_keys(qc, level);
304 if (!keys)
305 return 0; // keys for this level are not available yet
306
307 // Unprotect on a copy so a failed decrypt does not corrupt following coalesced packets. The
308 // engine runs sequentially on one task, so the scratch is a shared static (not reentrant).
309 static uint8_t work[DETWS_QUIC_MAX_DATAGRAM];
310 static uint8_t plain[DETWS_QUIC_MAX_DATAGRAM];
311 if (pkt_len > sizeof(work))
312 return 0;
313 memcpy(work, dg, pkt_len);
314 uint64_t pn = 0;
315 size_t pt = quic_packet_unprotect(work, pn_offset, (size_t)payload_length, qc->space[level].largest_rx, keys,
316 is_long, plain, &pn);
317 if (pt == (size_t)-1)
318 return is_long ? pkt_len : 0; // drop this packet, keep parsing later coalesced ones
319
320 if (!qc->space[level].have_rx || pn > qc->space[level].largest_rx)
321 qc->space[level].largest_rx = pn;
322 qc->space[level].have_rx = true;
323
324 // Receiving a Handshake packet validates the client's address (lifts anti-amplification).
325 if (level == QuicEnc::QUIC_ENC_HANDSHAKE)
326 {
327 qc->address_validated = true;
328 qc->space[QuicEnc::QUIC_ENC_INITIAL].discarded = true;
329 }
330
331 bool ack_eliciting = false;
332 process_frames(qc, level, plain, pt, &ack_eliciting);
333 if (ack_eliciting)
334 qc->space[level].ack_eliciting_rx = true;
335
336 return pkt_len;
337}
338} // namespace
339
340bool quic_conn_recv(QuicConn *qc, const uint8_t *datagram, size_t len)
341{
342 if (qc->closed)
343 return false;
344 qc->recv_bytes += len;
345 size_t off = 0;
346 bool any = false;
347 while (off < len)
348 {
349 size_t n = recv_packet(qc, datagram + off, len - off);
350 if (!n)
351 break;
352 any = true;
353 off += n;
354 }
355 return any;
356}
357
358// --- Sending ---------------------------------------------------------------------------------
359namespace
360{
361// Append the owed ACK frame for space @p s (RFC 9000 sec 13.2); returns bytes written.
362size_t build_ack_frame(QuicPnSpace *s, uint8_t *buf, size_t cap)
363{
364 if (!s->ack_eliciting_rx || !s->have_rx)
365 return 0;
366 size_t n = quic_build_ack(buf, cap, s->largest_rx, 0, s->largest_rx);
367 if (n)
368 s->ack_eliciting_rx = false;
369 return n;
370}
371
372// Append the CRYPTO flight for INITIAL/HANDSHAKE (ServerHello / EE..Finished); returns bytes written,
373// sets *ae when it emits an ack-eliciting CRYPTO frame.
374size_t build_crypto_frame(const QuicConn *qc, int level, QuicPnSpace *s, uint8_t *buf, size_t cap, bool *ae)
375{
376 if (level != QuicEnc::QUIC_ENC_INITIAL && level != QuicEnc::QUIC_ENC_HANDSHAKE)
377 return 0;
378 size_t flen = 0;
379 const uint8_t *flight = quic_tls_flight(&qc->tls, level, &flen);
380 if (!flight || s->crypto_tx_off >= flen)
381 return 0;
382 size_t remain = flen - (size_t)s->crypto_tx_off;
383 // Leave room for the CRYPTO frame header (type + offset + length varints, <= 1+8+8).
384 size_t room = (cap > 20) ? (cap - 20) : 0;
385 size_t take = remain < room ? remain : room;
386 if (!take)
387 return 0;
388 size_t n = quic_build_crypto(buf, cap, s->crypto_tx_off, flight + s->crypto_tx_off, take);
389 if (n)
390 {
391 s->crypto_tx_off += take;
392 *ae = true;
393 }
394 return n;
395}
396
397// Append 1-RTT extras (HANDSHAKE_DONE + stream data) at APP level; returns bytes written, sets *ae.
398size_t build_app_frames(QuicConn *qc, int level, uint8_t *buf, size_t cap, bool *ae)
399{
400 if (level != QuicEnc::QUIC_ENC_APP)
401 return 0;
402 size_t p = 0;
403 if (qc->handshake_done_queued)
404 {
405 size_t n = quic_build_handshake_done(buf + p, cap - p);
406 if (n)
407 {
408 p += n;
409 qc->handshake_done_queued = false;
410 qc->handshake_done_sent = true;
411 *ae = true;
412 }
413 }
414 for (size_t i = 0; i < DETWS_QUIC_MAX_STREAMS; i++)
415 {
416 QuicStream *st = &qc->streams[i];
417 if (st->id == UINT64_MAX)
418 continue;
419 bool more = st->tx_sent < st->tx_have;
420 bool fin_due = st->tx_fin && !st->tx_fin_sent && st->tx_sent == st->tx_have;
421 if (!more && !fin_due)
422 continue;
423 size_t room = (cap - p > 24) ? (cap - p - 24) : 0;
424 size_t remain = st->tx_have - st->tx_sent;
425 size_t take = remain < room ? remain : room;
426 bool fin = st->tx_fin && (st->tx_sent + take == st->tx_have);
427 size_t n = quic_build_stream(buf + p, cap - p, st->id, st->tx_off, st->tx + st->tx_sent, take, fin);
428 if (n)
429 {
430 p += n;
431 st->tx_off += take;
432 st->tx_sent += take;
433 st->tx_fin_sent = st->tx_fin_sent || fin;
434 *ae = true;
435 }
436 }
437 return p;
438}
439
440// Build the frame payload for one encryption level into buf; returns its length (0 = nothing to send).
441// @p ae is set true if the payload carries an ack-eliciting frame (CRYPTO / STREAM / HANDSHAKE_DONE),
442// which arms loss recovery for this space.
443size_t build_frames(QuicConn *qc, int level, uint8_t *buf, size_t cap, bool *ae)
444{
445 QuicPnSpace *s = &qc->space[level];
446 size_t p = 0;
447 *ae = false;
448
449 // While closing, the only frame we send is the transport CONNECTION_CLOSE (RFC 9000 sec 10.2.3).
450 // It is not ack-eliciting (*ae stays false), so no PTO is armed for it. quic_conn_send() invokes
451 // this for a single level when a close is queued, so it is emitted exactly once.
452 if (qc->close_queued && !qc->close_sent)
453 return quic_build_connection_close(buf, cap, qc->close_error, qc->close_frame_type, nullptr, 0);
454
455 p += build_ack_frame(s, buf + p, cap - p); // ACK first, if we owe one
456 p += build_crypto_frame(qc, level, s, buf + p, cap - p, ae);
457 p += build_app_frames(qc, level, buf + p, cap - p, ae);
458 return p;
459}
460
461// Long-header packet type for an encryption level.
462uint8_t level_lp_type(int level)
463{
464 return level == QuicEnc::QUIC_ENC_INITIAL ? QuicLongPacket::QUIC_LP_INITIAL : QuicLongPacket::QUIC_LP_HANDSHAKE;
465}
466
467// Build one protected packet for a level into out; returns its length (0 = nothing to send).
468size_t build_packet(QuicConn *qc, int level, uint8_t *out, size_t cap)
469{
470 QuicPnSpace *s = &qc->space[level];
471 if (s->discarded)
472 return 0;
473 const QuicPacketKeys *keys = seal_keys(qc, level);
474 if (!keys)
475 return 0;
476
477 uint8_t frames[DETWS_QUIC_MAX_DATAGRAM];
478 bool ae = false;
479 size_t frame_len = build_frames(qc, level, frames, sizeof(frames), &ae);
480 if (frame_len == 0)
481 return 0;
482
483 uint64_t pn = s->next_pn;
484 uint8_t pn_len = quic_pn_length(pn, s->largest_acked);
485 bool is_long = (level != QuicEnc::QUIC_ENC_APP);
486
487 // Header protection samples 16 bytes at (packet number + 4), so the packet number and payload
488 // together must be at least 4 bytes (RFC 9001 sec 5.4.2). Pad tiny packets (e.g. a lone
489 // HANDSHAKE_DONE or ACK) with PADDING frames (zero bytes) to reach that minimum.
490 if ((size_t)pn_len + frame_len < 4)
491 {
492 size_t pad = 4 - pn_len - frame_len;
493 memset(frames + frame_len, 0, pad);
494 frame_len += pad;
495 }
496
497 size_t p = 0;
498 if (is_long)
499 {
500 // Invariant header fields, then the type-specific token (Initial only) + Length + PN.
501 size_t hn = quic_build_long_header(out, cap, level_lp_type(level), QUIC_VERSION_1, qc->dcid, qc->dcid_len,
502 qc->scid, qc->scid_len, pn_len);
503 if (!hn)
504 return 0;
505 p = hn;
506 if (level == QuicEnc::QUIC_ENC_INITIAL)
507 {
508 size_t n = quic_varint_encode(out + p, cap - p, 0); // empty token
509 if (!n)
510 return 0;
511 p += n;
512 }
513 uint64_t length = (uint64_t)pn_len + frame_len + QUIC_AEAD_TAG_LEN;
514 size_t n = quic_varint_encode(out + p, cap - p, length);
515 if (!n)
516 return 0;
517 p += n;
518 }
519 else
520 {
521 // Short header: 0x40 fixed bit | pn_len-1; then the peer's DCID (no length on the wire).
522 if (1 + qc->dcid_len > cap)
523 return 0;
524 out[0] = (uint8_t)(0x40 | (pn_len - 1));
525 memcpy(out + 1, qc->dcid, qc->dcid_len);
526 p = 1 + qc->dcid_len;
527 }
528
529 size_t pn_offset = p;
530 // Bound the whole packet up front (p <= cap here): the header builders checked their own writes
531 // but not the packet number + payload + tag that follow, so verify the remainder fits before
532 // writing it (avoids a size_t addition wrap in the bounds check, cpp:S3519).
533 if (cap - p < (size_t)pn_len + frame_len + QUIC_AEAD_TAG_LEN)
534 return 0;
535 // Write the (unprotected) truncated packet number.
536 for (uint8_t i = 0; i < pn_len; i++)
537 out[pn_offset + i] = (uint8_t)(pn >> (8 * (pn_len - 1 - i)));
538 p += pn_len;
539
540 memcpy(out + p, frames, frame_len);
541
542 size_t total = quic_packet_protect(out, cap, pn_offset, pn_len, pn, frame_len, keys, is_long);
543 if (!total) // GCOVR_EXCL_LINE the pn_offset bound above already guarantees protect has room, and with
544 return 0; // GCOVR_EXCL_LINE valid keys the AEAD seal cannot otherwise fail
545 if (ae)
546 s->last_ae_pn = (int64_t)pn; // this space now has ack-eliciting data outstanding
547 s->next_pn++;
548 return total;
549}
550
551// Highest encryption level (INITIAL..APP) we still hold seal keys for and haven't discarded - the
552// level at which a CONNECTION_CLOSE can still be decrypted by the peer. Falls back to INITIAL.
553int quic_highest_sealed_level(QuicConn *qc)
554{
555 for (int l = QuicEnc::QUIC_ENC_APP; l >= QuicEnc::QUIC_ENC_INITIAL; l--)
556 if (!qc->space[l].discarded && seal_keys(qc, l))
557 return l;
558 return QuicEnc::QUIC_ENC_INITIAL;
559}
560} // namespace
561
562size_t quic_conn_send(QuicConn *qc, uint8_t *out, size_t cap)
563{
564 if (qc->closed && !qc->draining)
565 return 0;
566 // Anti-amplification (RFC 9000 sec 8.1): until the client's address is validated, send nothing
567 // once we have already put 3x the received bytes on the wire. Checked BEFORE building the packets
568 // so a blocked send never advances packet-number / CRYPTO / stream state (which would desync the
569 // flight); a build then discard was the bug. Being at-most one datagram approximate here is fine.
570 if (!qc->address_validated && qc->sent_bytes >= 3 * qc->recv_bytes)
571 return 0;
572 if (cap > DETWS_QUIC_MAX_DATAGRAM)
573 cap = DETWS_QUIC_MAX_DATAGRAM;
574
575 // A queued transport CONNECTION_CLOSE is sent once - at the highest encryption level we still hold
576 // keys for (so the peer can decrypt it) - and then the connection is closed. It replaces the normal
577 // frame build (a closing endpoint sends nothing else) and is bounded by the amplification limit above.
578 if (qc->close_queued && !qc->close_sent)
579 {
580 // Send at the level the error was seen on (the peer holds those keys); if that space has since
581 // been discarded, fall back to the highest level we still hold keys for.
582 int level = qc->close_level;
583 if (level < QuicEnc::QUIC_ENC_INITIAL || level > QuicEnc::QUIC_ENC_APP || qc->space[level].discarded ||
584 !seal_keys(qc, level))
585 level = quic_highest_sealed_level(qc);
586 size_t n = build_packet(qc, level, out, cap);
587 if (n)
588 {
589 qc->close_sent = true;
590 qc->closed = true;
591 qc->sent_bytes += n;
592 }
593 return n;
594 }
595
596 size_t dg = 0;
597 // Coalesce Initial, then Handshake, then 1-RTT into one datagram.
598 for (int level = QuicEnc::QUIC_ENC_INITIAL; level <= QuicEnc::QUIC_ENC_APP; level++)
599 {
600 size_t n = build_packet(qc, level, out + dg, cap - dg);
601 dg += n;
602 }
603 if (dg == 0)
604 return 0;
605 qc->sent_bytes += dg;
606 return dg;
607}
608
609namespace
610{
611// PTO period with exponential backoff, capped so the shift cannot overflow (RFC 9002 sec 6.2.1).
612uint32_t pto_period(uint8_t count)
613{
614 uint32_t p = DETWS_QUIC_PTO_MS;
615 for (uint8_t i = 0; i < count && p < (1u << 30); i++)
616 p <<= 1;
617 return p;
618}
619// A space has unacknowledged ack-eliciting data outstanding: it sent an ack-eliciting packet the peer
620// has not yet acknowledged, and its keys are still live.
621bool space_outstanding(const QuicPnSpace *s)
622{
623 return !s->discarded && s->last_ae_pn >= 0 && s->largest_acked < s->last_ae_pn;
624}
625} // namespace
626
627void quic_conn_on_timeout(QuicConn *qc, uint32_t now_ms)
628{
629 if (qc->closed)
630 return;
631 // Loss recovery (RFC 9002): retransmission is driven by a Probe Timeout, because a lost server
632 // packet is not re-triggered by the peer (a duplicate ClientHello re-delivers no CRYPTO; a lost
633 // 1-RTT response is never re-requested). Anything the peer has not acknowledged in a live space -
634 // the handshake CRYPTO flight, HANDSHAKE_DONE, or the 1-RTT response - is outstanding.
635 bool outstanding = space_outstanding(&qc->space[QuicEnc::QUIC_ENC_INITIAL]) ||
636 space_outstanding(&qc->space[QuicEnc::QUIC_ENC_HANDSHAKE]) ||
637 space_outstanding(&qc->space[QuicEnc::QUIC_ENC_APP]);
638 if (!outstanding)
639 {
640 qc->pto_armed = false; // everything acknowledged: nothing to retransmit
641 qc->pto_count = 0;
642 return;
643 }
644 if (!qc->pto_armed)
645 {
646 qc->pto_armed = true;
647 qc->pto_deadline_ms = now_ms + pto_period(qc->pto_count);
648 return;
649 }
650 if ((int32_t)(now_ms - qc->pto_deadline_ms) < 0)
651 return; // not yet (wrap-safe compare)
652
653 // PTO fired: mark the unacknowledged data in each outstanding space for retransmission so the next
654 // quic_conn_send() re-sends it, then back the timer off.
655 for (int level = QuicEnc::QUIC_ENC_INITIAL; level <= QuicEnc::QUIC_ENC_HANDSHAKE; level++)
656 if (space_outstanding(&qc->space[level]))
657 qc->space[level].crypto_tx_off = 0; // re-send the CRYPTO flight for this level
658 if (space_outstanding(&qc->space[QuicEnc::QUIC_ENC_APP]))
659 {
660 // Re-send 1-RTT data. The peer dedups STREAM data by offset, so rewinding each stream to 0
661 // recovers a lost response and is a no-op for data already received.
662 if (qc->handshake_done_sent)
663 {
664 qc->handshake_done_queued = true;
665 qc->handshake_done_sent = false;
666 }
667 for (size_t i = 0; i < DETWS_QUIC_MAX_STREAMS; i++)
668 {
669 QuicStream *st = &qc->streams[i];
670 if (st->id == UINT64_MAX)
671 continue;
672 st->tx_off = 0;
673 st->tx_sent = 0;
674 st->tx_fin_sent = false;
675 }
676 }
677 if (qc->pto_count < 8)
678 qc->pto_count++;
679 qc->pto_deadline_ms = now_ms + pto_period(qc->pto_count);
680}
681
682size_t quic_conn_stream_send(QuicConn *qc, uint64_t stream_id, const uint8_t *data, size_t len, bool fin)
683{
684 QuicStream *st = stream_get(qc, stream_id, true);
685 if (!st)
686 return 0;
687 size_t room = sizeof(st->tx) - st->tx_have;
688 size_t take = len < room ? len : room;
689 memcpy(st->tx + st->tx_have, data, take);
690 st->tx_have += take;
691 if (fin && take == len)
692 st->tx_fin = true;
693 return take;
694}
695
696void quic_conn_close(QuicConn *qc, uint64_t error_code)
697{
698 // Application-initiated close: send at the highest level we still hold keys for.
699 int level = quic_highest_sealed_level(qc);
700 queue_close(qc, error_code, 0, level);
701}
702
703bool quic_conn_established(const QuicConn *qc)
704{
705 return qc->tls.state == QtlsState::QTLS_DONE;
706}
707
708bool quic_conn_is_closed(const QuicConn *qc)
709{
710 return qc->closed || qc->draining;
711}
712
713#endif // DETWS_ENABLE_HTTP3
AES-128 block cipher and AEAD_AES_128_GCM (RFC 5116 / NIST SP 800-38D).
Stateful QUIC v1 server connection engine (RFC 9000 / RFC 9001).
QUIC frame parsing and building (RFC 9000 sec 19).
QUIC packet headers and packet-number coding (RFC 9000 sec 17).
QUIC variable-length integer coding (RFC 9000 sec 16).