# M4a task 08: WebSocket frames **Branch:** `m4a` (run `git switch m4a`; `git status --short` must be empty, otherwise stop) **Commit subject:** `gatewayd: ws frames, a strict decoder and a masked encoder` ## Goal After the handshake, everything is frames (RFC 6455, section 5). This task is the codec, without I/O: a `Decoder` that is fed bytes as they arrive, in any pieces, and yields whole messages, and `encode`, which builds our frames. Everything the server sends is untrusted, so the decoder is strict and **checks every length before it allocates** (spec section 6): - A frame from the server that is masked, sets a reserved bit, uses an opcode other than continuation (0), text (1), close (8), ping (9) or pong (10), is a control frame (8 to 10) over 125 bytes or not final, is a continuation with nothing to continue, or a new text frame while a message is unfinished, is a `Protocol` error. - A length not in its shortest form (126 for a length under 126; 127 for one that fits in 16 bits), or a 64-bit length with its top bit set, is a `Protocol` error. - A message over `MAX_MESSAGE` (1 MiB), counted from the length fields of its frames **before** their payloads arrive, is `TooLarge`. - Text must be UTF-8 once whole (a character may be split across frames). - A close frame's payload is empty, or a 2-byte code and a UTF-8 reason; one byte is an error. Every frame **we** send is final, masked with the 4 bytes we are given. ## Files - Copy: `crates/gatewayd/tests/ws_frame.rs`, and the skeleton `crates/gatewayd/src/ws/frame.rs` - Modify: `crates/gatewayd/src/ws/mod.rs` (`pub mod frame;`), `docs/implementer-log.md` ## The skeleton Written: `MAX_MESSAGE`, the opcode constants, `Incoming` (`Text`, `Ping`, `Pong`, `Close(Option, String)`), `Decoder { buf, partial }` with `new` and `feed`, and the private `Header { fin, opcode, header_len, payload_len }`. Also written, because they are the glue: **`Decoder::next_message`** (take a whole frame out of `buf` and act on it) and **`Decoder::header`** (read the first two bytes, then call the checks and `length` below). Read both first: they call everything you write. To fill, **one at a time, in this order**, each with its steps above it. Each is a few lines: 1. `check_first_bytes(b0, b1)`: the reserved bits, the mask bit, the opcode. 2. `check_control(fin, payload_len)`. 3. `Decoder::length(&self, short)`: the three length forms. `Ok(None)` means "wait for more bytes". 4. `Decoder::check_data(&self, opcode, payload_len)`: the rules that depend on `partial`. 5. `Decoder::data_frame(&mut self, opcode, fin, payload)`: a text or continuation payload. 6. `close(payload)`. 7. `encode(opcode, payload, mask)`. The comment above each `todo!()` gives the constructs to use, down to the expressions for the indexing and the conversions. Use them as written: they are checked against the tests, and the indexing in them cannot go out of bounds. Do not weigh other ways to write the same thing. Write the function, run `cargo check -p gatewayd`, go on to the next. ## Steps - [ ] **1. Copy.** `git switch m4a`, then `cp docs/plans/M4a/files/crates/gatewayd/tests/ws_frame.rs crates/gatewayd/tests/ && cp docs/plans/M4a/files/crates/gatewayd/src/ws/frame.rs crates/gatewayd/src/ws/`. Add `pub mod frame;` to `crates/gatewayd/src/ws/mod.rs`. - [ ] **2. See it fail.** `cargo test -p gatewayd --test ws_frame`. Expected: it compiles and 7 tests fail. - [ ] **3. Fill the seven functions** in the order above, `cargo check -p gatewayd` after each. - [ ] **4. See it pass.** `cargo test -p gatewayd --test ws_frame`. Expected: 7 passed. The last test feeds 300 random streams, half of them with bits flipped, in random pieces, and compares your decoder with a simple one written inside the test: they must return the same messages, and both fail or both not. - [ ] **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_frame` passes; `make gate` prints `gate: ok`. ## Stop and report if - The random-stream test fails on a case you believe the naive decoder gets wrong. Report the seed and the bytes; do not change the test.