Goal#
Implement the wire-level WebSocket protocol (RFC 6455) in we-net, including the opening HTTP Upgrade handshake, frame parse/serialize, masking, fragmentation, close handshake, and the permessage-deflate extension. This is the transport layer that the JS API will sit on top of in the following issue.
Scope#
- Opening handshake (client side)
- Construct an HTTP/1.1 request with
Upgrade: websocket,Connection: Upgrade,Sec-WebSocket-Version: 13,Sec-WebSocket-Key(16 random bytes, base64-encoded), optionalSec-WebSocket-Protocol,Sec-WebSocket-Extensions - Parse the server's 101 Switching Protocols response and verify
Sec-WebSocket-Accept = base64(SHA-1(key + \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\")) - Reuse the Phase 6 TCP/TLS stack —
ws://over TCP,wss://over TLS 1.3 - Add SHA-1 to
we-crypto(FIPS 180-4) since RFC 6455 mandates it; constant-time not required (the value is public). Document that SHA-1 is used only for the WebSocket handshake.
- Construct an HTTP/1.1 request with
- Frame layer
- Parse and serialize frames per RFC 6455 §5.2: FIN, RSV1/2/3, opcode, MASK, payload length (7 / 7+16 / 7+64), masking key, payload
- Opcodes: continuation (0x0), text (0x1), binary (0x2), close (0x8), ping (0x9), pong (0xA)
- Client-to-server frames are always masked with a random 32-bit key
- Fragmentation: reassemble continuation frames into a complete message; reject interleaved control frames within a fragmented data frame? — control frames are allowed between data fragments, must not be fragmented themselves
- Validate text frames as UTF-8 (per RFC 6455 §8.1) — invalid UTF-8 closes the connection with status 1007
- Maximum frame and message size limits (configurable; default 16 MiB)
- Close handshake
- 2-byte status code + optional UTF-8 reason
- Defined codes: 1000, 1001, 1002, 1003, 1007, 1008, 1009, 1010, 1011, plus 3000–4999 application range
- Echo close frame back; close TCP after both directions have sent close (or after timeout)
permessage-deflateextension (RFC 7692)- Negotiate via
Sec-WebSocket-Extensions: permessage-deflate - Per-message DEFLATE compression with sliding window context (
client_max_window_bits,server_max_window_bits,client_no_context_takeover,server_no_context_takeover) - Reuse the streaming DEFLATE implementation from the transform-streams issue (or share code)
- Negotiate via
- Public Rust API in
we-net- Async-ish (
std::sync::mpscor callback-driven) interface usable from JS - Sender half:
send_text,send_binary,send_ping,close(code, reason) - Receiver half: surface inbound messages, pings, pongs, and close frames
- Async-ish (
Acceptance Criteria#
- Open and close handshakes interoperate with a standard server (e.g.
ws://echo.websocket.eventsoncewssis verified separately, or a small embedded test server using the same implementation) - Text and binary messages round-trip including fragmented and large (>64 KiB) payloads
- Server-sent pings are answered with matching pongs automatically
- Invalid UTF-8 on a text frame triggers a close with status 1007
permessage-deflateround-trips messages and respects context-takeover parameters- A
wss://connection (TLS 1.3) round-trips messages - Unit tests: handshake validation (good and bad
Sec-WebSocket-Accept), frame parse/serialize for each opcode and length encoding, masking round-trip, fragmentation reassembly, close handshake symmetry, permessage-deflate with and without context takeover - Conventions
cargo fmt --all --checkcargo clippy --workspace -- -D warningscargo test --workspace- SHA-1 implementation lives in
we-cryptoas a standalone module; no external dependencies