ProtoCore v0.0.2
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
quic_tp.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_tp.cpp
6 * @brief QUIC transport parameters codec (see pc_quic_tp.h).
7 */
8
10
11#if PC_ENABLE_HTTP3
12
14#include <string.h>
15
16void pc_quic_tp_defaults(QuicTransportParams *tp)
17{
18 memset(tp, 0, sizeof(*tp));
19 tp->max_udp_payload_size = 65527;
20 tp->ack_delay_exponent = 3;
21 tp->max_ack_delay = 25;
22 tp->active_connection_id_limit = 2;
23}
24
25namespace
26{
27// Append one parameter: ID (varint) || Length (varint) || raw value bytes.
28bool put_param(uint8_t *out, size_t cap, size_t *p, uint64_t id, const uint8_t *val, size_t val_len)
29{
30 size_t n = pc_quic_varint_encode(out + *p, cap - *p, id);
31 if (!n)
32 {
33 return false;
34 }
35 *p += n;
36 n = pc_quic_varint_encode(out + *p, cap - *p, val_len);
37 if (!n)
38 {
39 return false;
40 }
41 *p += n;
42 if (val_len)
43 {
44 // Overflow-safe check (*p <= cap holds after the varint encodes): avoids the *p + val_len
45 // size_t wraparound an analyzer flags as a possible destination overflow (cpp:S3519).
46 if (val_len > cap - *p)
47 {
48 return false;
49 }
50 memcpy(out + *p, val, val_len);
51 *p += val_len;
52 }
53 return true;
54}
55
56// Append a varint-valued parameter (Value is itself a varint).
57bool put_varint_param(uint8_t *out, size_t cap, size_t *p, uint64_t id, uint64_t value)
58{
59 uint8_t v[8];
60 size_t vlen = pc_quic_varint_encode(v, sizeof(v), value);
61 if (!vlen)
62 {
63 return false;
64 }
65 return put_param(out, cap, p, id, v, vlen);
66}
67} // namespace
68
69size_t pc_quic_tp_encode(const QuicTransportParams *tp, uint8_t *out, size_t cap)
70{
71 size_t p = 0;
72 bool ok = true;
73 if (tp->has_original_dcid)
74 {
75 // This is the first assignment in the ok-chain (ok is still its line-64 default), so the
76 // "ok already false" arm of this && can never fire.
77 ok = ok && put_param(out, cap, &p, QuicTp::QUIC_TP_ORIGINAL_DCID, tp->original_dcid, // GCOVR_EXCL_BR_LINE
78 tp->original_dcid_len);
79 }
80 if (tp->has_initial_scid)
81 {
82 ok = ok && put_param(out, cap, &p, QuicTp::QUIC_TP_INITIAL_SCID, tp->initial_scid, tp->initial_scid_len);
83 }
84 if (tp->has_retry_scid)
85 {
86 ok = ok && put_param(out, cap, &p, QuicTp::QUIC_TP_RETRY_SCID, tp->retry_scid, tp->retry_scid_len);
87 }
88
89 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data);
90 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_INITIAL_MAX_SD_BIDI_LOCAL, tp->initial_max_sd_bidi_local);
91 ok = ok &&
92 put_varint_param(out, cap, &p, QuicTp::QUIC_TP_INITIAL_MAX_SD_BIDI_REMOTE, tp->initial_max_sd_bidi_remote);
93 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_INITIAL_MAX_SD_UNI, tp->initial_max_sd_uni);
94 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_INITIAL_MAX_STREAMS_BIDI, tp->initial_max_streams_bidi);
95 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_INITIAL_MAX_STREAMS_UNI, tp->initial_max_streams_uni);
96 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_MAX_IDLE_TIMEOUT, tp->max_idle_timeout);
97 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_MAX_UDP_PAYLOAD_SIZE, tp->max_udp_payload_size);
98 ok = ok && put_varint_param(out, cap, &p, QuicTp::QUIC_TP_ACTIVE_CID_LIMIT, tp->active_connection_id_limit);
99 if (tp->disable_active_migration)
100 {
101 ok = ok && put_param(out, cap, &p, QuicTp::QUIC_TP_DISABLE_ACTIVE_MIGRATION, nullptr, 0);
102 }
103
104 return ok ? p : 0;
105}
106
107namespace
108{
109// Decode the varint that IS the whole value of a varint-valued parameter (must consume exactly len).
110bool value_varint(const uint8_t *val, size_t len, uint64_t *out)
111{
112 size_t consumed = 0;
113 if (!pc_quic_varint_decode(val, len, out, &consumed))
114 {
115 return false;
116 }
117 return consumed == len;
118}
119
120// Copy a connection-ID value (<= QUIC_MAX_CID_LEN) into a fixed field.
121bool copy_cid(const uint8_t *val, size_t len, uint8_t *dst, uint8_t *dst_len, bool *has)
122{
123 if (len > QUIC_MAX_CID_LEN)
124 {
125 return false;
126 }
127 memcpy(dst, val, len);
128 *dst_len = (uint8_t)len;
129 *has = true;
130 return true;
131}
132
133// Apply a connection-ID transport parameter. *handled is set true if id names a CID param (whether or
134// not the copy succeeded); false leaves it for another category. Returns false only on a bad value.
135bool pc_quic_tp_apply_cid(uint64_t id, const uint8_t *val, size_t vlen, QuicTransportParams *tp, bool *handled)
136{
137 *handled = true;
138 switch (id)
139 {
140 case QuicTp::QUIC_TP_ORIGINAL_DCID:
141 return copy_cid(val, vlen, tp->original_dcid, &tp->original_dcid_len, &tp->has_original_dcid);
142 case QuicTp::QUIC_TP_INITIAL_SCID:
143 return copy_cid(val, vlen, tp->initial_scid, &tp->initial_scid_len, &tp->has_initial_scid);
144 case QuicTp::QUIC_TP_RETRY_SCID:
145 return copy_cid(val, vlen, tp->retry_scid, &tp->retry_scid_len, &tp->has_retry_scid);
146 default:
147 *handled = false;
148 return true;
149 }
150}
151
152// Apply a varint-valued transport parameter with its RFC 9000 range checks. *handled is set as above.
153bool pc_quic_tp_apply_varint(uint64_t id, const uint8_t *val, size_t vlen, QuicTransportParams *tp, bool *handled)
154{
155 *handled = true;
156 switch (id)
157 {
158 case QuicTp::QUIC_TP_MAX_IDLE_TIMEOUT:
159 return value_varint(val, vlen, &tp->max_idle_timeout);
160 case QuicTp::QUIC_TP_MAX_UDP_PAYLOAD_SIZE:
161 return value_varint(val, vlen, &tp->max_udp_payload_size) && tp->max_udp_payload_size >= 1200;
162 case QuicTp::QUIC_TP_INITIAL_MAX_DATA:
163 return value_varint(val, vlen, &tp->initial_max_data);
164 case QuicTp::QUIC_TP_INITIAL_MAX_SD_BIDI_LOCAL:
165 return value_varint(val, vlen, &tp->initial_max_sd_bidi_local);
166 case QuicTp::QUIC_TP_INITIAL_MAX_SD_BIDI_REMOTE:
167 return value_varint(val, vlen, &tp->initial_max_sd_bidi_remote);
168 case QuicTp::QUIC_TP_INITIAL_MAX_SD_UNI:
169 return value_varint(val, vlen, &tp->initial_max_sd_uni);
170 case QuicTp::QUIC_TP_INITIAL_MAX_STREAMS_BIDI:
171 return value_varint(val, vlen, &tp->initial_max_streams_bidi);
172 case QuicTp::QUIC_TP_INITIAL_MAX_STREAMS_UNI:
173 return value_varint(val, vlen, &tp->initial_max_streams_uni);
174 case QuicTp::QUIC_TP_ACK_DELAY_EXPONENT:
175 return value_varint(val, vlen, &tp->ack_delay_exponent) && tp->ack_delay_exponent <= 20;
176 case QuicTp::QUIC_TP_MAX_ACK_DELAY:
177 return value_varint(val, vlen, &tp->max_ack_delay) && tp->max_ack_delay < (1u << 14);
178 case QuicTp::QUIC_TP_ACTIVE_CID_LIMIT:
179 return value_varint(val, vlen, &tp->active_connection_id_limit) && tp->active_connection_id_limit >= 2;
180 default:
181 *handled = false;
182 return true;
183 }
184}
185
186// Dispatch one parsed transport parameter to tp; false on a malformed / out-of-range value. Unknown
187// (GREASE) IDs are silently ignored, matching RFC 9000 ยง7.4.1.
188bool pc_quic_tp_apply(uint64_t id, const uint8_t *val, size_t vlen, QuicTransportParams *tp)
189{
190 bool handled = false;
191 if (!pc_quic_tp_apply_cid(id, val, vlen, tp, &handled))
192 {
193 return false;
194 }
195 if (handled)
196 {
197 return true;
198 }
199 if (!pc_quic_tp_apply_varint(id, val, vlen, tp, &handled))
200 {
201 return false;
202 }
203 if (handled)
204 {
205 return true;
206 }
207 if (id == QuicTp::QUIC_TP_DISABLE_ACTIVE_MIGRATION)
208 {
209 if (vlen != 0)
210 {
211 return false;
212 }
213 tp->disable_active_migration = true;
214 }
215 return true; // unknown / GREASE: skip
216}
217} // namespace
218
219bool pc_quic_tp_parse(const uint8_t *buf, size_t len, QuicTransportParams *tp)
220{
221 pc_quic_tp_defaults(tp);
222 uint32_t seen = 0; // dup-guard bitmask over the known IDs (all < 32)
223
224 size_t off = 0;
225 while (off < len)
226 {
227 uint64_t id = 0;
228 uint64_t vlen = 0;
229 size_t c = 0;
230 if (!pc_quic_varint_decode(buf + off, len - off, &id, &c))
231 {
232 return false;
233 }
234 off += c;
235 if (!pc_quic_varint_decode(buf + off, len - off, &vlen, &c))
236 {
237 return false;
238 }
239 off += c;
240 if (off + vlen > len)
241 {
242 return false;
243 }
244 const uint8_t *val = buf + off;
245 off += vlen;
246
247 if (id < 32)
248 {
249 uint32_t bit = 1u << id;
250 if (seen & bit)
251 {
252 return false; // a known parameter must not appear twice
253 }
254 seen |= bit;
255 }
256
257 if (!pc_quic_tp_apply(id, val, vlen, tp))
258 {
259 return false;
260 }
261 }
262 return true;
263}
264
265#endif // PC_ENABLE_HTTP3