fix(audit): address C2, C3, M4, M5 from 2026-05-25 audit

C2: Add EncryptingTransport wrapper — all media I/O now goes through
ChaChaSession encrypt/decrypt before hitting the QUIC datagram path.
cli.rs run_live/run_silence/run_file_mode accept Arc<dyn MediaTransport>
and receive a wrapped transport after the handshake.

C3: Wire VideoScorer::observe() into both plain and trunked forwarding
loops in room.rs. Packets from participants with Abusive verdict are
dropped before forwarding. last_bwe_kbps tracked from quality reports.

M4: Widen FEC repair symbol index from u8 to u16 throughout
(FecEncoder::generate_repair, FecDecoder::add_symbol, all call sites in
call.rs, bench.rs, pipeline.rs, wzp-android). Eliminates theoretical
wrapping when num_source + repair_count > 255.

M5: Track last_encrypt_timestamp in ChaChaSession. debug_assert in
encrypt() that timestamp is non-decreasing across calls (including post-
rekey). complete_rekey() explicitly preserves last_encrypt_timestamp to
prevent accidental timestamp reset regressions.

583 tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-05-25 06:20:05 +04:00
parent 15af58a95d
commit 52a6f5e048
13 changed files with 299 additions and 29 deletions

View File

@@ -111,7 +111,7 @@ impl RelayPipeline {
let header = &packet.header;
let _ = self.fec_decoder.add_symbol(
(header.fec_block & 0xFF) as u8,
(header.fec_block >> 8) as u8,
header.fec_block >> 8,
header.is_repair(),
&packet.payload,
);

View File

@@ -21,6 +21,8 @@ use wzp_proto::{MediaTransport, default_signal_version};
use crate::conformance::ConformanceMeter;
use crate::metrics::RelayMetrics;
use crate::trunk::TrunkBatcher;
use crate::verdict::Verdict;
use crate::video_scorer::VideoScorer;
/// Debug tap: logs packet metadata for matching rooms.
#[derive(Clone)]
@@ -1194,6 +1196,9 @@ async fn run_participant_plain(
None
};
let mut video_scorer = VideoScorer::new();
let mut last_bwe_kbps: Option<u32> = None;
info!(
room = %room_name,
participant = participant_id,
@@ -1261,10 +1266,20 @@ async fn run_participant_plain(
);
}
// TODO(T6.2-follow-up): feed video packets to VideoScorer here.
// if pkt.header.media_type == MediaType::Video {
// video_scorer.observe(&pkt.header, pkt.payload.len(), now, bwe_kbps);
// }
// Feed video packets to VideoScorer; drop if verdict is Abusive.
if pkt.header.media_type == wzp_proto::MediaType::Video {
let now = std::time::Instant::now();
video_scorer.observe(&pkt.header, pkt.payload.len(), now, last_bwe_kbps);
if let Some(Verdict::Abusive) = video_scorer.verdict() {
warn!(
room = %room_name,
participant = participant_id,
seq = pkt.header.seq,
"VideoScorer: Abusive verdict — dropping packet"
);
continue;
}
}
// Update per-session quality metrics if a quality report is present
if let Some(ref report) = pkt.quality_report {
@@ -1274,6 +1289,7 @@ async fn run_participant_plain(
// Update receiver state from this participant's quality report (if present).
if let Some(ref report) = pkt.quality_report {
let bwe_kbps = report.bitrate_cap_kbps as u32;
last_bwe_kbps = Some(bwe_kbps);
room_mgr.update_receiver_state(&room_name, participant_id, bwe_kbps, report.loss_pct);
}
@@ -1454,6 +1470,8 @@ async fn run_participant_trunked(
let mut last_log_instant = std::time::Instant::now();
let mut conformance =
ConformanceMeter::with_token_bucket(crate::conformance::TokenBucket::for_audio_session());
let mut video_scorer_trunked = VideoScorer::new();
let mut last_bwe_kbps_trunked: Option<u32> = None;
info!(
room = %room_name,
@@ -1533,9 +1551,25 @@ async fn run_participant_trunked(
);
}
// Feed video packets to VideoScorer; drop if verdict is Abusive.
if pkt.header.media_type == wzp_proto::MediaType::Video {
let now = std::time::Instant::now();
video_scorer_trunked.observe(&pkt.header, pkt.payload.len(), now, last_bwe_kbps_trunked);
if let Some(Verdict::Abusive) = video_scorer_trunked.verdict() {
warn!(
room = %room_name,
participant = participant_id,
seq = pkt.header.seq,
"VideoScorer: Abusive verdict — dropping packet (trunked)"
);
continue;
}
}
// Update receiver state from this participant's quality report.
if let Some(ref report) = pkt.quality_report {
let bwe_kbps = report.bitrate_cap_kbps as u32;
last_bwe_kbps_trunked = Some(bwe_kbps);
room_mgr.update_receiver_state(&room_name, participant_id, bwe_kbps, report.loss_pct);
}