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>
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, andcrates/gatewayd/src/ws/mod.rs(complete) and the skeletoncrates/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, thenmkdir -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/. Addpub mod ws;tolib.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, sincerequest_textis given). - 3. Fill
base64,accept_for,new_key,check_response,handshake, in that order,cargo check -p gatewaydafter each. - 4. See it pass.
cargo test -p gatewayd --test ws_handshake. Expected: 7 passed, including the RFC 6455 example (dGhlIHNhbXBsZSBub25jZQ==givess3pPLMBiTxaQ9kYGzzhZRbK+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, thenmake 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_handshakepasses;make gateprintsgate: ok.