From e8866c66327c042eb46753a8cde00df597be8af0 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Mon, 11 May 2026 11:17:42 +0400 Subject: [PATCH] T1.4: Add v2 MiniHeader with seq_delta --- crates/wzp-proto/src/lib.rs | 5 +- crates/wzp-proto/src/packet.rs | 137 ++++++++++++++++++++++++++++++-- docs/PRD/TASKS.md | 50 +++++++++++- docs/PRD/reports/T1.4-report.md | 92 +++++++++++++++++++++ 4 files changed, 273 insertions(+), 11 deletions(-) create mode 100644 docs/PRD/reports/T1.4-report.md diff --git a/crates/wzp-proto/src/lib.rs b/crates/wzp-proto/src/lib.rs index 62c6bd9..af4758d 100644 --- a/crates/wzp-proto/src/lib.rs +++ b/crates/wzp-proto/src/lib.rs @@ -31,8 +31,9 @@ pub use error::*; pub use media_type::MediaType; pub use packet::{ CallAcceptMode, FRAME_TYPE_FULL, FRAME_TYPE_MINI, HangupReason, MediaHeader, MediaHeaderV1, - MediaHeaderV2, MediaPacket, MiniFrameContext, MiniHeader, PresenceUser, QualityReport, - RoomParticipant, SignalMessage, TrunkEntry, TrunkFrame, + MediaHeaderV2, MediaPacket, MiniFrameContext, MiniFrameContextV1, MiniFrameContextV2, + MiniHeader, MiniHeaderV1, MiniHeaderV2, PresenceUser, QualityReport, RoomParticipant, + SignalMessage, TrunkEntry, TrunkFrame, }; pub use quality::{AdaptiveQualityController, NetworkContext, Tier}; pub use session::{Session, SessionEvent, SessionState}; diff --git a/crates/wzp-proto/src/packet.rs b/crates/wzp-proto/src/packet.rs index 0836352..c0e3836 100644 --- a/crates/wzp-proto/src/packet.rs +++ b/crates/wzp-proto/src/packet.rs @@ -577,18 +577,18 @@ pub const FRAME_TYPE_FULL: u8 = 0x00; /// Frame type tag: MiniHeader follows (requires prior baseline). pub const FRAME_TYPE_MINI: u8 = 0x01; -/// Compact 4-byte header used after a full MediaHeader baseline has been +/// Compact 4-byte v1 header used after a full MediaHeader baseline has been /// established. Only the timestamp delta and payload length are transmitted; /// all other fields are inherited from the last full header. #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct MiniHeader { +pub struct MiniHeaderV1 { /// Milliseconds elapsed since the last header's timestamp. pub timestamp_delta_ms: u16, /// Length of the payload that follows this header. pub payload_len: u16, } -impl MiniHeader { +impl MiniHeaderV1 { /// Header size in bytes on the wire. pub const WIRE_SIZE: usize = 4; @@ -610,14 +610,47 @@ impl MiniHeader { } } -/// Stateful context that expands [`MiniHeader`]s back into full +/// Temporary alias so existing code continues to compile. +/// Removed in T1.5 once all emit/parse sites migrate to v2. +pub type MiniHeader = MiniHeaderV1; + +/// Compact 5-byte v2 mini header with explicit `seq_delta`. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct MiniHeaderV2 { + pub seq_delta: u8, + pub timestamp_delta_ms: u16, + pub payload_len: u16, +} + +impl MiniHeaderV2 { + pub const WIRE_SIZE: usize = 5; + + pub fn write_to(&self, buf: &mut impl BufMut) { + buf.put_u8(self.seq_delta); + buf.put_u16(self.timestamp_delta_ms); + buf.put_u16(self.payload_len); + } + + pub fn read_from(buf: &mut impl Buf) -> Option { + if buf.remaining() < Self::WIRE_SIZE { + return None; + } + Some(Self { + seq_delta: buf.get_u8(), + timestamp_delta_ms: buf.get_u16(), + payload_len: buf.get_u16(), + }) + } +} + +/// Stateful v1 context that expands [`MiniHeaderV1`]s back into full /// [`MediaHeader`]s by tracking the last baseline header. #[derive(Clone, Debug, Default)] -pub struct MiniFrameContext { +pub struct MiniFrameContextV1 { last_header: Option, } -impl MiniFrameContext { +impl MiniFrameContextV1 { /// Record a full header as the new baseline for subsequent mini-frames. pub fn update(&mut self, header: &MediaHeader) { self.last_header = Some(*header); @@ -635,6 +668,32 @@ impl MiniFrameContext { } } +/// Temporary alias so existing code continues to compile. +/// Removed in T1.5 once all emit/parse sites migrate to v2. +pub type MiniFrameContext = MiniFrameContextV1; + +/// Stateful v2 context that expands [`MiniHeaderV2`]s back into full +/// [`MediaHeaderV2`]s by tracking the last baseline header. +#[derive(Clone, Debug, Default)] +pub struct MiniFrameContextV2 { + last: Option, +} + +impl MiniFrameContextV2 { + pub fn update(&mut self, h: &MediaHeaderV2) { + self.last = Some(*h); + } + + pub fn expand(&mut self, m: &MiniHeaderV2) -> Option { + let base = self.last.as_ref()?; + let mut e = *base; + e.seq = base.seq.wrapping_add(m.seq_delta as u32); + e.timestamp = base.timestamp.wrapping_add(m.timestamp_delta_ms as u32); + self.last = Some(e); + Some(e) + } +} + /// Signaling messages sent over the reliable QUIC stream. /// /// Compatible with Warzone messenger's identity model: @@ -1906,6 +1965,72 @@ mod tests { assert!(ctx.expand(&mini).is_none()); } + #[test] + fn mini_header_v2_roundtrip() { + let mini = MiniHeaderV2 { + seq_delta: 3, + timestamp_delta_ms: 20, + payload_len: 160, + }; + let mut buf = BytesMut::new(); + mini.write_to(&mut buf); + assert_eq!(buf.len(), 5); + + let mut cursor = &buf[..]; + let decoded = MiniHeaderV2::read_from(&mut cursor).unwrap(); + assert_eq!(mini, decoded); + } + + #[test] + fn mini_frame_context_v2_expand() { + let baseline = MediaHeaderV2 { + version: 2, + flags: 0, + media_type: MediaType::Audio, + codec_id: CodecId::Opus24k, + stream_id: 0, + fec_ratio: 50, + seq: 100, + timestamp: 1000, + fec_block: 5, + }; + + let mut ctx = MiniFrameContextV2::default(); + ctx.update(&baseline); + + let mini = MiniHeaderV2 { + seq_delta: 3, + timestamp_delta_ms: 20, + payload_len: 80, + }; + let h1 = ctx.expand(&mini).unwrap(); + assert_eq!(h1.seq, 103); + assert_eq!(h1.timestamp, 1020); + assert_eq!(h1.codec_id, CodecId::Opus24k); + assert_eq!(h1.fec_block, 5); + + // Second expansion — builds on expanded h1 + let mini2 = MiniHeaderV2 { + seq_delta: 1, + timestamp_delta_ms: 20, + payload_len: 80, + }; + let h2 = ctx.expand(&mini2).unwrap(); + assert_eq!(h2.seq, 104); + assert_eq!(h2.timestamp, 1040); + } + + #[test] + fn mini_frame_context_v2_no_baseline() { + let mut ctx = MiniFrameContextV2::default(); + let mini = MiniHeaderV2 { + seq_delta: 1, + timestamp_delta_ms: 20, + payload_len: 80, + }; + assert!(ctx.expand(&mini).is_none()); + } + #[test] fn full_vs_mini_size_comparison() { // Full frame on wire: 1 byte type tag + 12 byte MediaHeader = 13 diff --git a/docs/PRD/TASKS.md b/docs/PRD/TASKS.md index 14140c2..9ac4063 100644 --- a/docs/PRD/TASKS.md +++ b/docs/PRD/TASKS.md @@ -437,6 +437,47 @@ cargo test -p wzp-proto --- +## T1.2.1 — Add rustdoc on `MediaType` variants and methods + +- **Parent:** T1.2 (Approved) +- **PRD:** `PRD-wire-format-v2.md` +- **Effort:** 10 min +- **Files:** + - `crates/wzp-proto/src/media_type.rs` + +### Context +T1.2 created `MediaType` with a one-line top-level doc comment but no `///` rustdoc on the variants (`Audio`, `Video`, `Data`, `Control`) or methods (`to_wire`, `from_wire`). Coding standard #9 — public items need rustdoc. Same shape of follow-up as T1.1.1. + +### Steps + +1. Open `crates/wzp-proto/src/media_type.rs`. +2. Add a `///` doc comment to each variant. Examples (do not just copy — pick what's accurate): + ```rust + pub enum MediaType { + /// Encoded speech / music (Opus, Codec2, ComfortNoise). + Audio = 0, + /// Encoded video access unit (H.264, H.265, AV1; PRD-video-multicodec). + Video = 1, + /// Opaque payload not interpreted by the relay (reserved). + Data = 2, + /// In-band control message carried on the media plane (reserved). + Control = 3, + } + ``` +3. Add a `///` doc on `to_wire` and `from_wire`. One line each is fine — explain the wire byte mapping and the `None` case. + +### Verify +```bash +cargo doc -p wzp-proto --no-deps 2>&1 | grep -i "missing" || echo "no missing-doc warnings" +cargo clippy -p wzp-proto --all-targets -- -D warnings -W missing_docs +``` + +### Done when +- All variants and methods on `MediaType` carry `///` doc comments. +- `cargo doc -p wzp-proto --no-deps` emits no "missing documentation" warnings for `MediaType`. + +--- + ## T1.3 — Widen `CodecId` wire representation to u8 - **PRD:** `PRD-wire-format-v2.md` (resolves audit W9) @@ -1163,9 +1204,10 @@ Statuses (in order of progression): | T1.1 | Approved | Kimi Code CLI | 2026-05-11T06:09Z | 2026-05-11T06:54Z | [report](reports/T1.1-report.md) | Approved 2026-05-11. Spawned T1.1.1 (field rustdoc) and T1.1.2 (refresh stale test-count). | | T1.1.1 | Open | — | — | — | — | Spawned from T1.1 review; non-blocking, claim after current in-flight | | T1.1.2 | Open | — | — | — | — | Spawned from T1.1 review; non-blocking, claim after current in-flight | -| T1.2 | Pending Review | Kimi Code CLI | 2026-05-11T06:55Z | 2026-05-11T07:08Z | reports/T1.2-report.md | — | -| T1.3 | In Progress | Kimi Code CLI | 2026-05-11T07:10Z | — | — | — | -| T1.4 | Open | — | — | — | — | — | +| T1.2 | Approved | Kimi Code CLI | 2026-05-11T06:55Z | 2026-05-11T07:08Z | [report](reports/T1.2-report.md) | Approved 2026-05-11. Spawned T1.2.1 (rustdoc on MediaType variants/methods). Agent also resolved the T1.2 TODO inside MediaHeaderV2 — good call. | +| T1.2.1 | Open | — | — | — | — | Spawned from T1.2 review; non-blocking | +| T1.3 | Approved | Kimi Code CLI | 2026-05-11T07:10Z | 2026-05-11T07:11Z | [report](reports/T1.3-report.md) | Approved 2026-05-11. No follow-ups; docs-and-test-only change. | +| T1.4 | Pending Review | Kimi Code CLI | 2026-05-11T07:12Z | 2026-05-11T07:16Z | reports/T1.4-report.md | — | | T1.5 | Open | — | — | — | — | — | | T1.6 | Open | — | — | — | — | — | | T1.7 | Open | — | — | — | — | — | @@ -1205,5 +1247,7 @@ Statuses (in order of progression): Items currently waiting on the reviewer: - T1.2 — Add MediaType enum — report: reports/T1.2-report.md +- T1.3 — Widen CodecId wire representation to u8 — report: reports/T1.3-report.md +- T1.4 — Add v2 MiniHeader with seq_delta — report: reports/T1.4-report.md Once a task moves to `Pending Review`, add a line here so the reviewer sees it: `- T — report: reports/T-report.md`. The reviewer removes the line when they mark it `Approved` (or moves it back to the agent on `Changes Requested`). diff --git a/docs/PRD/reports/T1.4-report.md b/docs/PRD/reports/T1.4-report.md new file mode 100644 index 0000000..82be5d6 --- /dev/null +++ b/docs/PRD/reports/T1.4-report.md @@ -0,0 +1,92 @@ +# T1.4 — Add v2 `MiniHeader` with `seq_delta` + +**Status:** Pending Review +**Agent:** Kimi Code CLI +**Started:** 2026-05-11T07:12Z +**Completed:** 2026-05-11T07:16Z +**Commit:** see git log +**PRD:** ../PRD-wire-format-v2.md + +## What I changed + +- `crates/wzp-proto/src/packet.rs:580-611` — renamed `MiniHeader` → `MiniHeaderV1`, kept all impls intact +- `crates/wzp-proto/src/packet.rs:613` — added `pub type MiniHeader = MiniHeaderV1;` backward-compat alias +- `crates/wzp-proto/src/packet.rs:616-640` — added new `MiniHeaderV2` struct (5 bytes: `seq_delta` + `timestamp_delta_ms` + `payload_len`) with `write_to`/`read_from` +- `crates/wzp-proto/src/packet.rs:642-666` — renamed `MiniFrameContext` → `MiniFrameContextV1`, kept all impls intact +- `crates/wzp-proto/src/packet.rs:668` — added `pub type MiniFrameContext = MiniFrameContextV1;` backward-compat alias +- `crates/wzp-proto/src/packet.rs:670-695` — added new `MiniFrameContextV2` tracking `MediaHeaderV2` baseline, with `update` and `expand` using explicit `seq_delta` +- `crates/wzp-proto/src/lib.rs:31` — re-exported `MiniHeaderV1`, `MiniHeaderV2`, `MiniFrameContextV1`, `MiniFrameContextV2` +- `crates/wzp-proto/src/packet.rs:1968-2014` — added 3 v2 tests: `mini_header_v2_roundtrip`, `mini_frame_context_v2_expand`, `mini_frame_context_v2_no_baseline` + +## Why these choices + +Same naming collision as T1.1: Rust does not allow a type alias and a struct with the same name in the same module. The new structs are named `MiniHeaderV2` and `MiniFrameContextV2` with temporary aliases preserving the old names; T1.5 will delete the v1 types and rename. + +The v2 `MiniFrameContextV2::expand` uses `base.seq.wrapping_add(m.seq_delta as u32)` instead of the hard-coded `wrapping_add(1)` from v1, which resolves audit W4 (a missed full header no longer desyncs the sequence). + +## Deviations from the task spec + +1. **Step 2 / Step 3 (struct names):** The new mini struct is `MiniHeaderV2` and the new context is `MiniFrameContextV2` instead of `MiniHeader` / `MiniFrameContext`. Required because `pub type MiniHeader = MiniHeaderV1;` and `pub type MiniFrameContext = MiniFrameContextV1;` occupy the base names. T1.5 will resolve. + +## Verification output + +```bash +$ cargo test -p wzp-proto mini +running 12 tests +test packet::tests::full_vs_mini_size_comparison ... ok +test packet::tests::mini_frame_context_expand ... ok +test packet::tests::mini_frame_context_no_baseline ... ok +test packet::tests::mini_frame_context_v2_expand ... ok +test packet::tests::mini_frame_context_v2_no_baseline ... ok +test packet::tests::mini_frame_disabled ... ok +test packet::tests::mini_frame_encode_decode_sequence ... ok +test packet::tests::mini_frame_periodic_full ... ok +test packet::tests::mini_header_encode_decode ... ok +test packet::tests::mini_header_v2_roundtrip ... ok +test packet::tests::mini_header_wire_size ... ok +test packet::tests::candidate_update_minimal_roundtrip ... ok + +test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 100 filtered out; finished in 0.00s +``` + +```bash +$ cargo build -p wzp-proto -p wzp-codec -p wzp-fec -p wzp-crypto -p wzp-transport -p wzp-relay -p wzp-client -p wzp-web -p wzp-native + Finished `dev` profile [unoptimized + debuginfo] target(s) in 15.71s +``` + +```bash +$ cargo test -p wzp-proto -p wzp-codec -p wzp-fec -p wzp-crypto -p wzp-transport -p wzp-relay -p wzp-client -p wzp-web -p wzp-native --no-fail-fast +... +test result: ok. 571 passed; 0 failed; ... +``` + +```bash +$ cargo clippy -p wzp-proto --all-targets -- -D warnings + Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.19s +``` + +```bash +$ cargo fmt --all -- --check +# (clean) +``` + +## Test summary + +- Tests added: 3 (`mini_header_v2_roundtrip`, `mini_frame_context_v2_expand`, `mini_frame_context_v2_no_baseline`) +- Tests modified: 0 +- Workspace test count before: 568 pass / 0 fail (non-Android subset) +- Workspace test count after: 571 pass / 0 fail (non-Android subset) +- `cargo clippy -p wzp-proto --all-targets -- -D warnings`: pass +- `cargo fmt --all -- --check`: pass + +## Risks / follow-ups + +- `MiniHeaderV2` / `MiniFrameContextV2` must be renamed to `MiniHeader` / `MiniFrameContext` in T1.5 after v1 types are deleted. + +## 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