ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
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 pc_quic_packet.cpp
6 * @brief QUIC packet headers and packet-number coding - implementation. See pc_quic_packet.h.
7 */
8
10
11#if PC_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 pc_quic_is_long_header(uint8_t first)
31{
32 return (first & 0x80) != 0;
33}
34
35bool pc_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 {
39 return false;
40 }
41 out->first = buf[0];
42 out->version = rd_be32(buf + 1);
43 out->type = (uint8_t)((buf[0] & 0x30) >> 4);
44 size_t pos = 5;
45 uint8_t dcl = buf[pos++];
46 if (dcl > QUIC_MAX_CID_LEN || pos + dcl + 1 > len) // +1 for the SCID length byte
47 {
48 return false;
49 }
50 memcpy(out->dcid, buf + pos, dcl);
51 out->dcid_len = dcl;
52 pos += dcl;
53 uint8_t scl = buf[pos++];
54 if (scl > QUIC_MAX_CID_LEN || pos + scl > len)
55 {
56 return false;
57 }
58 memcpy(out->scid, buf + pos, scl);
59 out->scid_len = scl;
60 pos += scl;
61 out->hdr_len = pos;
62 return true;
63}
64
65size_t pc_quic_build_long_header(uint8_t *out, size_t cap, uint8_t type, uint32_t version, const uint8_t *dcid,
66 uint8_t dcid_len, const uint8_t *scid, uint8_t scid_len, uint8_t pn_len)
67{
68 if (dcid_len > QUIC_MAX_CID_LEN || scid_len > QUIC_MAX_CID_LEN || pn_len < 1 || pn_len > 4)
69 {
70 return 0;
71 }
72 size_t need = 1 + 4 + 1 + dcid_len + 1 + scid_len;
73 if (need > cap)
74 {
75 return 0;
76 }
77 // 1 Fixed(1) type(2) reserved(00) pn_len-1(2). Reserved bits are 0 before header protection.
78 out[0] = (uint8_t)(0x80 | 0x40 | ((type & 0x03) << 4) | ((pn_len - 1) & 0x03));
79 wr_be32(out + 1, version);
80 size_t pos = 5;
81 out[pos++] = dcid_len;
82 memcpy(out + pos, dcid, dcid_len);
83 pos += dcid_len;
84 out[pos++] = scid_len;
85 memcpy(out + pos, scid, scid_len);
86 pos += scid_len;
87 return pos;
88}
89
90bool pc_quic_parse_short_header(const uint8_t *buf, size_t len, uint8_t dcid_len, QuicShortHeader *out)
91{
92 if (dcid_len > QUIC_MAX_CID_LEN || len < (size_t)1 + dcid_len || (buf[0] & 0x80))
93 {
94 return false;
95 }
96 out->first = buf[0];
97 out->spin = (uint8_t)((buf[0] & 0x20) ? 1 : 0);
98 out->key_phase = (uint8_t)((buf[0] & 0x04) ? 1 : 0);
99 out->pn_len = (uint8_t)((buf[0] & 0x03) + 1);
100 memcpy(out->dcid, buf + 1, dcid_len);
101 out->dcid_len = dcid_len;
102 out->hdr_len = (size_t)1 + dcid_len;
103 return true;
104}
105
106size_t pc_quic_build_version_negotiation(uint8_t *out, size_t cap, const uint8_t *dcid, uint8_t dcid_len,
107 const uint8_t *scid, uint8_t scid_len, const uint32_t *versions,
108 size_t nversions)
109{
110 if (dcid_len > QUIC_MAX_CID_LEN || scid_len > QUIC_MAX_CID_LEN)
111 {
112 return 0;
113 }
114 size_t need = 1 + 4 + 1 + dcid_len + 1 + scid_len + nversions * 4;
115 if (need > cap)
116 {
117 return 0;
118 }
119 out[0] = 0x80 | 0x40; // header form + the recommended set Fixed-Bit position (sec 17.2.1)
120 wr_be32(out + 1, 0); // Version = 0 marks a Version Negotiation packet
121 size_t pos = 5;
122 out[pos++] = dcid_len;
123 memcpy(out + pos, dcid, dcid_len);
124 pos += dcid_len;
125 out[pos++] = scid_len;
126 memcpy(out + pos, scid, scid_len);
127 pos += scid_len;
128 for (size_t i = 0; i < nversions; i++)
129 {
130 wr_be32(out + pos, versions[i]);
131 pos += 4;
132 }
133 return pos;
134}
135
136uint8_t pc_quic_pn_length(uint64_t full_pn, int64_t largest_acked)
137{
138 // num_unacked = full_pn + 1 when nothing acked, else full_pn - largest_acked (RFC 9000 A.2).
139 uint64_t num_unacked = (largest_acked < 0) ? (full_pn + 1) : (full_pn - (uint64_t)largest_acked);
140 // Smallest k in 1..4 with 2^(8k) >= 2 * num_unacked (i.e. min_bits = log2(n)+1 rounded up to bytes).
141 for (uint8_t k = 1; k < 4; k++)
142 {
143 if (((uint64_t)1 << (8 * k)) >= (num_unacked << 1))
144 {
145 return k;
146 }
147 }
148 return 4;
149}
150
151size_t pc_quic_pn_encode(uint8_t *out, size_t cap, uint64_t full_pn, int64_t largest_acked)
152{
153 uint8_t n = pc_quic_pn_length(full_pn, largest_acked);
154 if (n > cap)
155 {
156 return 0;
157 }
158 for (uint8_t i = 0; i < n; i++)
159 {
160 out[i] = (uint8_t)(full_pn >> (8 * (n - 1 - i))); // truncate to the n least-significant bytes, big-endian
161 }
162 return n;
163}
164
165uint64_t pc_quic_pn_decode(uint64_t largest_pn, uint64_t truncated_pn, uint8_t pn_nbits)
166{
167 uint64_t expected = largest_pn + 1;
168 uint64_t pn_win = (uint64_t)1 << pn_nbits;
169 uint64_t pn_hwin = pn_win / 2;
170 uint64_t pn_mask = pn_win - 1;
171 uint64_t candidate = (expected & ~pn_mask) | (truncated_pn & pn_mask);
172 // candidate <= expected - pn_hwin, guarded against underflow and the 2^62 ceiling.
173 if (candidate + pn_hwin <= expected && candidate < (((uint64_t)1 << 62) - pn_win))
174 {
175 return candidate + pn_win;
176 }
177 if (candidate > expected + pn_hwin && candidate >= pn_win)
178 {
179 return candidate - pn_win;
180 }
181 return candidate;
182}
183
184#endif // PC_ENABLE_HTTP3