# M4a task 09: the WebSocket connection **Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop) **Commit subject:** `gatewayd: ws conn, messages, pings, closing and a dead peer` ## Goal The connection that uses tasks 07 and 08: open it, send text, and wait for the next message while keeping the connection alive (spec section 6): - A ping from the server is answered with a pong carrying the same payload. - We send a ping every `ping_every`. No bytes at all for `dead_after` means the peer is dead. - A close frame is answered with a close frame, and the connection ends (`Closed`); so does the end of the stream. - Any protocol error ends the connection. `gatewayd` then reconnects (task 14); nothing panics. ## Files - Copy: `crates/gatewayd/tests/ws_conn.rs`, `crates/gatewayd/tests/support/ws_server.rs`, and the skeleton `crates/gatewayd/src/ws/conn.rs` - Modify: `crates/gatewayd/src/ws/mod.rs` (`pub mod conn;`), `docs/implementer-log.md` ## The skeleton ```rust pub const PATH: &str = "/api/v4/websocket"; pub struct Timing { pub ping_every: Duration, pub dead_after: Duration } // Clone, Copy pub struct Ws { stream, decoder, random, timing, last_heard, last_ping } impl Ws { pub fn open(connector: &Connector, token: &str, timing: Timing, mut random: Box) -> Result; fn send(&mut self, opcode: u8, payload: &[u8]) -> Result<(), WsError>; pub fn send_text(&mut self, text: &str) -> Result<(), WsError>; pub fn poll(&mut self, wait: Duration) -> Result, WsError>; pub fn close(mut self); } pub fn host_header(server: &ServerUrl) -> String; ``` All are `todo!()`, each with its steps above it. `poll` is the one with the most in it: it returns the next text message, or `None` after about `wait` with none, and does the pinging, the pong answers and the dead-peer check on the way. Its read timeout is always the time to the **next thing it must do** (the end of `wait`, the next ping, or the dead-after limit), so a quiet connection neither spins nor oversleeps. Write it as its comment says, step by step; a helper function for step 1 is fine. `random` gives the handshake key and a fresh 4-byte mask for every frame we send (in `gatewayd`, `/dev/urandom`). ## Steps - [ ] **1. Copy.** `git switch m4a`, then `cp docs/plans/M4a/files/crates/gatewayd/tests/ws_conn.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/tests/support/ws_server.rs crates/gatewayd/tests/support/ && cp docs/plans/M4a/files/crates/gatewayd/src/ws/conn.rs crates/gatewayd/src/ws/`. Add `pub mod conn;` to `crates/gatewayd/src/ws/mod.rs`. - [ ] **2. See it fail.** `cargo test -p gatewayd --test ws_conn`. Expected: it compiles and 10 tests fail. - [ ] **3. Fill `host_header`, `send`, `send_text`, `open`, `close`, then `poll`**, `cargo check -p gatewayd` after each. - [ ] **4. See it pass.** `cargo test -p gatewayd --test ws_conn`, five times. Expected: 10 passed each time, in under a second. - [ ] **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_conn` passes five times running; `make gate` prints `gate: ok`. ## Stop and report if - A test passes only sometimes, or takes seconds.