23#if PC_ENABLE_WS_DEFLATE
95static void ws_reset_perframe(
WsConn *ws)
109 ws_reset_perframe(ws);
137static bool ws_emit_one(
TcpConn *conn, uint8_t b0,
const uint8_t *payload, uint16_t len)
144 header[1] = (uint8_t)len;
150 header[2] = (uint8_t)(len >> 8);
151 header[3] = (uint8_t)len;
168 if (!pc_conn_active(ws->
slot_id))
175#if PC_ENABLE_WS_DEFLATE
183 size_t cap = (size_t)len + len / 8 + 16;
189 DeflateResult rc = deflate_raw(payload, len, cbuf, cap, &clen, scr, DEFLATE_SCRATCH_SIZE);
198 if (rc == DeflateResult::DEFLATE_OK && clen < len)
201 len = (uint16_t)clen;
213 if (!data || frag == 0 || len <= frag)
215 return ws_emit_one(conn, (uint8_t)(0x80 | rsv1 | (uint8_t)opcode), payload, len);
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) |
229 if (!ws_emit_one(conn, b0, payload + off, chunk))
233 off = (uint16_t)(off + chunk);
242 uint8_t payload[2] = {(uint8_t)((uint16_t)code >> 8), (uint8_t)code};
245 if (pc_conn_active(ws->
slot_id))
258static inline bool ws_is_control(
WsOpcode op)
260 return ((uint8_t)op & 0x08) != 0;
270 if (ws_is_control(ws->
opcode))
278 if (pc_conn_active(conn->
id))
291 ws_reset_perframe(ws);
300#if PC_ENABLE_WS_DEFLATE
305 if (ws->msg_compressed)
311 uint8_t *tbl = (uint8_t *)
scratch_alloc(INFLATE_SCRATCH_SIZE, 16);
312 if (!in || !out || !tbl)
318 memcpy(in, ws->
buf, comp_len);
320 in[comp_len + 1] = 0x00;
321 in[comp_len + 2] = 0xff;
322 in[comp_len + 3] = 0xff;
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)
331 if (rc != InflateResult::INFLATE_OK)
337 memcpy(ws->
buf, out, dlen);
339 ws->msg_compressed =
false;
363 ws_reset_perframe(ws);
369 if (!pc_conn_active(ws->
slot_id))
374 while (pc_conn_available(ws->
slot_id) > 0)
395 if (!pc_conn_read_byte(ws->
slot_id, &
byte))
410 ws->
fin = (
byte & 0x80) != 0;
413 uint8_t rsv =
byte & 0x70;
432 if (ws_is_control(ws->
opcode) && !ws->
fin)
439 if (!ws_is_control(ws->
opcode))
464#if PC_ENABLE_WS_DEFLATE
467 ws->msg_compressed = ws->pmd && (rsv & 0x40);
473#if PC_ENABLE_WS_DEFLATE
476 if ((rsv & 0x30) || ((rsv & 0x40) && !(ws->pmd && new_data)))
496 ws->
masked = (
byte & 0x80) != 0;
505 uint8_t len7 =
byte & 0x7F;
507 if (ws_is_control(ws->
opcode) && len7 > 125)
527 else if (len7 == 126)
590 ws_finish_frame(ws, conn);
598 if (ws_is_control(ws->
opcode))
613 ws->
buf[pos] = unmasked;
620 ws_finish_frame(ws, conn);
RAII scope guard for transient scratch borrows.
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.
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.
TcpConn conn_pool[CONN_POOL_SLOTS]
Static pool of connection contexts. Defined in tcp.cpp. Sized CONN_POOL_SLOTS: MAX_CONNS TCP slots pl...
void pc_conn_flush(uint8_t slot)
Flush queued bytes / finish the send on slot (TLS-aware).
bool pc_conn_send(uint8_t slot, const void *data, u16_t len)
Send len bytes on connection slot (copies data; TLS-aware).
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.
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.