DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
quic_packet.h
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.h
6 * @brief QUIC packet headers and packet-number coding (RFC 9000 sec 17).
7 *
8 * The structural, version-independent layer of a QUIC packet: the long-header form (Initial /
9 * 0-RTT / Handshake / Retry, plus the Version Negotiation packet whose Version is 0) and the
10 * short-header 1-RTT form, and the packet-number truncation coding (sec 17.1, Appendix A.2/A.3).
11 *
12 * This is the unprotected structure only - it parses and builds the header fields that are not
13 * covered by header protection (header form, version, connection IDs) and codes packet numbers.
14 * Packet protection (AEAD) and header protection are layered on top by the QUIC crypto module.
15 * Pure, zero heap, host-tested against the RFC worked examples.
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef DETERMINISTICESPASYNCWEBSERVER_QUIC_PACKET_H
22#define DETERMINISTICESPASYNCWEBSERVER_QUIC_PACKET_H
23
24#include "ServerConfig.h"
25
26#if DETWS_ENABLE_HTTP3
27
28#include <stddef.h>
29#include <stdint.h>
30
31#define QUIC_VERSION_1 0x00000001u ///< RFC 9000
32#define QUIC_MAX_CID_LEN 20 ///< maximum connection-ID length in QUIC version 1
33
34/** @brief Long-header packet types (RFC 9000 sec 17.2, Table 5). */
35struct QuicLongPacket
36{
37 static constexpr uint8_t QUIC_LP_INITIAL = 0x00;
38 static constexpr uint8_t QUIC_LP_0RTT = 0x01;
39 static constexpr uint8_t QUIC_LP_HANDSHAKE = 0x02;
40 static constexpr uint8_t QUIC_LP_RETRY = 0x03;
41};
42
43/** @brief A parsed long header (invariant fields). A Version of 0 marks a Version Negotiation. */
44struct QuicLongHeader
45{
46 uint8_t first; ///< raw first byte
47 uint8_t type; ///< long packet type (first & 0x30) >> 4; meaningful when version != 0
48 uint32_t version; ///< QUIC version; 0 = Version Negotiation
49 uint8_t dcid_len; ///< Destination Connection ID length
50 uint8_t dcid[QUIC_MAX_CID_LEN]; ///< Destination Connection ID
51 uint8_t scid_len; ///< Source Connection ID length
52 uint8_t scid[QUIC_MAX_CID_LEN]; ///< Source Connection ID
53 size_t hdr_len; ///< bytes consumed up to the start of the type-specific payload
54};
55
56/** @brief A parsed short header (1-RTT). The DCID length is known locally, not on the wire. */
57struct QuicShortHeader
58{
59 uint8_t first; ///< raw first byte
60 uint8_t spin; ///< latency spin bit (0x20)
61 uint8_t key_phase; ///< key-phase bit (0x04)
62 uint8_t pn_len; ///< packet-number length in bytes (1..4)
63 uint8_t dcid_len; ///< Destination Connection ID length (caller-supplied)
64 uint8_t dcid[QUIC_MAX_CID_LEN]; ///< Destination Connection ID
65 size_t hdr_len; ///< bytes up to the (protected) Packet Number field
66};
67
68/** @brief True if byte 0 selects the long header form (0x80 set). */
69bool quic_is_long_header(uint8_t first);
70
71/** @brief Parse a long header. @return false if truncated or a connection ID exceeds 20 bytes. */
72bool quic_parse_long_header(const uint8_t *buf, size_t len, QuicLongHeader *out);
73
74/**
75 * @brief Build a long header's invariant fields (first byte .. Source Connection ID). The caller
76 * appends the type-specific fields (Token / Length / Packet Number / payload). @p pn_len is the
77 * packet-number length in bytes (1..4); the reserved bits are written as 0 (pre-protection).
78 * @return bytes written, or 0 on overflow / bad length.
79 */
80size_t quic_build_long_header(uint8_t *out, size_t cap, uint8_t type, uint32_t version, const uint8_t *dcid,
81 uint8_t dcid_len, const uint8_t *scid, uint8_t scid_len, uint8_t pn_len);
82
83/** @brief Parse a short (1-RTT) header given the locally chosen @p dcid_len. @return false if truncated. */
84bool quic_parse_short_header(const uint8_t *buf, size_t len, uint8_t dcid_len, QuicShortHeader *out);
85
86/**
87 * @brief Build a Version Negotiation packet (RFC 9000 sec 17.2.1): Version 0 and the list of
88 * @p versions the server supports. The caller passes the connection IDs already echoed (its DCID =
89 * the received SCID, its SCID = the received DCID). @return bytes written, or 0 on overflow.
90 */
91size_t quic_build_version_negotiation(uint8_t *out, size_t cap, const uint8_t *dcid, uint8_t dcid_len,
92 const uint8_t *scid, uint8_t scid_len, const uint32_t *versions,
93 size_t nversions);
94
95// --- Packet-number coding (RFC 9000 sec 17.1, Appendix A.2 / A.3) -----------------------------
96
97/** @brief Packet-number length in bytes (1..4) for @p full_pn; @p largest_acked < 0 means none. */
98uint8_t quic_pn_length(uint64_t full_pn, int64_t largest_acked);
99
100/** @brief Encode @p full_pn truncated to quic_pn_length() bytes, big-endian. @return bytes written or 0. */
101size_t quic_pn_encode(uint8_t *out, size_t cap, uint64_t full_pn, int64_t largest_acked);
102
103/** @brief Recover the full packet number from a @p truncated_pn of @p pn_nbits bits (Appendix A.3). */
104uint64_t quic_pn_decode(uint64_t largest_pn, uint64_t truncated_pn, uint8_t pn_nbits);
105
106#endif // DETWS_ENABLE_HTTP3
107#endif // DETERMINISTICESPASYNCWEBSERVER_QUIC_PACKET_H
User-facing configuration for DeterministicESPAsyncWebServer.