Files
boxmaker/docs/plans/M4a/07-gatewayd-ws-handshake.md
T
kyleandClaude Opus 5.5 0339dc13b2 Plan M4a: gatewayd in 15 tasks, with skeletons and given tests
Each task's tests were run against a reference at its end state; the end states were replayed
from master in order with the gate at each step (650 to 762 tests); each skeleton compiles
against its tests and fails them. The reference is kept off this machine. Lessons T27 (every
wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:05:44 -07:00

3.1 KiB

M4a task 07: the WebSocket handshake

Branch: m4a (run git switch m4a; git status --short must be empty, otherwise stop) Commit subject: gatewayd: ws, the WebSocket error type and the opening handshake

Goal

Mattermost pushes events over a WebSocket at /api/v4/websocket (RFC 6455). Our client is our own: blocking, one connection, tested against hostile input. This task is its first part: the error type, base64, and the opening handshake (RFC 6455, section 4.1; spec section 6).

The request, exactly (it is written for you in request_text):

GET <path> HTTP/1.1\r\n
Host: <host>\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Key: <16 random bytes in base64>\r\n
Sec-WebSocket-Version: 13\r\n
Authorization: Bearer <token>\r\n
\r\n

The answer must be status 101, with Upgrade: websocket, a Connection header holding the token upgrade, and Sec-WebSocket-Accept equal to base64(SHA-1(key + the GUID 258EAFA5-E914-47DA-95CA-C5AB0DC85B11)). Anything else is an error. SHA-1 is proto::sha1::sha1 (task 01); the head is read with http::read_head (task 06), which stops at the blank line.

Files

  • Copy: crates/gatewayd/tests/ws_handshake.rs, and crates/gatewayd/src/ws/mod.rs (complete) and the skeleton crates/gatewayd/src/ws/handshake.rs
  • Modify: crates/gatewayd/src/lib.rs (pub mod ws;), docs/implementer-log.md

ws/mod.rs is complete: WsError (Handshake, Protocol, TooLarge, Closed, Dead, Io) with Display and From<std::io::Error>, and pub mod handshake;. Tasks 08 and 09 add their pub mod lines to it.

The skeleton

Written: GUID, ALPHABET, request_text. To fill: base64, accept_for, new_key, check_response, handshake. The key's 16 bytes come from random: &mut dyn Read (in gatewayd, /dev/urandom; in the tests, fixed bytes), so the tests know the key.

Steps

  • 1. Copy. git switch m4a, then mkdir -p crates/gatewayd/src/ws && cp docs/plans/M4a/files/crates/gatewayd/src/ws/mod.rs docs/plans/M4a/files/crates/gatewayd/src/ws/handshake.rs crates/gatewayd/src/ws/ && cp docs/plans/M4a/files/crates/gatewayd/tests/ws_handshake.rs crates/gatewayd/tests/. Add pub mod ws; to lib.rs.
  • 2. See it fail. cargo test -p gatewayd --test ws_handshake. Expected: it compiles; 6 fail and 1 passes (the_request_is_exactly_this, since request_text is given).
  • 3. Fill base64, accept_for, new_key, check_response, handshake, in that order, cargo check -p gatewayd after each.
  • 4. See it pass. cargo test -p gatewayd --test ws_handshake. Expected: 7 passed, including the RFC 6455 example (dGhlIHNhbXBsZSBub25jZQ== gives s3pPLMBiTxaQ9kYGzzhZRbK+xOo=) and a handshake whose first frame arrives in the same packet as the head and is left unread.
  • 5. Run the gate. cargo fmt --all, then make gate. Expected last line: gate: ok.
  • 6. Log and commit. git add crates/gatewayd docs/implementer-log.md Cargo.lock && git commit

Done when

  • cargo test -p gatewayd --test ws_handshake passes; make gate prints gate: ok.