ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
Loading...
Searching...
No Matches
websocket.cpp
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.cpp
6 * @brief WebSocket frame parser and connection pool implementation.
7 *
8 * Handles RFC 6455 framing. Control frames (ping/pong/close) are handled
9 * automatically here; data frames (text/binary) are surfaced to the
10 * application layer via WsParseState::WS_FRAME_READY.
11 *
12 * **Automatic control frame handling**
13 * - Ping -> sends Pong with the same payload immediately.
14 * - Close -> sends echoed Close frame, marks slot WsParseState::WS_CLOSED.
15 * - Pong -> silently discarded (keepalive response, no action needed).
16 */
17
18#include "websocket.h"
21#include <string.h>
22
23#if PC_ENABLE_WS_DEFLATE
26#include "server/mmgr/scratch.h"
27#endif
28
30
31void ws_init()
32{
33 for (int i = 0; i < MAX_WS_CONNS; i++)
34 {
35 ws_pool[i] = {};
36 ws_pool[i].ws_id = (uint8_t)i;
37 }
38}
39
40bool ws_active(uint8_t ws_id)
41{
42 return ws_id < MAX_WS_CONNS && ws_pool[ws_id].active;
43}
44
45const char *ws_payload(uint8_t ws_id)
46{
47 return (ws_id < MAX_WS_CONNS && ws_pool[ws_id].active) ? (const char *)ws_pool[ws_id].buf : nullptr;
48}
49
50WsConn *ws_alloc(uint8_t slot_id)
51{
52 for (int i = 0; i < MAX_WS_CONNS; i++)
53 {
54 if (!ws_pool[i].active)
55 {
56 ws_pool[i] = {};
57 ws_pool[i].ws_id = (uint8_t)i;
58 ws_pool[i].slot_id = slot_id;
59 ws_pool[i].active = true;
61 return &ws_pool[i];
62 }
63 }
64 return nullptr;
65}
66
67WsConn *ws_find(uint8_t slot_id)
68{
69 for (int i = 0; i < MAX_WS_CONNS; i++)
70 {
71 if (ws_pool[i].active && ws_pool[i].slot_id == slot_id)
72 {
73 return &ws_pool[i];
74 }
75 }
76 return nullptr;
77}
78
79void ws_free(uint8_t slot_id)
80{
81 for (int i = 0; i < MAX_WS_CONNS; i++)
82 {
83 if (ws_pool[i].active && ws_pool[i].slot_id == slot_id)
84 {
85 ws_pool[i] = {};
86 ws_pool[i].ws_id = (uint8_t)i;
87 return;
88 }
89 }
90}
91
92// Reset only the per-frame parser fields, preserving any in-progress
93// fragmented-message state (msg_len/msg_opcode/fragmenting/buf). Used to
94// resume reading the next frame after handling an interleaved control frame.
95static void ws_reset_perframe(WsConn *ws)
96{
99 ws->fin = false;
100 ws->masked = false;
101 ws->payload_len = 0;
102 ws->payload_idx = 0;
103 ws->len64_count = 0;
104 ws->mask_key[0] = ws->mask_key[1] = ws->mask_key[2] = ws->mask_key[3] = 0;
105}
106
108{
109 ws_reset_perframe(ws);
110 // Also clear reassembly state - a full reset between messages.
111 ws->fragmenting = false;
113 ws->msg_len = 0;
114 ws->buf[0] = '\0';
115 ws->ctl_buf[0] = '\0';
116}
117
118// ---------------------------------------------------------------------------
119// Frame send helpers
120// ---------------------------------------------------------------------------
121
122// WebSocket presentation config, owned by one instance (internal linkage): the outbound
123// fragmentation size (RFC 6455 sec 5.4), payload bytes; 0 = one frame per message (default).
124// One named owner, unreachable cross-TU. (The ws_pool[] table is the shared substrate.)
125struct WsCtx
126{
128};
129static WsCtx s_ws;
130void ws_set_frag_size(uint16_t bytes)
131{
132 s_ws.frag_size = bytes;
133}
134
135// Emit one WebSocket frame. b0 is the finished first header byte (FIN | RSV1 | opcode). Server frames
136// are never masked (RFC 6455 sec 5.1). Returns false if a transport send fails.
137static bool ws_emit_one(TcpConn *conn, uint8_t b0, const uint8_t *payload, uint16_t len)
138{
139 uint8_t header[4];
140 uint8_t hlen;
141 header[0] = b0;
142 if (len <= 125)
143 {
144 header[1] = (uint8_t)len;
145 hlen = 2;
146 }
147 else
148 {
149 header[1] = 126;
150 header[2] = (uint8_t)(len >> 8);
151 header[3] = (uint8_t)len;
152 hlen = 4;
153 }
154 if (!pc_conn_send(conn->id, header, hlen))
155 {
156 return false;
157 }
158 if (len > 0 && payload && !pc_conn_send(conn->id, payload, len))
159 {
160 return false;
161 }
162 return true;
163}
164
165bool ws_send_frame(WsConn *ws, WsOpcode opcode, const uint8_t *payload, uint16_t len)
166{
167 TcpConn *conn = &conn_pool[ws->slot_id];
168 if (!pc_conn_active(ws->slot_id))
169 {
170 return false;
171 }
172
173 uint8_t rsv1 = 0; // permessage-deflate per-message "compressed" flag (RFC 7692)
174
175#if PC_ENABLE_WS_DEFLATE
176 // Compress data frames when permessage-deflate is negotiated. Control frames
177 // (close/ping/pong) are never compressed (RFC 7692 sec 5.1). Scratch + output
178 // are borrowed from the per-dispatch arena and released when this scope exits;
179 // pc_conn_send copies (TCP_WRITE_FLAG_COPY) so the buffer can go immediately.
180 ScratchScope scope;
181 if (ws->pmd && len > 0 && (opcode == WsOpcode::WS_OP_TEXT || opcode == WsOpcode::WS_OP_BINARY))
182 {
183 size_t cap = (size_t)len + len / 8 + 16; // static-Huffman worst-case headroom
184 void *scr = scratch_alloc(DEFLATE_SCRATCH_SIZE, 16);
185 uint8_t *cbuf = (uint8_t *)scratch_alloc(cap, 1);
186 if (scr && cbuf)
187 {
188 size_t clen = 0;
189 DeflateResult rc = deflate_raw(payload, len, cbuf, cap, &clen, scr, DEFLATE_SCRATCH_SIZE);
190 // Only adopt it if it actually shrank the message; otherwise send it
191 // uncompressed (the per-message RSV1 flag makes that legal).
192 // rc != DEFLATE_OK is unreachable here: deflate_raw returns non-OK only on
193 // ERR_SCRATCH (we always pass the full DEFLATE_SCRATCH_SIZE) or ERR_OVERFLOW,
194 // and cap = len + len/8 + 16 exactly bounds the fixed-Huffman worst case
195 // (all-9-bit literals = 1.125*len, matches only shrink, +16 covers the fixed
196 // header/EOB/stored-trailer/4-byte-marker overhead). The clen < len leg is
197 // exercised both ways in test; only the rc-error leg is the dead branch below.
198 if (rc == DeflateResult::DEFLATE_OK && clen < len) // GCOVR_EXCL_BR_LINE dead rc-error leg (see above)
199 {
200 payload = cbuf;
201 len = (uint16_t)clen;
202 rsv1 = 0x40;
203 }
204 }
205 }
206#endif
207
208 // Fragment only data frames (RFC 6455 §5.4: control frames MUST NOT be fragmented, and are small
209 // anyway). frag == 0, a non-data frame, or a message that already fits -> a single FIN frame (the
210 // default, unchanged). Server-to-client frames are never masked (§5.1).
211 bool data = (opcode == WsOpcode::WS_OP_TEXT || opcode == WsOpcode::WS_OP_BINARY);
212 uint16_t frag = s_ws.frag_size;
213 if (!data || frag == 0 || len <= frag)
214 {
215 return ws_emit_one(conn, (uint8_t)(0x80 | rsv1 | (uint8_t)opcode), payload, len);
216 }
217
218 // Split into <= frag-byte frames: the opcode (+ RSV1) rides the first frame, the rest are
219 // CONTINUATION, and FIN marks the last. The compressed bytes (RFC 7692) are split as-is - the peer
220 // concatenates the fragment payloads back into one stream before inflating.
221 uint16_t off = 0;
222 bool first = true;
223 while (off < len)
224 {
225 uint16_t chunk = (uint16_t)(len - off) < frag ? (uint16_t)(len - off) : frag;
226 bool last = (uint16_t)(off + chunk) >= len;
227 uint8_t b0 = (uint8_t)((last ? 0x80 : 0x00) |
228 (first ? (rsv1 | (uint8_t)opcode) : (uint8_t)WsOpcode::WS_OP_CONTINUATION));
229 if (!ws_emit_one(conn, b0, payload + off, chunk))
230 {
231 return false;
232 }
233 off = (uint16_t)(off + chunk);
234 first = false;
235 }
236 return true;
237}
238
240{
241 // Send Close frame with 2-byte status code payload
242 uint8_t payload[2] = {(uint8_t)((uint16_t)code >> 8), (uint8_t)code};
243 ws_send_frame(ws, WsOpcode::WS_OP_CLOSE, payload, 2);
244
245 if (pc_conn_active(ws->slot_id))
246 {
248 }
249
251}
252
253// ---------------------------------------------------------------------------
254// Frame parser
255// ---------------------------------------------------------------------------
256
257// RFC 6455 §5.5: opcodes 0x8 (close), 0x9 (ping), 0xA (pong) are control frames.
258static inline bool ws_is_control(WsOpcode op)
259{
260 return ((uint8_t)op & 0x08) != 0;
261}
262
263// Called once a frame's full payload has been received (payload_idx ==
264// payload_len, also true immediately for zero-length frames once the masking
265// key is consumed). Control frames are handled in place; data frames are
266// reassembled and delivered as WsParseState::WS_FRAME_READY only when the FIN frame arrives.
267static void ws_finish_frame(WsConn *ws, TcpConn *conn)
268{
269 // ---- Control frames (ping/pong/close): use the separate ctl_buf ----
270 if (ws_is_control(ws->opcode))
271 {
272 size_t n = ws->payload_idx < sizeof(ws->ctl_buf) - 1 ? ws->payload_idx : sizeof(ws->ctl_buf) - 1;
273 ws->ctl_buf[n] = '\0';
274
275 if (ws->opcode == WsOpcode::WS_OP_PING)
276 {
277 ws_send_frame(ws, WsOpcode::WS_OP_PONG, ws->ctl_buf, (uint16_t)ws->payload_idx);
278 if (pc_conn_active(conn->id))
279 {
280 pc_conn_flush(conn->id);
281 }
282 }
283 else if (ws->opcode == WsOpcode::WS_OP_CLOSE)
284 {
286 return;
287 }
288 // PONG: silently ignored.
289
290 // Resume reading the next frame, keeping any partial data message.
291 ws_reset_perframe(ws);
292 return;
293 }
294
295 // ---- Data frames (text/binary/continuation): reassemble into buf ----
296 ws->msg_len += ws->payload_idx;
297
298 if (ws->fin)
299 {
300#if PC_ENABLE_WS_DEFLATE
301 // permessage-deflate: decompress the reassembled message before delivery.
302 // The compressed bytes are in ws->buf; append the RFC 7692 00 00 ff ff
303 // marker, INFLATE into an arena buffer, and copy the result back. All
304 // scratch is borrowed per-dispatch and released when this scope exits.
305 if (ws->msg_compressed)
306 {
307 ScratchScope scope;
308 size_t comp_len = ws->msg_len;
309 uint8_t *in = (uint8_t *)scratch_alloc(comp_len + 4, 1);
310 uint8_t *out = (uint8_t *)scratch_alloc(WS_FRAME_SIZE, 1);
311 uint8_t *tbl = (uint8_t *)scratch_alloc(INFLATE_SCRATCH_SIZE, 16);
312 if (!in || !out || !tbl)
313 {
314 ws_close(ws, WsCloseCode::WS_CLOSE_PROTOCOL); // arena exhausted: fail closed
316 return;
317 }
318 memcpy(in, ws->buf, comp_len);
319 in[comp_len] = 0x00;
320 in[comp_len + 1] = 0x00;
321 in[comp_len + 2] = 0xff;
322 in[comp_len + 3] = 0xff;
323 size_t dlen = 0;
324 InflateResult rc = inflate_raw(in, comp_len + 4, out, WS_FRAME_SIZE, &dlen, tbl, INFLATE_SCRATCH_SIZE);
325 if (rc == InflateResult::INFLATE_ERR_OVERFLOW)
326 {
329 return;
330 }
331 if (rc != InflateResult::INFLATE_OK)
332 {
335 return;
336 }
337 memcpy(ws->buf, out, dlen);
338 ws->msg_len = dlen;
339 ws->msg_compressed = false;
340 }
341#endif
342 // Whole message received - surface it to the application.
343 size_t n = ws->msg_len < WS_FRAME_SIZE ? ws->msg_len : WS_FRAME_SIZE;
344 // RFC 6455 8.1: a TEXT message MUST be valid UTF-8 (checked on the fully
345 // reassembled + decompressed message); otherwise fail the connection with 1007.
346 if (ws->msg_opcode == WsOpcode::WS_OP_TEXT && !pc_utf8_valid(ws->buf, n))
347 {
350 return;
351 }
352 ws->buf[n] = '\0';
353 ws->opcode = ws->msg_opcode; // report the original TEXT/BINARY opcode
354 ws->payload_len = ws->msg_len; // app reads payload_len / payload_idx
355 ws->payload_idx = ws->msg_len;
356 ws->fragmenting = false;
358 }
359 else
360 {
361 // More fragments to come; keep buf and msg_len, read the next frame.
362 ws->fragmenting = true;
363 ws_reset_perframe(ws);
364 }
365}
366
368{
369 if (!pc_conn_active(ws->slot_id))
370 {
371 return;
372 }
373
374 while (pc_conn_available(ws->slot_id) > 0)
375 {
376 // Stop if we hit a terminal state (leave the rest in the ring)
379 {
380 return;
381 }
382
383 uint8_t byte = 0;
384 // Unreachable by construction (not merely untested): this while-condition's
385 // pc_conn_available() and this call's pc_conn_read_byte() read the identical
386 // rx_head/rx_tail pair for this slot with no call in between that could touch either
387 // index. rx_head is advanced only by lowlevel_recv_cb() (tcp.cpp), which refuses
388 // (ERR_MEM, backpressure) any segment that would not fit in the free space already
389 // computed against rx_tail - so head can never overrun tail, meaning available() can
390 // only grow, never shrink, between the two reads. rx_tail is advanced only by this
391 // same synchronous consumer call. So available() > 0 here structurally guarantees
392 // read_byte() succeeds - true on the host AND on-device (SPSC ring, single producer /
393 // single consumer per slot); there is no interrupt-race window at this specific call
394 // site to reproduce.
395 if (!pc_conn_read_byte(ws->slot_id, &byte)) // GCOVR_EXCL_BR_LINE see above: cannot fail here by construction
396 {
397 break; // GCOVR_EXCL_LINE
398 }
399 ws_feed_byte(ws, byte);
400 }
401}
402
403void ws_feed_byte(WsConn *ws, uint8_t byte)
404{
405 TcpConn *conn = &conn_pool[ws->slot_id];
406 {
407 switch (ws->parse_state)
408 {
410 ws->fin = (byte & 0x80) != 0;
411 // RSV bits are validated below, once the opcode / message position is
412 // known (RSV1 is permessage-deflate's per-message "compressed" flag).
413 uint8_t rsv = byte & 0x70;
414 ws->opcode = static_cast<WsOpcode>(byte & 0x0F);
415 // RFC 6455 §5.2: only opcodes 0x0/0x1/0x2 (data) and 0x8/0x9/0xA
416 // (control) are defined; everything else MUST fail the connection.
417 switch (ws->opcode)
418 {
425 break;
426 default:
429 return;
430 }
431 // RFC 6455 §5.5: control frames MUST NOT be fragmented (FIN set).
432 if (ws_is_control(ws->opcode) && !ws->fin)
433 {
436 return;
437 }
438 // RFC 6455 §5.4: fragmentation sequencing for data frames.
439 if (!ws_is_control(ws->opcode))
440 {
442 {
443 // A continuation with no message in progress is illegal.
444 if (!ws->fragmenting)
445 {
448 return;
449 }
450 }
451 else
452 {
453 // A new text/binary frame while a message is still open is
454 // illegal - the previous message must finish first.
455 if (ws->fragmenting)
456 {
459 return;
460 }
461 // Start of a new data message.
462 ws->msg_opcode = ws->opcode;
463 ws->msg_len = 0;
464#if PC_ENABLE_WS_DEFLATE
465 // RSV1 on the first frame of a data message marks it compressed
466 // (RFC 7692); only honored when permessage-deflate was negotiated.
467 ws->msg_compressed = ws->pmd && (rsv & 0x40);
468#endif
469 }
470 }
471 // Validate reserved bits. RSV2/RSV3 are never legal; RSV1 is legal only
472 // as the per-message compression flag set above (pmd + new data frame).
473#if PC_ENABLE_WS_DEFLATE
474 {
475 bool new_data = !ws_is_control(ws->opcode) && ws->opcode != WsOpcode::WS_OP_CONTINUATION;
476 if ((rsv & 0x30) || ((rsv & 0x40) && !(ws->pmd && new_data)))
477 {
480 return;
481 }
482 }
483#else
484 if (rsv)
485 {
488 return;
489 }
490#endif
492 break;
493 }
494
496 ws->masked = (byte & 0x80) != 0;
497 // RFC 6455 §5.1: every client-to-server frame MUST be masked.
498 if (!ws->masked)
499 {
502 return;
503 }
504 {
505 uint8_t len7 = byte & 0x7F;
506 // RFC 6455 §5.5: control frames MUST have payload length <= 125.
507 if (ws_is_control(ws->opcode) && len7 > 125)
508 {
511 return;
512 }
513 if (len7 <= 125)
514 {
515 // Masking is mandatory, so always consume the 4 mask bytes
516 // next - even for zero-length frames (WsParseState::WS_MASK3 finishes them).
517 ws->payload_len = len7;
518 // Reassembled data message must fit in WS_FRAME_SIZE.
519 if (!ws_is_control(ws->opcode) && ws->msg_len + ws->payload_len > WS_FRAME_SIZE)
520 {
523 return;
524 }
526 }
527 else if (len7 == 126)
528 {
529 ws->payload_len = 0;
531 }
532 else
533 {
534 // 64-bit length -- always too large
535 ws->len64_count = 0;
537 }
538 }
539 break;
540
542 ws->payload_len = (uint32_t)byte << 8;
544 break;
545
547 ws->payload_len |= byte;
548 // 16-bit length only occurs on data frames (control frames are
549 // capped at 125); the reassembled message must fit WS_FRAME_SIZE.
550 if (ws->msg_len + ws->payload_len > WS_FRAME_SIZE)
551 {
554 return;
555 }
556 // Masking is mandatory; consume the 4 mask bytes next.
558 break;
559
561 // Consume all 8 bytes then reject
562 if (++ws->len64_count == 8)
563 {
566 return;
567 }
568 break;
569
571 ws->mask_key[0] = byte;
573 break;
575 ws->mask_key[1] = byte;
577 break;
579 ws->mask_key[2] = byte;
581 break;
583 ws->mask_key[3] = byte;
584 if (ws->payload_len > 0)
585 {
587 }
588 else
589 {
590 ws_finish_frame(ws, conn); // zero-length frame is complete now
591 }
592 break;
593
595 // Mask is applied per frame, so the keystream index is the
596 // within-frame position.
597 uint8_t unmasked = byte ^ ws->mask_key[ws->payload_idx % 4];
598 if (ws_is_control(ws->opcode))
599 {
600 // Control payload goes to its own buffer so it never disturbs
601 // a partially-assembled data message.
602 if (ws->payload_idx < sizeof(ws->ctl_buf) - 1)
603 {
604 ws->ctl_buf[ws->payload_idx] = unmasked;
605 }
606 }
607 else
608 {
609 // Data payload appends after any earlier fragments.
610 uint32_t pos = ws->msg_len + ws->payload_idx;
611 if (pos < WS_FRAME_SIZE)
612 {
613 ws->buf[pos] = unmasked;
614 }
615 }
616 ws->payload_idx++;
617
618 if (ws->payload_idx >= ws->payload_len)
619 {
620 ws_finish_frame(ws, conn);
621 }
622 break;
623 }
624
625 default:
626 break;
627 }
628 }
629}
#define MAX_WS_CONNS
Definition c2_defaults.h:72
RAII scope guard for transient scratch borrows.
Definition scratch.h:196
Bounded RFC 1951 DEFLATE compressor (DEFLATE) - no heap.
Bounded RFC 1951 DEFLATE decompressor (INFLATE) - no heap.
#define PC_WS_FRAG_SIZE
WebSocket outbound fragmentation size (RFC 6455 sec 5.4), in payload bytes. 0 = off.
#define WS_FRAME_SIZE
Maximum WebSocket frame payload in bytes.
void * scratch_alloc(size_t n, size_t align)
Borrow n bytes of scratch, aligned to align.
Definition scratch.cpp:135
Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
A single TCP connection context.
Definition tcp.h:72
uint8_t id
Fixed slot index (0 … MAX_CONNS-1).
Definition tcp.h:73
WebSocket connection state stored in ws_pool[].
Definition websocket.h:125
bool active
True when this entry is in use.
Definition websocket.h:128
uint8_t len64_count
Bytes consumed from 64-bit length.
Definition websocket.h:138
uint8_t mask_key[4]
Client masking key.
Definition websocket.h:135
uint32_t payload_len
Expected payload byte count (current frame).
Definition websocket.h:136
uint32_t msg_len
Bytes assembled so far across all fragments.
Definition websocket.h:147
uint8_t ws_id
Index into ws_pool[] (set at init).
Definition websocket.h:126
bool fin
FIN bit of the frame being parsed.
Definition websocket.h:132
bool masked
True if client sent a masking key.
Definition websocket.h:133
uint8_t slot_id
Owning TCP slot in conn_pool[].
Definition websocket.h:127
WsOpcode msg_opcode
Opcode of the data message being assembled.
Definition websocket.h:146
uint32_t payload_idx
Bytes received so far (current frame).
Definition websocket.h:137
WsParseState parse_state
Current frame parser state.
Definition websocket.h:130
WsOpcode opcode
Opcode of the frame being parsed.
Definition websocket.h:131
bool fragmenting
True between a non-FIN data frame and its FIN.
Definition websocket.h:145
uint8_t buf[WS_FRAME_SIZE+1]
Reassembled message payload, null-terminated.
Definition websocket.h:139
uint8_t ctl_buf[125+1]
Control-frame payload (ping/pong/close), null-terminated.
Definition websocket.h:148
uint16_t frag_size
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Definition tcp.cpp:425
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
Definition tcp.cpp:561
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
Definition tcp.cpp:500
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
Strict UTF-8 validation (RFC 3629), one shared copy.
bool pc_utf8_valid(const uint8_t *s, size_t n)
True if [s, s+n) is well-formed UTF-8.
Definition utf8.h:29
void ws_free(uint8_t slot_id)
Free the WsConn associated with a TCP slot.
Definition websocket.cpp:79
bool ws_send_frame(WsConn *ws, WsOpcode opcode, const uint8_t *payload, uint16_t len)
Send a WebSocket frame to the client.
const char * ws_payload(uint8_t ws_id)
The NUL-terminated reassembled message payload for ws_id, or nullptr if the slot is out of range / in...
Definition websocket.cpp:45
void ws_feed_byte(WsConn *ws, uint8_t byte)
Feed one already-plaintext byte through the WS frame state machine.
void ws_close(WsConn *ws, WsCloseCode code)
Send a Close frame and mark the slot WsParseState::WS_CLOSED.
void ws_parse(WsConn *ws)
Drain the ring buffer for slot_id and feed bytes to the WS parser.
WsConn * ws_find(uint8_t slot_id)
Find the WsConn for a given TCP slot, or nullptr if none.
Definition websocket.cpp:67
void ws_reset_frame(WsConn *ws)
Reset the frame parser back to WsParseState::WS_HEADER1, ready for the next frame.
void ws_set_frag_size(uint16_t bytes)
Set the outbound fragmentation size (RFC 6455 sec 5.4), in payload bytes; 0 = off.
bool ws_active(uint8_t ws_id)
True if ws_id is a valid, in-use WebSocket slot. Use this instead of reaching into ws_pool[ws_id]....
Definition websocket.cpp:40
WsConn * ws_alloc(uint8_t slot_id)
Allocate a WsConn slot and bind it to a TCP slot.
Definition websocket.cpp:50
WsConn ws_pool[MAX_WS_CONNS]
Pool of WebSocket connection state, one per MAX_WS_CONNS.
Definition websocket.cpp:29
void ws_init()
Initialize all WebSocket pool slots to inactive.
Definition websocket.cpp:31
Layer 6 (Presentation) – WebSocket frame parser and connection pool.
WsCloseCode
WebSocket close status codes (RFC 6455 §7.4.1).
Definition websocket.h:83
@ WS_CLOSE_INVALID_PAYLOAD
Text message that is not valid UTF-8 (RFC 6455 8.1).
@ WS_CLOSE_PROTOCOL
Protocol error.
@ WS_CLOSE_NORMAL
Normal closure.
@ WS_CLOSE_TOO_BIG
Payload too large for WS_FRAME_SIZE.
@ WS_MASK3
Reading masking key byte 3.
@ WS_PAYLOAD
Accumulating payload bytes.
@ WS_FRAME_READY
Complete frame ready for dispatch.
@ WS_LEN16_LO
Reading extended 16-bit length, low byte.
@ WS_ERROR
Protocol error; close frame has been queued.
@ WS_HEADER2
Awaiting second header byte (MASK, 7-bit length).
@ WS_MASK2
Reading masking key byte 2.
@ WS_LEN64
Consuming 8-byte 64-bit length (always rejected).
@ WS_LEN16_HI
Reading extended 16-bit length, high byte.
@ WS_MASK0
Reading masking key byte 0.
@ WS_CLOSED
Connection closed; slot may be recycled.
@ WS_MASK1
Reading masking key byte 1.
@ WS_HEADER1
Awaiting first header byte (FIN, RSV, opcode).
WsOpcode
WebSocket frame opcodes.
Definition websocket.h:72
@ WS_OP_CLOSE
Connection close.
@ WS_OP_PING
Ping (auto-ponged by the library).
@ WS_OP_CONTINUATION
Continuation frame (data-message fragment; reassembled into buf).
@ WS_OP_PONG
Pong (echoed ping; ignored by library).
@ WS_OP_TEXT
UTF-8 text payload.
@ WS_OP_BINARY
Binary payload.