DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
quic_frame.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_frame.h
6 * @brief QUIC frame parsing and building (RFC 9000 sec 19).
7 *
8 * The payload of a QUIC packet is a sequence of frames, each `Frame Type (i)` followed by
9 * type-specific fields coded with QUIC varints. This module reads one frame at a time into a
10 * tagged QuicFrame and builds the frames a server sends. It covers the frames a minimal HTTP/3
11 * server needs - PADDING, PING, ACK, CRYPTO, STREAM, MAX_DATA, CONNECTION_CLOSE, HANDSHAKE_DONE -
12 * and reports the frame type for anything else so the caller can decide.
13 *
14 * Data-bearing frames (CRYPTO / STREAM / CONNECTION_CLOSE reason) point into the caller's packet
15 * buffer; nothing is copied. Pure, zero heap, host-tested.
16 *
17 * @author Douglas Quigg (dstroy0)
18 * @date 2026
19 */
20
21#ifndef DETERMINISTICESPASYNCWEBSERVER_QUIC_FRAME_H
22#define DETERMINISTICESPASYNCWEBSERVER_QUIC_FRAME_H
23
24#include "ServerConfig.h"
25
26#if DETWS_ENABLE_HTTP3
27
28#include <stddef.h>
29#include <stdint.h>
30
31/** @brief Frame types (RFC 9000 sec 19 / Table 3). STREAM is the range 0x08..0x0f. */
32struct QuicFrameType
33{
34 static constexpr uint8_t QUIC_FT_PADDING = 0x00;
35 static constexpr uint8_t QUIC_FT_PING = 0x01;
36 static constexpr uint8_t QUIC_FT_ACK = 0x02; ///< 0x02 (no ECN) .. 0x03 (with ECN counts)
37 static constexpr uint8_t QUIC_FT_ACK_ECN = 0x03;
38 static constexpr uint8_t QUIC_FT_CRYPTO = 0x06;
39 static constexpr uint8_t QUIC_FT_STREAM = 0x08; ///< 0x08..0x0f; low 3 bits are OFF (0x04) / LEN (0x02) / FIN (0x01)
40 static constexpr uint8_t QUIC_FT_MAX_DATA = 0x10;
41 static constexpr uint8_t QUIC_FT_CONNECTION_CLOSE =
42 0x1c; ///< transport-level close (carries the triggering frame type)
43 static constexpr uint8_t QUIC_FT_CONNECTION_CLOSE_APP = 0x1d; ///< application-level close
44 static constexpr uint8_t QUIC_FT_HANDSHAKE_DONE = 0x1e;
45 // Frames the minimal server does not act on but MUST still parse (skip) so a well-formed frame from
46 // a real client is not rejected as a FRAME_ENCODING_ERROR (RFC 9000 sec 12.4). Grouped by wire shape
47 // in quic_frame_parse(): 3 varints (RESET_STREAM), 2 varints (STOP_SENDING / MAX_STREAM_DATA /
48 // STREAM_DATA_BLOCKED), 1 varint (MAX_STREAMS / DATA_BLOCKED / STREAMS_BLOCKED / RETIRE_CONNECTION_ID),
49 // and the length-prefixed / fixed-width shapes (NEW_TOKEN, NEW_CONNECTION_ID, PATH_CHALLENGE/RESPONSE).
50 static constexpr uint8_t QUIC_FT_RESET_STREAM = 0x04;
51 static constexpr uint8_t QUIC_FT_STOP_SENDING = 0x05;
52 static constexpr uint8_t QUIC_FT_NEW_TOKEN = 0x07;
53 static constexpr uint8_t QUIC_FT_MAX_STREAM_DATA = 0x11;
54 static constexpr uint8_t QUIC_FT_MAX_STREAMS_BIDI = 0x12;
55 static constexpr uint8_t QUIC_FT_MAX_STREAMS_UNI = 0x13;
56 static constexpr uint8_t QUIC_FT_DATA_BLOCKED = 0x14;
57 static constexpr uint8_t QUIC_FT_STREAM_DATA_BLOCKED = 0x15;
58 static constexpr uint8_t QUIC_FT_STREAMS_BLOCKED_BIDI = 0x16;
59 static constexpr uint8_t QUIC_FT_STREAMS_BLOCKED_UNI = 0x17;
60 static constexpr uint8_t QUIC_FT_NEW_CONNECTION_ID = 0x18;
61 static constexpr uint8_t QUIC_FT_RETIRE_CONNECTION_ID = 0x19;
62 static constexpr uint8_t QUIC_FT_PATH_CHALLENGE = 0x1a;
63 static constexpr uint8_t QUIC_FT_PATH_RESPONSE = 0x1b;
64};
65
66/** @brief STREAM frame type bits. */
67struct QuicStreamFlag
68{
69 static constexpr uint8_t QUIC_STREAM_FIN = 0x01;
70 static constexpr uint8_t QUIC_STREAM_LEN = 0x02;
71 static constexpr uint8_t QUIC_STREAM_OFF = 0x04;
72};
73
74/** @brief Transport error codes for CONNECTION_CLOSE (RFC 9000 sec 20.1). */
75struct QuicErr
76{
77 static constexpr uint16_t QUIC_ERR_NO_ERROR = 0x00;
78 static constexpr uint16_t QUIC_ERR_INTERNAL = 0x01;
79 static constexpr uint16_t QUIC_ERR_FLOW_CONTROL = 0x03;
80 static constexpr uint16_t QUIC_ERR_STREAM_LIMIT = 0x04;
81 static constexpr uint16_t QUIC_ERR_FRAME_ENCODING = 0x07; ///< a frame could not be decoded
82 static constexpr uint16_t QUIC_ERR_PROTOCOL_VIOLATION = 0x0a; ///< a frame/packet violated the protocol
83 static constexpr uint16_t QUIC_ERR_CRYPTO_BASE = 0x0100; ///< 0x0100 + the TLS alert code (RFC 9001 sec 4.8)
84};
85
86/** @brief One parsed frame. Pointer fields alias the input buffer (not copied). */
87struct QuicFrame
88{
89 uint64_t type; ///< the frame type (STREAM reported as its exact 0x08..0x0f value)
90 union {
91 struct
92 {
93 uint64_t largest; ///< Largest Acknowledged
94 uint64_t delay; ///< ACK Delay (encoded units)
95 uint64_t range_count; ///< number of additional ACK Ranges (skipped, but counted)
96 uint64_t first_range; ///< First ACK Range
97 } ack;
98 struct
99 {
100 uint64_t offset;
101 uint64_t length;
102 const uint8_t *data;
103 } crypto;
104 struct
105 {
106 uint64_t id;
107 uint64_t offset; ///< 0 when the OFF bit is clear
108 uint64_t length;
109 const uint8_t *data;
110 uint8_t fin;
111 } stream;
112 struct
113 {
114 uint64_t max;
115 } max_data;
116 struct
117 {
118 uint64_t error_code;
119 uint64_t frame_type; ///< 0 for the application-level variant (0x1d)
120 uint64_t reason_len;
121 const uint8_t *reason;
122 uint8_t app; ///< 1 if this was the application-level close (0x1d)
123 } close;
124 };
125};
126
127/** @brief Parse one frame at @p buf. @return bytes consumed, or 0 on malformed / truncated input. */
128size_t quic_frame_parse(const uint8_t *buf, size_t len, QuicFrame *out);
129
130// --- Builders (server side) ------------------------------------------------------------------
131
132/** @brief @p n PADDING frames (n zero bytes). @return n, or 0 if it does not fit. */
133size_t quic_build_padding(uint8_t *out, size_t cap, size_t n);
134/** @brief A PING frame. */
135size_t quic_build_ping(uint8_t *out, size_t cap);
136/** @brief A HANDSHAKE_DONE frame. */
137size_t quic_build_handshake_done(uint8_t *out, size_t cap);
138/** @brief A single-range ACK frame (ACK Range Count 0): Largest, ACK Delay, First ACK Range. */
139size_t quic_build_ack(uint8_t *out, size_t cap, uint64_t largest, uint64_t delay, uint64_t first_range);
140/** @brief A CRYPTO frame carrying @p len bytes at stream @p offset. */
141size_t quic_build_crypto(uint8_t *out, size_t cap, uint64_t offset, const uint8_t *data, size_t len);
142/** @brief A STREAM frame (LEN always set; OFF set when @p offset > 0; FIN per @p fin). */
143size_t quic_build_stream(uint8_t *out, size_t cap, uint64_t id, uint64_t offset, const uint8_t *data, size_t len,
144 bool fin);
145/** @brief A MAX_DATA frame. */
146size_t quic_build_max_data(uint8_t *out, size_t cap, uint64_t max);
147/** @brief A transport CONNECTION_CLOSE (0x1c) with the triggering @p frame_type and a reason phrase. */
148size_t quic_build_connection_close(uint8_t *out, size_t cap, uint64_t error_code, uint64_t frame_type,
149 const char *reason, size_t reason_len);
150
151#endif // DETWS_ENABLE_HTTP3
152#endif // DETERMINISTICESPASYNCWEBSERVER_QUIC_FRAME_H
User-facing configuration for DeterministicESPAsyncWebServer.