23#if DETWS_ENABLE_WS_DEFLATE
93static void ws_reset_perframe(
WsConn *ws)
107 ws_reset_perframe(ws);
135static bool ws_emit_one(
TcpConn *conn, uint8_t b0,
const uint8_t *payload, uint16_t len)
142 header[1] = (uint8_t)len;
148 header[2] = (uint8_t)(len >> 8);
149 header[3] = (uint8_t)len;
162 if (!det_conn_active(ws->
slot_id))
167#if DETWS_ENABLE_WS_DEFLATE
175 size_t cap = (size_t)len + len / 8 + 16;
181 DeflateResult rc = deflate_raw(payload, len, cbuf, cap, &clen, scr, DEFLATE_SCRATCH_SIZE);
184 if (rc == DeflateResult::DEFLATE_OK && clen < len)
187 len = (uint16_t)clen;
199 if (!data || frag == 0 || len <= frag)
200 return ws_emit_one(conn, (uint8_t)(0x80 | rsv1 | (uint8_t)opcode), payload, len);
209 uint16_t chunk = (uint16_t)(len - off) < frag ? (uint16_t)(len - off) : frag;
210 bool last = (uint16_t)(off + chunk) >= len;
211 uint8_t b0 = (uint8_t)((last ? 0x80 : 0x00) |
213 if (!ws_emit_one(conn, b0, payload + off, chunk))
215 off = (uint16_t)(off + chunk);
224 uint8_t payload[2] = {(uint8_t)((uint16_t)code >> 8), (uint8_t)code};
227 if (det_conn_active(ws->
slot_id))
238static inline bool ws_is_control(
WsOpcode op)
240 return ((uint8_t)op & 0x08) != 0;
250 if (ws_is_control(ws->
opcode))
258 if (det_conn_active(conn->
id))
269 ws_reset_perframe(ws);
278#if DETWS_ENABLE_WS_DEFLATE
283 if (ws->msg_compressed)
289 uint8_t *tbl = (uint8_t *)
scratch_alloc(INFLATE_SCRATCH_SIZE, 16);
290 if (!in || !out || !tbl)
296 memcpy(in, ws->
buf, comp_len);
298 in[comp_len + 1] = 0x00;
299 in[comp_len + 2] = 0xff;
300 in[comp_len + 3] = 0xff;
302 InflateResult rc = inflate_raw(in, comp_len + 4, out,
WS_FRAME_SIZE, &dlen, tbl, INFLATE_SCRATCH_SIZE);
303 if (rc == InflateResult::INFLATE_ERR_OVERFLOW)
309 if (rc != InflateResult::INFLATE_OK)
315 memcpy(ws->
buf, out, dlen);
317 ws->msg_compressed =
false;
341 ws_reset_perframe(ws);
347 if (!det_conn_active(ws->
slot_id))
350 while (det_conn_available(ws->
slot_id) > 0)
358 if (!det_conn_read_byte(ws->
slot_id, &
byte))
371 ws->
fin = (
byte & 0x80) != 0;
374 uint8_t rsv =
byte & 0x70;
393 if (ws_is_control(ws->
opcode) && !ws->
fin)
400 if (!ws_is_control(ws->
opcode))
425#if DETWS_ENABLE_WS_DEFLATE
428 ws->msg_compressed = ws->pmd && (rsv & 0x40);
434#if DETWS_ENABLE_WS_DEFLATE
437 if ((rsv & 0x30) || ((rsv & 0x40) && !(ws->pmd && new_data)))
457 ws->
masked = (
byte & 0x80) != 0;
466 uint8_t len7 =
byte & 0x7F;
468 if (ws_is_control(ws->
opcode) && len7 > 125)
488 else if (len7 == 126)
548 ws_finish_frame(ws, conn);
555 if (ws_is_control(ws->
opcode))
567 ws->
buf[pos] = unmasked;
572 ws_finish_frame(ws, conn);
#define DETWS_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.
#define MAX_WS_CONNS
Maximum simultaneous WebSocket connections.
RAII scope guard for transient scratch borrows.
Bounded RFC 1951 DEFLATE compressor (DEFLATE) - no heap.
Bounded RFC 1951 DEFLATE decompressor (INFLATE) - no heap.
void * scratch_alloc(size_t n, size_t align)
Borrow n bytes of scratch, aligned to align.
Shared per-dispatch scratch arena (Layer 5, session-scoped memory).
A single TCP connection context.
uint8_t id
Fixed slot index (0 … MAX_CONNS-1).
WebSocket connection state stored in ws_pool[].
bool active
True when this entry is in use.
uint8_t len64_count
Bytes consumed from 64-bit length.
uint8_t mask_key[4]
Client masking key.
uint32_t payload_len
Expected payload byte count (current frame).
uint32_t msg_len
Bytes assembled so far across all fragments.
uint8_t ws_id
Index into ws_pool[] (set at init).
bool fin
FIN bit of the frame being parsed.
bool masked
True if client sent a masking key.
uint8_t slot_id
Owning TCP slot in conn_pool[].
WsOpcode msg_opcode
Opcode of the data message being assembled.
uint32_t payload_idx
Bytes received so far (current frame).
WsParseState parse_state
Current frame parser state.
WsOpcode opcode
Opcode of the frame being parsed.
bool fragmenting
True between a non-FIN data frame and its FIN.
uint8_t buf[WS_FRAME_SIZE+1]
Reassembled message payload, null-terminated.
uint8_t ctl_buf[125+1]
Control-frame payload (ping/pong/close), null-terminated.
bool det_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
void det_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
Layer 4 (Transport) - TCP connection pool, ring buffers, and lwIP integration.
Strict UTF-8 validation (RFC 3629), one shared copy.
bool det_utf8_valid(const uint8_t *s, size_t n)
True if [s, s+n) is well-formed UTF-8.
void ws_free(uint8_t slot_id)
Free the WsConn associated with a TCP slot.
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...
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.
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]....
WsConn * ws_alloc(uint8_t slot_id)
Allocate a WsConn slot and bind it to a TCP slot.
WsConn ws_pool[MAX_WS_CONNS]
Pool of WebSocket connection state, one per MAX_WS_CONNS.
void ws_init()
Initialize all WebSocket pool slots to inactive.
Layer 6 (Presentation) – WebSocket frame parser and connection pool.
WsCloseCode
WebSocket close status codes (RFC 6455 §7.4.1).
@ 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.
@ 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.