M4a tasks 08 and 09: finer skeletons after task 08's session wrote nothing
Task 08's `header` was one todo!() with a dozen branches; Ornith planned it in its head until the turn ran out (tip T25). `next_message` and `header` are now written as glue over seven small helpers, and task 09's `poll` over four, each checked to pass when filled from its comments. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,19 +34,24 @@ Written: `MAX_MESSAGE`, the opcode constants, `Incoming` (`Text`, `Ping`, `Pong`
|
||||
`Close(Option<u16>, String)`), `Decoder { buf, partial }` with `new` and `feed`, and the private
|
||||
`Header { fin, opcode, header_len, payload_len }`.
|
||||
|
||||
To fill, in this order, each with its steps above it:
|
||||
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.
|
||||
|
||||
1. `Decoder::header(&self) -> Result<Option<Header>, WsError>`: the next frame's header once all of
|
||||
it is in `buf`, checked against every rule above that does not need the payload. `Ok(None)`
|
||||
means "wait for more bytes". It only reads `buf`; it removes nothing.
|
||||
2. `close(payload) -> Result<Incoming, WsError>`.
|
||||
3. `Decoder::next_message(&mut self)`: uses `header`, and when the whole frame is in `buf`, takes it
|
||||
out (`drain`) and acts on it. Control frames may come between the frames of a text message and
|
||||
are returned at once.
|
||||
4. `encode(opcode, payload, mask) -> Vec<u8>`.
|
||||
To fill, **one at a time, in this order**, each with its steps above it. Each is a few lines:
|
||||
|
||||
No indexing that can go out of bounds, no `as` casts: read lengths with `get(2..4)` and
|
||||
`u16::from_be_bytes`, and convert with `usize::try_from` / `u8::try_from`.
|
||||
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
|
||||
|
||||
@@ -55,7 +60,7 @@ No indexing that can go out of bounds, no `as` casts: read lengths with `get(2..
|
||||
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 four functions**, `cargo check -p gatewayd` after each.
|
||||
- [ ] **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
|
||||
|
||||
Reference in New Issue
Block a user