feat(video+desktop): camera capture, video UI, E2E AEAD wiring, test fixes
Blockers 4 & 5: browser getUserMedia → JPEG IPC → Rust I420 pipeline; remote video strip renders decoded frames via canvas; EncryptingTransport wraps QuinnTransport so WZP AEAD is applied to all media (C2 fix). Test fixes: HandshakeResult.session destructuring across relay/client/crypto integration tests; video_codecs field added to all CallOffer/CallAnswer structs; wzp-video pipeline_roundtrip integration tests added. PRD docs: five Kimi-ready specs for E2E encryption, Android NDK 0.9 migration, quality upgrade flow, wire-format hardening, and clippy debt. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
260
docs/PRD/PRD-clippy-debt.md
Normal file
260
docs/PRD/PRD-clippy-debt.md
Normal file
@@ -0,0 +1,260 @@
|
||||
# PRD: Fix wzp-codec Clippy Lint Debt
|
||||
|
||||
> **Status:** proposed
|
||||
> **Resolves:** 9 pre-existing clippy lints in `crates/wzp-codec/src/` that cause `cargo clippy --workspace -D warnings` to fail, breaking any strict-CI configuration.
|
||||
> **Depends on:** Nothing — all changes are in `crates/wzp-codec/src/`.
|
||||
|
||||
## Problem
|
||||
|
||||
`cargo clippy -p wzp-codec -- -D warnings` fails with 9 lints across 5 files.
|
||||
These are pre-existing code patterns that were never flagged during development
|
||||
because the CI flag was not set. They have no runtime impact today but prevent
|
||||
adding `-D warnings` to CI without first cleaning them up.
|
||||
|
||||
The 3 errors in `deps/featherchat` are in a submodule — do NOT touch them.
|
||||
`warzone_protocol` clippy errors are accepted debt (not our code).
|
||||
|
||||
## Goals
|
||||
|
||||
- `cargo clippy -p wzp-codec -- -D warnings` exits 0.
|
||||
- No behavior changes — every fix is a semantically equivalent rewrite.
|
||||
- No changes outside `crates/wzp-codec/src/`.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Fixing clippy lints in any crate other than `wzp-codec`.
|
||||
- Adding new functionality.
|
||||
- Touching the `deps/featherchat` submodule.
|
||||
|
||||
## Design
|
||||
|
||||
### Lint inventory
|
||||
|
||||
| Lint | Count | File | Approx line | Fix |
|
||||
|------|-------|------|-------------|-----|
|
||||
| `implicit_saturating_sub` | 1 | `aec.rs` | 117–119 | `saturating_sub` |
|
||||
| `needless_range_loop` | 2 | `aec.rs:164`, `resample.rs:51` | — | iterate with `iter().enumerate()` or direct iter |
|
||||
| `manual_div_ceil` | 2 | `codec2_dec.rs:48`, `codec2_enc.rs:48` | — | `div_ceil` |
|
||||
| `manual_clamp` | 2 | `denoise.rs:59`, `opus_enc.rs:250` | — | `.clamp(min, max)` |
|
||||
| `manual_ascii_check` | 1 | `opus_enc.rs:104` | — | `.eq_ignore_ascii_case()` |
|
||||
| `same_item_push` | 1 | `resample.rs:184` | — | `vec.resize` or `extend(repeat)` |
|
||||
|
||||
### Fix details
|
||||
|
||||
#### 1. `implicit_saturating_sub` — `aec.rs` line ~117
|
||||
|
||||
Current code:
|
||||
|
||||
```rust
|
||||
fn delay_available(&self) -> usize {
|
||||
let buffered = self.delay_write - self.delay_read;
|
||||
if buffered > self.delay_samples {
|
||||
buffered - self.delay_samples
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Clippy wants `saturating_sub` because the subtraction can underflow if
|
||||
`buffered < self.delay_samples`:
|
||||
|
||||
```rust
|
||||
fn delay_available(&self) -> usize {
|
||||
let buffered = self.delay_write - self.delay_read;
|
||||
buffered.saturating_sub(self.delay_samples)
|
||||
}
|
||||
```
|
||||
|
||||
This is semantically identical (both return 0 when `buffered <= delay_samples`).
|
||||
|
||||
#### 2a. `needless_range_loop` — `aec.rs` line ~164
|
||||
|
||||
Current code:
|
||||
|
||||
```rust
|
||||
for i in 0..n {
|
||||
let near_f = nearend[i] as f32;
|
||||
let base = (self.far_pos + fl * ((n / fl) + 2) + i - n) % fl;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
`i` is used both to index `nearend[i]` and in arithmetic (`+ i - n`).
|
||||
Clippy fires because `nearend[i]` could use `.iter().enumerate()`.
|
||||
Convert to `enumerate`:
|
||||
|
||||
```rust
|
||||
for (i, &sample) in nearend.iter().enumerate() {
|
||||
let near_f = sample as f32;
|
||||
let base = (self.far_pos + fl * ((n / fl) + 2) + i - n) % fl;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Make sure to update any references to `nearend[i]` inside the loop body
|
||||
to use `sample` (or `near_f` directly). Also update the NLMS adaptation
|
||||
sub-loop if it references `nearend[i]`.
|
||||
|
||||
#### 2b. `needless_range_loop` — `resample.rs` line ~51
|
||||
|
||||
Current code:
|
||||
|
||||
```rust
|
||||
for i in 0..FIR_TAPS {
|
||||
let n = i as f64 - m / 2.0;
|
||||
let sinc = ...;
|
||||
let t = 2.0 * i as f64 / m - 1.0;
|
||||
let kaiser = ...;
|
||||
kernel[i] = sinc * kaiser;
|
||||
}
|
||||
```
|
||||
|
||||
`i` is used both as an index (`kernel[i]`) and in arithmetic. Use
|
||||
`iter_mut().enumerate()`:
|
||||
|
||||
```rust
|
||||
for (i, slot) in kernel.iter_mut().enumerate() {
|
||||
let n = i as f64 - m / 2.0;
|
||||
let sinc = ...;
|
||||
let t = 2.0 * i as f64 / m - 1.0;
|
||||
let kaiser = ...;
|
||||
*slot = sinc * kaiser;
|
||||
}
|
||||
```
|
||||
|
||||
#### 3a. `manual_div_ceil` — `codec2_dec.rs` line ~48
|
||||
|
||||
Current code:
|
||||
|
||||
```rust
|
||||
fn bytes_per_frame(&self) -> usize {
|
||||
(self.inner.bits_per_frame() + 7) / 8
|
||||
}
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```rust
|
||||
fn bytes_per_frame(&self) -> usize {
|
||||
self.inner.bits_per_frame().div_ceil(8)
|
||||
}
|
||||
```
|
||||
|
||||
`div_ceil` is stable as of Rust 1.73. The builder uses a recent enough
|
||||
toolchain. If `bits_per_frame()` returns `usize`, the method is available.
|
||||
If it returns a different integer type, cast accordingly.
|
||||
|
||||
#### 3b. `manual_div_ceil` — `codec2_enc.rs` line ~48
|
||||
|
||||
Same pattern, same fix:
|
||||
|
||||
```rust
|
||||
fn bytes_per_frame(&self) -> usize {
|
||||
self.inner.bits_per_frame().div_ceil(8)
|
||||
}
|
||||
```
|
||||
|
||||
#### 4a. `manual_clamp` — `denoise.rs` line ~59
|
||||
|
||||
Current code:
|
||||
|
||||
```rust
|
||||
let clamped = val.max(-32768.0).min(32767.0);
|
||||
```
|
||||
|
||||
Replace with:
|
||||
|
||||
```rust
|
||||
let clamped = val.clamp(-32768.0_f32, 32767.0_f32);
|
||||
```
|
||||
|
||||
Note: `.clamp()` on `f32` requires both bounds to be the same type. If `val`
|
||||
is already `f32`, no extra cast is needed. Verify the type of `val` in
|
||||
context (it is `f32` per the output array type `[f32; 480]`).
|
||||
|
||||
#### 4b. `manual_clamp` — `opus_enc.rs` line ~252
|
||||
|
||||
Read the surrounding code for the exact pattern. It will be something like:
|
||||
|
||||
```rust
|
||||
let v = if x < min_val { min_val } else if x > max_val { max_val } else { x };
|
||||
```
|
||||
|
||||
or the `.max().min()` chain. Replace with `x.clamp(min_val, max_val)`.
|
||||
|
||||
#### 5. `manual_ascii_check` — `opus_enc.rs` line ~104
|
||||
|
||||
Current code:
|
||||
|
||||
```rust
|
||||
Ok(v) => !v.is_empty() && v != "0" && v.to_ascii_lowercase() != "false",
|
||||
```
|
||||
|
||||
Clippy wants `.eq_ignore_ascii_case()` instead of lowercasing the whole string:
|
||||
|
||||
```rust
|
||||
Ok(v) => !v.is_empty() && v != "0" && !v.eq_ignore_ascii_case("false"),
|
||||
```
|
||||
|
||||
#### 6. `same_item_push` — `resample.rs` line ~183
|
||||
|
||||
Current code:
|
||||
|
||||
```rust
|
||||
for _ in 1..RATIO {
|
||||
work.push(0.0);
|
||||
}
|
||||
```
|
||||
|
||||
This pushes the same `0.0` value `(RATIO - 1)` times. Replace with:
|
||||
|
||||
```rust
|
||||
work.resize(work.len() + (RATIO - 1), 0.0f64);
|
||||
```
|
||||
|
||||
Or equivalently:
|
||||
|
||||
```rust
|
||||
work.extend(std::iter::repeat(0.0f64).take(RATIO - 1));
|
||||
```
|
||||
|
||||
Note: `RATIO` is a `const usize`. Verify `work` is `Vec<f64>` in context
|
||||
(it is — `work.push(s as f64)` immediately before).
|
||||
|
||||
## Implementation steps
|
||||
|
||||
1. Read each file at the line numbers listed above to confirm the exact current
|
||||
code before editing (line numbers may shift slightly due to prior edits).
|
||||
2. Apply all 9 fixes. They are independent — no ordering requirement.
|
||||
3. Run `cargo clippy -p wzp-codec -- -D warnings` locally or via the CI
|
||||
command.
|
||||
4. If any lint persists, re-read that file section and adjust.
|
||||
|
||||
## Files to read before implementing
|
||||
|
||||
- `crates/wzp-codec/src/aec.rs` lines 114–200
|
||||
- `crates/wzp-codec/src/resample.rs` lines 45–70 and 178–190
|
||||
- `crates/wzp-codec/src/codec2_dec.rs` lines 40–55
|
||||
- `crates/wzp-codec/src/codec2_enc.rs` lines 40–55
|
||||
- `crates/wzp-codec/src/denoise.rs` lines 45–65
|
||||
- `crates/wzp-codec/src/opus_enc.rs` lines 96–110 and 244–260
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
cargo clippy -p wzp-codec -- -D warnings
|
||||
```
|
||||
|
||||
Expected: exits 0 with no warnings.
|
||||
|
||||
Also run to confirm no regressions:
|
||||
|
||||
```bash
|
||||
cargo test -p wzp-codec
|
||||
```
|
||||
|
||||
## Done when
|
||||
|
||||
`cargo clippy -p wzp-codec -- -D warnings` exits 0. All 9 lints are gone.
|
||||
`cargo test -p wzp-codec` passes. No changes outside `crates/wzp-codec/src/`.
|
||||
Reference in New Issue
Block a user