DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
quic_packet.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_packet.cpp
6 * @brief QUIC packet headers and packet-number coding - implementation. See quic_packet.h.
7 */
8
10
11#if DETWS_ENABLE_HTTP3
12
13#include <string.h>
14
15namespace
16{
17uint32_t rd_be32(const uint8_t *p)
18{
19 return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | p[3];
20}
21void wr_be32(uint8_t *p, uint32_t v)
22{
23 p[0] = (uint8_t)(v >> 24);
24 p[1] = (uint8_t)(v >> 16);
25 p[2] = (uint8_t)(v >> 8);
26 p[3] = (uint8_t)v;
27}
28} // namespace
29
30bool quic_is_long_header(uint8_t first)
31{
32 return (first & 0x80) != 0;
33}
34
35bool quic_parse_long_header(const uint8_t *buf, size_t len, QuicLongHeader *out)
36{
37 if (len < 7 || !(buf[0] & 0x80)) // first byte + version(4) + dcid_len(1) + scid_len(1)
38 return false;
39 out->first = buf[0];
40 out->version = rd_be32(buf + 1);
41 out->type = (uint8_t)((buf[0] & 0x30) >> 4);
42 size_t pos = 5;
43 uint8_t dcl = buf[pos++];
44 if (dcl > QUIC_MAX_CID_LEN || pos + dcl + 1 > len) // +1 for the SCID length byte
45 return false;
46 memcpy(out->dcid, buf + pos, dcl);
47 out->dcid_len = dcl;
48 pos += dcl;
49 uint8_t scl = buf[pos++];
50 if (scl > QUIC_MAX_CID_LEN || pos + scl > len)
51 return false;
52 memcpy(out->scid, buf + pos, scl);
53 out->scid_len = scl;
54 pos += scl;
55 out->hdr_len = pos;
56 return true;
57}
58
59size_t quic_build_long_header(uint8_t *out, size_t cap, uint8_t type, uint32_t version, const uint8_t *dcid,
60 uint8_t dcid_len, const uint8_t *scid, uint8_t scid_len, uint8_t pn_len)
61{
62 if (dcid_len > QUIC_MAX_CID_LEN || scid_len > QUIC_MAX_CID_LEN || pn_len < 1 || pn_len > 4)
63 return 0;
64 size_t need = 1 + 4 + 1 + dcid_len + 1 + scid_len;
65 if (need > cap)
66 return 0;
67 // 1 Fixed(1) type(2) reserved(00) pn_len-1(2). Reserved bits are 0 before header protection.
68 out[0] = (uint8_t)(0x80 | 0x40 | ((type & 0x03) << 4) | ((pn_len - 1) & 0x03));
69 wr_be32(out + 1, version);
70 size_t pos = 5;
71 out[pos++] = dcid_len;
72 memcpy(out + pos, dcid, dcid_len);
73 pos += dcid_len;
74 out[pos++] = scid_len;
75 memcpy(out + pos, scid, scid_len);
76 pos += scid_len;
77 return pos;
78}
79
80bool quic_parse_short_header(const uint8_t *buf, size_t len, uint8_t dcid_len, QuicShortHeader *out)
81{
82 if (dcid_len > QUIC_MAX_CID_LEN || len < (size_t)1 + dcid_len || (buf[0] & 0x80))
83 return false;
84 out->first = buf[0];
85 out->spin = (uint8_t)((buf[0] & 0x20) ? 1 : 0);
86 out->key_phase = (uint8_t)((buf[0] & 0x04) ? 1 : 0);
87 out->pn_len = (uint8_t)((buf[0] & 0x03) + 1);
88 memcpy(out->dcid, buf + 1, dcid_len);
89 out->dcid_len = dcid_len;
90 out->hdr_len = (size_t)1 + dcid_len;
91 return true;
92}
93
94size_t quic_build_version_negotiation(uint8_t *out, size_t cap, const uint8_t *dcid, uint8_t dcid_len,
95 const uint8_t *scid, uint8_t scid_len, const uint32_t *versions, size_t nversions)
96{
97 if (dcid_len > QUIC_MAX_CID_LEN || scid_len > QUIC_MAX_CID_LEN)
98 return 0;
99 size_t need = 1 + 4 + 1 + dcid_len + 1 + scid_len + nversions * 4;
100 if (need > cap)
101 return 0;
102 out[0] = 0x80 | 0x40; // header form + the recommended set Fixed-Bit position (sec 17.2.1)
103 wr_be32(out + 1, 0); // Version = 0 marks a Version Negotiation packet
104 size_t pos = 5;
105 out[pos++] = dcid_len;
106 memcpy(out + pos, dcid, dcid_len);
107 pos += dcid_len;
108 out[pos++] = scid_len;
109 memcpy(out + pos, scid, scid_len);
110 pos += scid_len;
111 for (size_t i = 0; i < nversions; i++)
112 {
113 wr_be32(out + pos, versions[i]);
114 pos += 4;
115 }
116 return pos;
117}
118
119uint8_t quic_pn_length(uint64_t full_pn, int64_t largest_acked)
120{
121 // num_unacked = full_pn + 1 when nothing acked, else full_pn - largest_acked (RFC 9000 A.2).
122 uint64_t num_unacked = (largest_acked < 0) ? (full_pn + 1) : (full_pn - (uint64_t)largest_acked);
123 // Smallest k in 1..4 with 2^(8k) >= 2 * num_unacked (i.e. min_bits = log2(n)+1 rounded up to bytes).
124 for (uint8_t k = 1; k < 4; k++)
125 if (((uint64_t)1 << (8 * k)) >= (num_unacked << 1))
126 return k;
127 return 4;
128}
129
130size_t quic_pn_encode(uint8_t *out, size_t cap, uint64_t full_pn, int64_t largest_acked)
131{
132 uint8_t n = quic_pn_length(full_pn, largest_acked);
133 if (n > cap)
134 return 0;
135 for (uint8_t i = 0; i < n; i++)
136 out[i] = (uint8_t)(full_pn >> (8 * (n - 1 - i))); // truncate to the n least-significant bytes, big-endian
137 return n;
138}
139
140uint64_t quic_pn_decode(uint64_t largest_pn, uint64_t truncated_pn, uint8_t pn_nbits)
141{
142 uint64_t expected = largest_pn + 1;
143 uint64_t pn_win = (uint64_t)1 << pn_nbits;
144 uint64_t pn_hwin = pn_win / 2;
145 uint64_t pn_mask = pn_win - 1;
146 uint64_t candidate = (expected & ~pn_mask) | (truncated_pn & pn_mask);
147 // candidate <= expected - pn_hwin, guarded against underflow and the 2^62 ceiling.
148 if (candidate + pn_hwin <= expected && candidate < (((uint64_t)1 << 62) - pn_win))
149 return candidate + pn_win;
150 if (candidate > expected + pn_hwin && candidate >= pn_win)
151 return candidate - pn_win;
152 return candidate;
153}
154
155#endif // DETWS_ENABLE_HTTP3
QUIC packet headers and packet-number coding (RFC 9000 sec 17).