ProtoCore v1.0.16
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
quic_packet.h
Go to the documentation of this file.
1// ProtoCore v1.0.16 - Copyright (C) 2026 Douglas Quigg (dstroy0) <dquigg123@gmail.com>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4#ifndef PROTOCORE_QUIC_PACKET_H
5#define PROTOCORE_QUIC_PACKET_H
6
7#include "protocore_config.h" // the entry point: protocore_types.h for the widths
8
10
11/**
12 * @file quic_packet.h
13 * @brief QUIC packet headers and packet-number coding (RFC 9000 sec 17).
14 *
15 * The structural, version-independent layer of a QUIC packet: the long-header form (Initial /
16 * 0-RTT / Handshake / Retry, plus the Version Negotiation packet whose Version is 0) and the
17 * short-header 1-RTT form, and the packet-number truncation coding (sec 17.1, Appendix A.2/A.3).
18 *
19 * This is the unprotected structure only - it parses and builds the header fields that are not
20 * covered by header protection (header form, version, connection IDs) and codes packet numbers.
21 * Packet protection (AEAD) and header protection are layered on top by the QUIC crypto module.
22 * Pure, zero heap, host-tested against the RFC worked examples.
23 *
24 * @c work is bytes the CALLER holds. This module reads none of them: it carries nothing
25 * between calls, so there is no state to keep and nothing to wipe. The parameter is there so
26 * a caller drives every namespace the same way.
27 *
28 * @author Douglas Quigg (dstroy0)
29 * @date 2026
30 */
31
32// PROTOCORE_QUIC_PACKET_BORROW - the bytes this module runs out of - is stated in protocore_config.h, which sums
33// it into its arena. Its size and its offset are each a static_assert, so a feature
34// combination that does not fit fails to compile rather than overrunning at run time.
35
36#define QUIC_VERSION_1 0x00000001u ///< RFC 9000
37#define QUIC_MAX_CID_LEN 20 ///< maximum connection-ID length in QUIC version 1
38
39/** @brief Long-header packet types (RFC 9000 sec 17.2, Table 5). */
40#define QUIC_LP_INITIAL 0x00
41#define QUIC_LP_0RTT 0x01
42#define QUIC_LP_HANDSHAKE 0x02
43#define QUIC_LP_RETRY 0x03
44
45/** @brief A parsed long header (invariant fields). A Version of 0 marks a Version Negotiation. */
46typedef struct
47{
48 uint8_t first; ///< raw first byte
49 uint8_t type; ///< long packet type (first & 0x30) >> 4; meaningful when version != 0
50 uint32_t version; ///< QUIC version; 0 = Version Negotiation
51 uint8_t dcid_len; ///< Destination Connection ID length
52 uint8_t dcid[QUIC_MAX_CID_LEN]; ///< Destination Connection ID
53 uint8_t scid_len; ///< Source Connection ID length
54 uint8_t scid[QUIC_MAX_CID_LEN]; ///< Source Connection ID
55 size_t hdr_len; ///< bytes consumed up to the start of the type-specific payload
57
58/** @brief A parsed short header (1-RTT). The DCID length is known locally, not on the wire. */
59typedef struct
60{
61 uint8_t first; ///< raw first byte
62 uint8_t spin; ///< latency spin bit (0x20)
63 uint8_t key_phase; ///< key-phase bit (0x04)
64 uint8_t pn_len; ///< packet-number length in bytes (1..4)
65 uint8_t dcid_len; ///< Destination Connection ID length (caller-supplied)
66 uint8_t dcid[QUIC_MAX_CID_LEN]; ///< Destination Connection ID
67 size_t hdr_len; ///< bytes up to the (protected) Packet Number field
69
70/** @brief Dispatch table. Addressed by offset, so the layout is asserted below. */
71typedef struct
72{
73 proto_bool (*is_long_header)(uint8_t *, uint8_t);
74 proto_bool (*parse_long_header)(uint8_t *, const uint8_t *, size_t, QuicLongHeader *);
75 size_t (*build_long_header)(uint8_t *, uint8_t *, size_t, uint8_t, uint32_t, const uint8_t *, uint8_t,
76 const uint8_t *, uint8_t, uint8_t);
77 proto_bool (*parse_short_header)(uint8_t *, const uint8_t *, size_t, uint8_t, QuicShortHeader *);
78 size_t (*build_version_negotiation)(uint8_t *, uint8_t *, size_t, const uint8_t *, uint8_t, const uint8_t *,
79 uint8_t, const uint32_t *, size_t);
80 uint8_t (*pn_length)(uint8_t *, uint64_t, int64_t);
81 size_t (*pn_encode)(uint8_t *, uint8_t *, size_t, uint64_t, int64_t);
82 uint64_t (*pn_decode)(uint8_t *, uint64_t, uint64_t, uint8_t);
84PROTOCORE_NS_LAYOUT(QuicPacketNs, is_long_header, parse_long_header, build_long_header, parse_short_header,
85 build_version_negotiation, pn_length, pn_encode, pn_decode);
86
87/**
88 * @brief True if byte 0 selects the long header form (0x80 set).
89 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
90 * @param first First
91 * @return PROTO_TRUE on success.
92 */
94/**
95 * @brief Parse a long header. false if truncated or a connection ID exceeds .
96 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
97 * @param buf Buf
98 * @param len Len
99 * @param out Out
100 * @return PROTO_TRUE on success.
101 */
102proto_bool protocore_quic_packet_parse_long_header(uint8_t *work, const uint8_t *buf, size_t len, QuicLongHeader *out);
103/**
104 * @brief Build a long header's invariant fields (first byte .. Source .
105 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
106 * @param out Out
107 * @param cap Cap
108 * @param type Type
109 * @param version Version
110 * @param dcid Dcid
111 * @param dcid_len Dcid len
112 * @param scid Scid
113 * @param scid_len Scid len
114 * @param pn_len Pn len
115 * @return The size_t.
116 */
117size_t protocore_quic_packet_build_long_header(uint8_t *work, uint8_t *out, size_t cap, uint8_t type, uint32_t version,
118 const uint8_t *dcid, uint8_t dcid_len, const uint8_t *scid,
119 uint8_t scid_len, uint8_t pn_len);
120/**
121 * @brief Parse a short (1-RTT) header given the locally chosen dcid_len. .
122 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
123 * @param buf Buf
124 * @param len Len
125 * @param dcid_len Dcid len
126 * @param out Out
127 * @return PROTO_TRUE on success.
128 */
129proto_bool protocore_quic_packet_parse_short_header(uint8_t *work, const uint8_t *buf, size_t len, uint8_t dcid_len,
130 QuicShortHeader *out);
131/**
132 * @brief Build a Version Negotiation packet (RFC 9000 sec 17.2.1): Version 0 .
133 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
134 * @param out Out
135 * @param cap Cap
136 * @param dcid Dcid
137 * @param dcid_len Dcid len
138 * @param scid Scid
139 * @param scid_len Scid len
140 * @param versions Versions
141 * @param nversions Nversions
142 * @return The size_t.
143 */
144size_t protocore_quic_packet_build_version_negotiation(uint8_t *work, uint8_t *out, size_t cap, const uint8_t *dcid,
145 uint8_t dcid_len, const uint8_t *scid, uint8_t scid_len,
146 const uint32_t *versions, size_t nversions);
147/**
148 * @brief Packet-number length in bytes (1..4) for full_pn; largest_acked < 0 .
149 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
150 * @param full_pn Full pn
151 * @param largest_acked Largest acked
152 * @return The uint8_t.
153 */
154uint8_t protocore_quic_packet_pn_length(uint8_t *work, uint64_t full_pn, int64_t largest_acked);
155/**
156 * @brief Encode full_pn truncated to ::QuicPacketNs::pn_length bytes, .
157 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
158 * @param out Out
159 * @param cap Cap
160 * @param full_pn Full pn
161 * @param largest_acked Largest acked
162 * @return The size_t.
163 */
164size_t protocore_quic_packet_pn_encode(uint8_t *work, uint8_t *out, size_t cap, uint64_t full_pn,
165 int64_t largest_acked);
166/**
167 * @brief Recover the full packet number from a truncated_pn of pn_nbits bits .
168 * @param work PROTOCORE_QUIC_PACKET_BORROW bytes the caller took. Not held past the call.
169 * @param largest_pn Largest pn
170 * @param truncated_pn Truncated pn
171 * @param pn_nbits Pn nbits
172 * @return The uint64_t.
173 */
174uint64_t protocore_quic_packet_pn_decode(uint8_t *work, uint64_t largest_pn, uint64_t truncated_pn, uint8_t pn_nbits);
175
176/** @brief Module namespace. */
186
188
189#endif // PROTOCORE_QUIC_PACKET_H
#define PROTOCORE_NS_LAYOUT(T,...)
Pin every dispatch slot of a table that is nothing but function pointers.
#define PROTOCORE_NS
Storage for a dispatch table. The const is load bearing.
PROTOCORE_NS QuicPacketNs QuicPacket PROTOCORE_UNUSED
Module namespace.
size_t protocore_quic_packet_build_long_header(uint8_t *work, uint8_t *out, size_t cap, uint8_t type, uint32_t version, const uint8_t *dcid, uint8_t dcid_len, const uint8_t *scid, uint8_t scid_len, uint8_t pn_len)
Build a long header's invariant fields (first byte .. Source .
uint64_t protocore_quic_packet_pn_decode(uint8_t *work, uint64_t largest_pn, uint64_t truncated_pn, uint8_t pn_nbits)
Recover the full packet number from a truncated_pn of pn_nbits bits .
proto_bool protocore_quic_packet_is_long_header(uint8_t *work, uint8_t first)
True if byte 0 selects the long header form (0x80 set).
uint8_t protocore_quic_packet_pn_length(uint8_t *work, uint64_t full_pn, int64_t largest_acked)
Packet-number length in bytes (1..4) for full_pn; largest_acked < 0 .
proto_bool protocore_quic_packet_parse_long_header(uint8_t *work, const uint8_t *buf, size_t len, QuicLongHeader *out)
Parse a long header. false if truncated or a connection ID exceeds .
proto_bool protocore_quic_packet_parse_short_header(uint8_t *work, const uint8_t *buf, size_t len, uint8_t dcid_len, QuicShortHeader *out)
Parse a short (1-RTT) header given the locally chosen dcid_len. .
size_t protocore_quic_packet_build_version_negotiation(uint8_t *work, uint8_t *out, size_t cap, const uint8_t *dcid, uint8_t dcid_len, const uint8_t *scid, uint8_t scid_len, const uint32_t *versions, size_t nversions)
Build a Version Negotiation packet (RFC 9000 sec 17.2.1): Version 0 .
size_t protocore_quic_packet_pn_encode(uint8_t *work, uint8_t *out, size_t cap, uint64_t full_pn, int64_t largest_acked)
Encode full_pn truncated to QuicPacketNs::pn_length bytes, .
#define QUIC_MAX_CID_LEN
maximum connection-ID length in QUIC version 1
Definition quic_packet.h:37
A parsed long header (invariant fields). A Version of 0 marks a Version Negotiation.
Definition quic_packet.h:47
size_t hdr_len
bytes consumed up to the start of the type-specific payload
Definition quic_packet.h:55
uint8_t first
raw first byte
Definition quic_packet.h:48
uint32_t version
QUIC version; 0 = Version Negotiation.
Definition quic_packet.h:50
uint8_t scid_len
Source Connection ID length.
Definition quic_packet.h:53
uint8_t type
long packet type (first & 0x30) >> 4; meaningful when version != 0
Definition quic_packet.h:49
uint8_t dcid_len
Destination Connection ID length.
Definition quic_packet.h:51
Dispatch table. Addressed by offset, so the layout is asserted below.
Definition quic_packet.h:72
proto_bool(* is_long_header)(uint8_t *, uint8_t)
Definition quic_packet.h:73
A parsed short header (1-RTT). The DCID length is known locally, not on the wire.
Definition quic_packet.h:60
uint8_t spin
latency spin bit (0x20)
Definition quic_packet.h:62
size_t hdr_len
bytes up to the (protected) Packet Number field
Definition quic_packet.h:67
uint8_t key_phase
key-phase bit (0x04)
Definition quic_packet.h:63
uint8_t dcid_len
Destination Connection ID length (caller-supplied)
Definition quic_packet.h:65
uint8_t first
raw first byte
Definition quic_packet.h:61
uint8_t pn_len
packet-number length in bytes (1..4)
Definition quic_packet.h:64
#define PROTOCORE_BEGIN_DECLS
Give a header's declarations C linkage, so their symbol names carry no parameter types.
Definition types.h:96
_Bool proto_bool
The truth value.
Definition types.h:64
#define PROTOCORE_END_DECLS
Definition types.h:97