DeterministicESPAsyncWebServer v6.27.1
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
h2_conn.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 h2_conn.h
6 * @brief HTTP/2 connection + stream engine (RFC 9113) over the HPACK + frame layers.
7 *
8 * One H2Conn drives a single HTTP/2 connection: it consumes the client connection preface, the
9 * SETTINGS exchange, and the frame stream; reassembles each request's header block (HEADERS +
10 * CONTINUATION) and HPACK-decodes it; tracks per-stream state and connection / stream flow
11 * control; and answers control frames (SETTINGS ACK, PING ACK, WINDOW_UPDATE). Decoded requests
12 * and body data are handed to the application through callbacks, and h2_conn_respond() serializes
13 * a response as HEADERS + DATA frames. Outbound bytes go through a caller-supplied writer, so the
14 * engine has no transport dependency and is host-testable by feeding it a byte stream.
15 *
16 * Fixed storage, no heap: one frame-reassembly buffer, one header-block buffer, an HPACK decoder
17 * table, and a small stream table per connection (sizes from DETWS_H2_*).
18 *
19 * @author Douglas Quigg (dstroy0)
20 * @date 2026
21 */
22
23#ifndef DETERMINISTICESPASYNCWEBSERVER_H2_CONN_H
24#define DETERMINISTICESPASYNCWEBSERVER_H2_CONN_H
25
26#include "ServerConfig.h"
27
28#if DETWS_ENABLE_HTTP2
29
32#include <stddef.h>
33#include <stdint.h>
34
35/** @brief Per-stream state (RFC 9113 sec 5.1, server side of a client-initiated stream). A
36 * mutually-exclusive internal lifecycle state, not a wire value. */
37enum class H2StreamState : uint8_t
38{
39 H2_ST_IDLE = 0,
40 H2_ST_OPEN, ///< receiving (headers seen, no END_STREAM yet)
41 H2_ST_HALF_CLOSED, ///< client finished (END_STREAM); we may still respond
42 H2_ST_CLOSED,
43};
44
45struct H2Stream
46{
47 uint32_t id; ///< stream identifier (0 = free slot)
48 H2StreamState state; ///< lifecycle state
49 int32_t send_window; ///< our remaining DATA flow window for this stream
50};
51
52/** @brief Application callbacks the engine drives (all optional except write). */
53struct H2Callbacks
54{
55 /** @brief Send @p len bytes to the peer (through TLS/transport); must send all. */
56 void (*write)(void *io, const uint8_t *data, size_t len);
57 /** @brief One decoded request header on @p stream_id (pseudo-headers included). */
58 void (*on_header)(void *app, uint32_t stream_id, const char *name, size_t nlen, const char *val, size_t vlen);
59 /** @brief The request header block for @p stream_id is complete. @p end_stream: no body. */
60 void (*on_headers_end)(void *app, uint32_t stream_id, bool end_stream);
61 /** @brief Request body bytes on @p stream_id (@p end_stream marks the last). */
62 void (*on_data)(void *app, uint32_t stream_id, const uint8_t *data, size_t len, bool end_stream);
63 void *io; ///< opaque, passed to write()
64 void *app; ///< opaque, passed to the on_* callbacks
65};
66
67/** @brief One HTTP/2 connection's engine state (fixed storage, no heap). */
68struct H2Conn
69{
70 uint8_t phase; ///< 0 = awaiting preface, 1 = running, 2 = closed
71 H2Callbacks cb;
72
73 // Inbound frame reassembly.
74 uint8_t fbuf[H2_FRAME_HEADER_LEN + DETWS_H2_MAX_FRAME];
75 size_t fhave; ///< bytes buffered for the current frame
76 size_t pre; ///< preface bytes matched so far
77
78 // Header-block reassembly (HEADERS + CONTINUATION); empty when a frame carries END_HEADERS.
79 uint8_t hblock[DETWS_H2_HDR_BLOCK];
80 size_t hblock_len;
81 uint32_t hblock_stream;
82 bool hblock_end_stream;
83 bool in_header_block; ///< between a non-END_HEADERS HEADERS and its END_HEADERS CONTINUATION
84
85 HpackDynTable hdec; ///< HPACK decoder (peer's encoder state)
86 char hscratch[DETWS_H2_HDR_BLOCK]; ///< HPACK per-header emit scratch
87
88 H2Settings peer; ///< the peer's settings (affect how we send)
89 int32_t conn_send_window; ///< our connection-level DATA flow window
90
91 H2Stream streams[DETWS_H2_MAX_STREAMS];
92 uint32_t last_peer_stream; ///< highest client (odd) stream id accepted
93};
94
95/** @brief Initialize a connection engine and send our initial SETTINGS via cb.write. */
96void h2_conn_init(H2Conn *c, const H2Callbacks *cb);
97
98/**
99 * @brief Feed inbound bytes. Drives the state machine, invokes callbacks, and writes control
100 * frames. @return false on a fatal connection error (the caller sends GOAWAY and closes).
101 */
102bool h2_conn_recv(H2Conn *c, const uint8_t *data, size_t len);
103
104/**
105 * @brief Serialize a complete response (status + optional content-type + body) as a HEADERS
106 * frame (HPACK) followed by a DATA frame on @p stream_id, and close the stream. @return false on
107 * a bad stream / serialization overflow.
108 */
109bool h2_conn_respond(H2Conn *c, uint32_t stream_id, int status, const char *content_type, const char *body,
110 size_t body_len);
111
112/** @brief Send a GOAWAY (last accepted stream, @p error) to begin a graceful shutdown. */
113void h2_conn_goaway(H2Conn *c, uint32_t error);
114
115#endif // DETWS_ENABLE_HTTP2
116#endif // DETERMINISTICESPASYNCWEBSERVER_H2_CONN_H
User-facing configuration for DeterministicESPAsyncWebServer.
#define DETWS_H2_MAX_FRAME
Largest HTTP/2 frame we accept, in bytes (advertised as SETTINGS_MAX_FRAME_SIZE). RFC 9113 requires a...
#define DETWS_H2_HDR_BLOCK
Header-block reassembly buffer for HTTP/2 requests that span HEADERS + CONTINUATION frames (a single ...
#define DETWS_H2_MAX_STREAMS
Max concurrent HTTP/2 streams per connection (advertised as MAX_CONCURRENT_STREAMS).
HTTP/2 binary framing (RFC 9113 sec 4 + sec 6).
HPACK header compression for HTTP/2 (RFC 7541).