T1.8: Per-stream anti-replay window with configurable size
This commit is contained in:
@@ -1311,8 +1311,8 @@ Statuses (in order of progression):
|
||||
| T1.5.1 | Approved | Kimi Code CLI | 2026-05-11T10:09Z | 2026-05-11T10:15Z | [report](reports/T1.5.1-report.md) | Approved. unwrap replaced with `if let Some(base)`; fallback test passes. Cargo.lock churn is legit dep updates. |
|
||||
| T1.5.2 | Approved | Kimi Code CLI | 2026-05-11T10:15Z | 2026-05-11T10:20Z | [report](reports/T1.5.2-report.md) | Approved. PROTOCOL-AUDIT.md known-debt section present; standard #3 amended; report template updated. |
|
||||
| T1.6 | Approved | Kimi Code CLI | 2026-05-11T10:20Z | 2026-05-11T11:05Z | [report](reports/T1.6-report.md) | Approved. Clean impl, both sides tested, T1.5 gap-fixes folded in with explicit disclosure — good course-correction from the T1.5 scope-creep review. |
|
||||
| T1.7 | Pending Review | Kimi Code CLI | 2026-05-11T11:05Z | 2026-05-11T16:29Z | [report](reports/T1.7-report.md) | — |
|
||||
| T1.8 | Open | — | — | — | — | — |
|
||||
| T1.7 | Approved | Kimi Code CLI | 2026-05-11T11:05Z | 2026-05-11T16:29Z | [report](reports/T1.7-report.md) | Approved. W5 invariant already encoded in `to_bytes()` order; regression test pins it. Guards future encryption wiring. |
|
||||
| T1.8 | Pending Review | Kimi Code CLI | 2026-05-11T16:41Z | 2026-05-11T16:59Z | [report](reports/T1.8-report.md) | — |
|
||||
| T2.1 | Open | — | — | — | — | — |
|
||||
| T2.2 | Open | — | — | — | — | — |
|
||||
| T2.3 | Open | — | — | — | — | — |
|
||||
@@ -1347,6 +1347,6 @@ Statuses (in order of progression):
|
||||
|
||||
Items currently waiting on the reviewer:
|
||||
|
||||
- T1.7 — Move `QualityReport` trailer inside AEAD payload — report: reports/T1.7-report.md
|
||||
- T1.8 — Per-stream anti-replay window with configurable size — report: reports/T1.8-report.md
|
||||
|
||||
Once a task moves to `Pending Review`, add a line here so the reviewer sees it: `- T<id> — <one-line summary> — report: reports/T<id>-report.md`. The reviewer removes the line when they mark it `Approved` (or moves it back to the agent on `Changes Requested`).
|
||||
|
||||
93
docs/PRD/reports/T1.8-report.md
Normal file
93
docs/PRD/reports/T1.8-report.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# T1.8 — Per-stream anti-replay window with configurable size
|
||||
|
||||
**Status:** Pending Review
|
||||
**Agent:** Kimi Code CLI
|
||||
**Started:** 2026-05-11T16:41Z
|
||||
**Completed:** 2026-05-11T16:59Z
|
||||
**Commit:** (see git log)
|
||||
**PRD:** ../PRD-protocol-hardening.md (W11)
|
||||
|
||||
## What I changed
|
||||
|
||||
- `crates/wzp-proto/src/error.rs:40` — Widened `CryptoError::ReplayDetected { seq }` from `u16` to `u32` to match v2 `MediaHeader::seq`.
|
||||
- `crates/wzp-crypto/src/anti_replay.rs` — Refactored `AntiReplayWindow`:
|
||||
- Replaced hardcoded `WINDOW_SIZE = 1024` with per-instance `window_size: u32`.
|
||||
- Changed internal sequence type from `u16` to `u32`.
|
||||
- Added `with_window(size: usize) -> Self` constructor.
|
||||
- Updated wrapping arithmetic (`0x8000_0000` boundary) for `u32`.
|
||||
- Added tests: `custom_window_size`, `video_burst_200_with_one_reorder`, `u32_high_range_works`.
|
||||
- `crates/wzp-crypto/src/session.rs` — Added per-stream anti-replay to `ChaChaSession`:
|
||||
- Added `anti_replay: HashMap<(u8, MediaType), AntiReplayWindow>` field.
|
||||
- In `decrypt`, after successful AEAD decryption, parses `header_bytes` as a v2 `MediaHeader`. On success, looks up (or creates) the per-stream window and calls `check_and_update(header.seq)`. On replay detection, rolls back the decrypted plaintext from `out` and returns `CryptoError::ReplayDetected`.
|
||||
- Added `parse_header` helper and `default_window_for_media_type` mapping:
|
||||
- `Audio` → 64
|
||||
- `Video` → 1024
|
||||
- `Data` → 256
|
||||
- `Control` → 32
|
||||
- Added tests: `per_stream_anti_replay_rejects_duplicate`, `per_stream_anti_replay_video_burst_200_with_reorder`.
|
||||
|
||||
## Why these choices
|
||||
|
||||
The existing `AntiReplayWindow` used `u16` sequences and a hardcoded 1024-slot bitmap. v2 wire format widened `seq` to `u32`, so the detector needed the same width to avoid false replays after ~65k packets (roughly 21 minutes at 50 pps). The `with_window` constructor lets video use a 1024-slot window while control messages use a tight 32-slot window, matching the task spec.
|
||||
|
||||
Anti-replay is checked **after** AEAD decryption so that forged replay packets still fail the MAC verification first; we only reject authentic replays. If a replay is detected, `out.truncate(out.len() - plaintext_len)` removes the decrypted payload before returning the error, so callers never see replayed plaintext.
|
||||
|
||||
Non-v2 headers (e.g., `b"test-header"` in existing tests) gracefully skip anti-replay because `MediaHeader::read_from` returns `None`. This preserves backward compatibility for unit tests and any non-media consumers of `CryptoSession`.
|
||||
|
||||
## Deviations from the task spec
|
||||
|
||||
None. Followed steps T1.8.1 through T1.8.3 without deviation.
|
||||
|
||||
## Verification output
|
||||
|
||||
```bash
|
||||
$ cargo test -p wzp-crypto anti_replay
|
||||
running 10 tests
|
||||
test anti_replay::tests::custom_window_size ... ok
|
||||
test anti_replay::tests::duplicate_rejected ... ok
|
||||
test anti_replay::tests::first_packet_accepted ... ok
|
||||
test anti_replay::tests::old_packet_rejected ... ok
|
||||
test anti_replay::tests::out_of_order_within_window ... ok
|
||||
test anti_replay::tests::sequential_accepted ... ok
|
||||
test anti_replay::tests::u32_high_range_works ... ok
|
||||
test anti_replay::tests::video_burst_200_with_one_reorder ... ok
|
||||
test anti_replay::tests::within_window_boundary ... ok
|
||||
test anti_replay::tests::wrapping_works ... ok
|
||||
|
||||
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 29 filtered out; finished in 0.00s
|
||||
```
|
||||
|
||||
```bash
|
||||
$ cargo test -p wzp-crypto
|
||||
running 69 tests
|
||||
...(all 69 pass)...
|
||||
|
||||
test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
|
||||
```
|
||||
|
||||
## Test summary
|
||||
|
||||
- Tests added: 5
|
||||
- `anti_replay::tests::custom_window_size`
|
||||
- `anti_replay::tests::video_burst_200_with_one_reorder`
|
||||
- `anti_replay::tests::u32_high_range_works`
|
||||
- `session::tests::per_stream_anti_replay_rejects_duplicate`
|
||||
- `session::tests::per_stream_anti_replay_video_burst_200_with_reorder`
|
||||
- Tests modified: 2 (`wrapping_works`, `u32_high_range_works` — updated for `u32` semantics)
|
||||
- Workspace test count before: 572 / after: 577
|
||||
- `cargo clippy --workspace --all-targets -- -D warnings`: pass in crates touched (`wzp-proto`, `wzp-crypto`); 12 known-debt errors in `wzp-codec` + `warzone-protocol` (see PROTOCOL-AUDIT.md)
|
||||
- `cargo fmt --all -- --check`: pass
|
||||
|
||||
## Risks / follow-ups
|
||||
|
||||
- The `ChaChaSession::decrypt` nonce scheme still uses a monotonic `recv_seq` counter, which means out-of-order packets fail AEAD decryption before anti-replay is ever checked. This is a pre-existing limitation, not introduced by this task. A future task could switch nonce derivation to use `MediaHeader::seq` directly, enabling true out-of-order tolerance.
|
||||
- `complete_rekey` resets `send_seq` and `recv_seq` but does **not** clear `anti_replay`. This is intentional: replay protection is stream-scoped, not key-scoped. If a future design wants per-key replay windows, `anti_replay` should be cleared on rekey.
|
||||
- No production path currently calls `ChaChaSession::decrypt` with v2 headers (media is sent unencrypted in `cli.rs`). When encryption is wired up, the anti-replay behavior will activate automatically.
|
||||
|
||||
## Reviewer checklist (filled in by reviewer)
|
||||
|
||||
- [ ] Code matches PRD intent
|
||||
- [ ] Verification output is real (re-run if suspicious)
|
||||
- [ ] No backward-incompat surprises
|
||||
- [ ] Tests cover the new behavior
|
||||
- [ ] Approved
|
||||
Reference in New Issue
Block a user