DeterministicESPAsyncWebServer 1.2.0
Zero-allocation, bounded-execution async HTTP server for ESP32
Loading...
Searching...
No Matches
websocket.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 websocket.h
6 * @brief Layer 6 (Presentation) -- WebSocket frame parser and connection pool.
7 *
8 * Implements RFC 6455 framing with a fixed-size payload buffer per slot.
9 * Connections are tracked in ws_pool[MAX_WS_CONNS]; each entry maps to one
10 * TCP slot in conn_pool[] via slot_id.
11 *
12 * **Frame format (client to server)**
13 * ```
14 * 0 1 2 3
15 * 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
16 * +-+-+-+-+-------+-+-------------+-------------------------------+
17 * |F|R|R|R| opcode|M| payload len | extended payload length |
18 * |I|S|S|S| (4) |A| (7) | (16/64) |
19 * |N|V|V|V| |S| +-------------------------------+
20 * | |1|2|3| |K| | |
21 * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - -+
22 * | extended payload length continued, if payload len == 127 |
23 * + - - - - - - - - - - - - - - -+-------------------------------+
24 * | | masking key, if MASK set |
25 * +-------------------------------+-------------------------------+
26 * | masking key (continued) | payload data |
27 * +-------------------------------- - - - - - - - - - - - - - - -+
28 * : payload data continued :
29 * +---------------------------------------------------------------+
30 * ```
31 *
32 * **State machine**
33 * ```
34 * WS_HEADER1 -- read FIN + opcode byte
35 * WS_HEADER2 -- read MASK + 7-bit payload length
36 * WS_LEN16_HI -- read extended 16-bit length high byte
37 * WS_LEN16_LO -- read extended 16-bit length low byte
38 * WS_LEN64 -- consume 8-byte 64-bit length (reject; too large)
39 * WS_MASK0..3 -- read 4-byte masking key
40 * WS_PAYLOAD -- accumulate payload bytes (unmasked)
41 * WS_FRAME_READY -- complete frame waiting for dispatch
42 * WS_CLOSED -- connection closed; slot may be recycled
43 * WS_ERROR -- protocol error; close frame sent
44 * ```
45 *
46 * **Limitations**
47 * - Fragmented messages are not supported (FIN=0 frames close with 1003).
48 * - Payload larger than WS_FRAME_SIZE closes with 1009.
49 * - RSV bits must be zero (no extensions supported).
50 *
51 * @author Douglas Quigg (dstroy0)
52 * @date 2026
53 */
54
55#ifndef DETERMINISTICESPASYNCWEBSERVER_WEBSOCKET_H
56#define DETERMINISTICESPASYNCWEBSERVER_WEBSOCKET_H
57
58#include "DetWebServerConfig.h"
59#include "transport.h"
60
61// ---------------------------------------------------------------------------
62// WebSocket opcodes (RFC 6455 §5.2)
63// ---------------------------------------------------------------------------
64
65/** @brief WebSocket frame opcodes. */
67{
68 WS_OP_CONTINUATION = 0x0, ///< Continuation frame (fragmentation -- rejected).
69 WS_OP_TEXT = 0x1, ///< UTF-8 text payload.
70 WS_OP_BINARY = 0x2, ///< Binary payload.
71 WS_OP_CLOSE = 0x8, ///< Connection close.
72 WS_OP_PING = 0x9, ///< Ping (auto-ponged by the library).
73 WS_OP_PONG = 0xA ///< Pong (echoed ping; ignored by library).
74};
75
76/** @brief WebSocket close status codes (RFC 6455 §7.4.1). */
78{
79 WS_CLOSE_NORMAL = 1000, ///< Normal closure.
80 WS_CLOSE_GOING_AWAY= 1001, ///< Endpoint going away.
81 WS_CLOSE_PROTOCOL = 1002, ///< Protocol error.
82 WS_CLOSE_UNSUPPORTED=1003, ///< Unsupported data (e.g. fragmented message).
83 WS_CLOSE_TOO_BIG = 1009 ///< Payload too large for WS_FRAME_SIZE.
84};
85
86// ---------------------------------------------------------------------------
87// Frame parser states
88// ---------------------------------------------------------------------------
89
90/** @brief States of the WebSocket frame parser. */
92{
93 WS_HEADER1, ///< Awaiting first header byte (FIN, RSV, opcode).
94 WS_HEADER2, ///< Awaiting second header byte (MASK, 7-bit length).
95 WS_LEN16_HI, ///< Reading extended 16-bit length, high byte.
96 WS_LEN16_LO, ///< Reading extended 16-bit length, low byte.
97 WS_LEN64, ///< Consuming 8-byte 64-bit length (always rejected).
98 WS_MASK0, ///< Reading masking key byte 0.
99 WS_MASK1, ///< Reading masking key byte 1.
100 WS_MASK2, ///< Reading masking key byte 2.
101 WS_MASK3, ///< Reading masking key byte 3.
102 WS_PAYLOAD, ///< Accumulating payload bytes.
103 WS_FRAME_READY, ///< Complete frame ready for dispatch.
104 WS_CLOSED, ///< Connection closed; slot may be recycled.
105 WS_ERROR ///< Protocol error; close frame has been queued.
107
108// ---------------------------------------------------------------------------
109// Per-connection WebSocket state
110// ---------------------------------------------------------------------------
111
112/**
113 * @brief WebSocket connection state stored in ws_pool[].
114 *
115 * Allocated when an HTTP upgrade handshake succeeds. slot_id ties this
116 * entry back to conn_pool[] and the ring buffer.
117 */
118struct WsConn
119{
120 uint8_t ws_id; ///< Index into ws_pool[] (set at init).
121 uint8_t slot_id; ///< Owning TCP slot in conn_pool[].
122 bool active; ///< True when this entry is in use.
123
124 WsParseState parse_state; ///< Current frame parser state.
125 WsOpcode opcode; ///< Opcode of the frame being parsed.
126 bool fin; ///< FIN bit of the frame being parsed.
127 bool masked; ///< True if client sent a masking key.
128
129 uint8_t mask_key[4]; ///< Client masking key.
130 uint32_t payload_len; ///< Expected payload byte count.
131 uint32_t payload_idx; ///< Bytes received so far.
132 uint8_t len64_count; ///< Bytes consumed from 64-bit length.
133 uint8_t buf[WS_FRAME_SIZE + 1]; ///< Unmasked payload, null-terminated.
134};
135
136/** @brief Pool of WebSocket connection state, one per MAX_WS_CONNS. */
138
139// ---------------------------------------------------------------------------
140// WebSocket API
141// ---------------------------------------------------------------------------
142
143/**
144 * @brief Initialise all WebSocket pool slots to inactive.
145 *
146 * Called once from DetWebServer::begin().
147 */
148void ws_init();
149
150/**
151 * @brief Allocate a WsConn slot and bind it to a TCP slot.
152 *
153 * @param slot_id TCP connection slot that just completed an upgrade.
154 * @return Pointer to the allocated WsConn, or nullptr if the pool is full.
155 */
156WsConn *ws_alloc(uint8_t slot_id);
157
158/**
159 * @brief Find the WsConn for a given TCP slot, or nullptr if none.
160 *
161 * @param slot_id TCP connection slot index.
162 */
163WsConn *ws_find(uint8_t slot_id);
164
165/**
166 * @brief Free the WsConn associated with a TCP slot.
167 *
168 * @param slot_id TCP connection slot index.
169 */
170void ws_free(uint8_t slot_id);
171
172/**
173 * @brief Drain the ring buffer for slot_id and feed bytes to the WS parser.
174 *
175 * Stops when the ring buffer is empty or the parser reaches a terminal state
176 * (WS_FRAME_READY, WS_CLOSED, WS_ERROR).
177 *
178 * @param ws WebSocket connection to drain into.
179 */
180void ws_parse(WsConn *ws);
181
182/**
183 * @brief Reset the frame parser back to WS_HEADER1, ready for the next frame.
184 *
185 * Does not change ws->active or ws->slot_id.
186 *
187 * @param ws WebSocket connection to reset.
188 */
189void ws_reset_frame(WsConn *ws);
190
191/**
192 * @brief Send a WebSocket frame to the client.
193 *
194 * Builds the header (no masking -- server-to-client frames are never masked)
195 * and hands both to tcp_write(). The caller is responsible for calling
196 * tcp_output() afterwards.
197 *
198 * @param ws WebSocket connection.
199 * @param opcode Frame opcode (WS_OP_TEXT, WS_OP_BINARY, WS_OP_PONG, etc.).
200 * @param payload Payload bytes (may be nullptr for zero-length frames).
201 * @param len Payload length in bytes.
202 * @return true on success, false if the TCP slot is not active.
203 */
204bool ws_send_frame(WsConn *ws, WsOpcode opcode,
205 const uint8_t *payload, uint16_t len);
206
207/**
208 * @brief Send a Close frame and mark the slot WS_CLOSED.
209 *
210 * @param ws WebSocket connection.
211 * @param code Close status code (e.g. WS_CLOSE_NORMAL).
212 */
213void ws_close(WsConn *ws, WsCloseCode code);
214
215#endif
User-facing configuration for DeterministicESPAsyncWebServer.
#define WS_FRAME_SIZE
Maximum WebSocket frame payload in bytes.
#define MAX_WS_CONNS
Maximum simultaneous WebSocket connections.
WebSocket connection state stored in ws_pool[].
Definition websocket.h:119
bool active
True when this entry is in use.
Definition websocket.h:122
uint8_t len64_count
Bytes consumed from 64-bit length.
Definition websocket.h:132
uint8_t mask_key[4]
Client masking key.
Definition websocket.h:129
uint32_t payload_len
Expected payload byte count.
Definition websocket.h:130
uint8_t ws_id
Index into ws_pool[] (set at init).
Definition websocket.h:120
bool fin
FIN bit of the frame being parsed.
Definition websocket.h:126
bool masked
True if client sent a masking key.
Definition websocket.h:127
uint8_t slot_id
Owning TCP slot in conn_pool[].
Definition websocket.h:121
uint32_t payload_idx
Bytes received so far.
Definition websocket.h:131
WsParseState parse_state
Current frame parser state.
Definition websocket.h:124
WsOpcode opcode
Opcode of the frame being parsed.
Definition websocket.h:125
uint8_t buf[WS_FRAME_SIZE+1]
Unmasked payload, null-terminated.
Definition websocket.h:133
Layer 4 (Transport) — TCP connection pool, ring buffers, and lwIP integration.
WsParseState
States of the WebSocket frame parser.
Definition websocket.h:92
@ WS_LEN16_LO
Reading extended 16-bit length, low byte.
Definition websocket.h:96
@ WS_HEADER1
Awaiting first header byte (FIN, RSV, opcode).
Definition websocket.h:93
@ WS_MASK0
Reading masking key byte 0.
Definition websocket.h:98
@ WS_FRAME_READY
Complete frame ready for dispatch.
Definition websocket.h:103
@ WS_HEADER2
Awaiting second header byte (MASK, 7-bit length).
Definition websocket.h:94
@ WS_MASK2
Reading masking key byte 2.
Definition websocket.h:100
@ WS_LEN16_HI
Reading extended 16-bit length, high byte.
Definition websocket.h:95
@ WS_ERROR
Protocol error; close frame has been queued.
Definition websocket.h:105
@ WS_CLOSED
Connection closed; slot may be recycled.
Definition websocket.h:104
@ WS_PAYLOAD
Accumulating payload bytes.
Definition websocket.h:102
@ WS_MASK1
Reading masking key byte 1.
Definition websocket.h:99
@ WS_MASK3
Reading masking key byte 3.
Definition websocket.h:101
@ WS_LEN64
Consuming 8-byte 64-bit length (always rejected).
Definition websocket.h:97
void ws_free(uint8_t slot_id)
Free the WsConn associated with a TCP slot.
Definition websocket.cpp:60
bool ws_send_frame(WsConn *ws, WsOpcode opcode, const uint8_t *payload, uint16_t len)
Send a WebSocket frame to the client.
Definition websocket.cpp:90
void ws_close(WsConn *ws, WsCloseCode code)
Send a Close frame and mark the slot WS_CLOSED.
WsOpcode
WebSocket frame opcodes.
Definition websocket.h:67
@ WS_OP_TEXT
UTF-8 text payload.
Definition websocket.h:69
@ WS_OP_CLOSE
Connection close.
Definition websocket.h:71
@ WS_OP_CONTINUATION
Continuation frame (fragmentation – rejected).
Definition websocket.h:68
@ WS_OP_PONG
Pong (echoed ping; ignored by library).
Definition websocket.h:73
@ WS_OP_PING
Ping (auto-ponged by the library).
Definition websocket.h:72
@ WS_OP_BINARY
Binary payload.
Definition websocket.h:70
void ws_parse(WsConn *ws)
Drain the ring buffer for slot_id and feed bytes to the WS parser.
WsCloseCode
WebSocket close status codes (RFC 6455 §7.4.1).
Definition websocket.h:78
@ WS_CLOSE_NORMAL
Normal closure.
Definition websocket.h:79
@ WS_CLOSE_PROTOCOL
Protocol error.
Definition websocket.h:81
@ WS_CLOSE_TOO_BIG
Payload too large for WS_FRAME_SIZE.
Definition websocket.h:83
@ WS_CLOSE_GOING_AWAY
Endpoint going away.
Definition websocket.h:80
@ WS_CLOSE_UNSUPPORTED
Unsupported data (e.g. fragmented message).
Definition websocket.h:82
WsConn * ws_find(uint8_t slot_id)
Find the WsConn for a given TCP slot, or nullptr if none.
Definition websocket.cpp:50
void ws_reset_frame(WsConn *ws)
Reset the frame parser back to WS_HEADER1, ready for the next frame.
Definition websocket.cpp:73
WsConn * ws_alloc(uint8_t slot_id)
Allocate a WsConn slot and bind it to a TCP slot.
Definition websocket.cpp:33
WsConn ws_pool[MAX_WS_CONNS]
Pool of WebSocket connection state, one per MAX_WS_CONNS.
Definition websocket.cpp:22
void ws_init()
Initialise all WebSocket pool slots to inactive.
Definition websocket.cpp:24