5.2 KiB
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— WidenedCryptoError::ReplayDetected { seq }fromu16tou32to match v2MediaHeader::seq.crates/wzp-crypto/src/anti_replay.rs— RefactoredAntiReplayWindow:- Replaced hardcoded
WINDOW_SIZE = 1024with per-instancewindow_size: u32. - Changed internal sequence type from
u16tou32. - Added
with_window(size: usize) -> Selfconstructor. - Updated wrapping arithmetic (
0x8000_0000boundary) foru32. - Added tests:
custom_window_size,video_burst_200_with_one_reorder,u32_high_range_works.
- Replaced hardcoded
crates/wzp-crypto/src/session.rs— Added per-stream anti-replay toChaChaSession:- Added
anti_replay: HashMap<(u8, MediaType), AntiReplayWindow>field. - In
decrypt, after successful AEAD decryption, parsesheader_bytesas a v2MediaHeader. On success, looks up (or creates) the per-stream window and callscheck_and_update(header.seq). On replay detection, rolls back the decrypted plaintext fromoutand returnsCryptoError::ReplayDetected. - Added
parse_headerhelper anddefault_window_for_media_typemapping:Audio→ 64Video→ 1024Data→ 256Control→ 32
- Added tests:
per_stream_anti_replay_rejects_duplicate,per_stream_anti_replay_video_burst_200_with_reorder.
- Added
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
$ 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
$ 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_sizeanti_replay::tests::video_burst_200_with_one_reorderanti_replay::tests::u32_high_range_workssession::tests::per_stream_anti_replay_rejects_duplicatesession::tests::per_stream_anti_replay_video_burst_200_with_reorder
- Tests modified: 2 (
wrapping_works,u32_high_range_works— updated foru32semantics) - 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 inwzp-codec+warzone-protocol(see PROTOCOL-AUDIT.md)cargo fmt --all -- --check: pass
Risks / follow-ups
- The
ChaChaSession::decryptnonce scheme still uses a monotonicrecv_seqcounter, 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 useMediaHeader::seqdirectly, enabling true out-of-order tolerance. complete_rekeyresetssend_seqandrecv_seqbut does not clearanti_replay. This is intentional: replay protection is stream-scoped, not key-scoped. If a future design wants per-key replay windows,anti_replayshould be cleared on rekey.- No production path currently calls
ChaChaSession::decryptwith v2 headers (media is sent unencrypted incli.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