use alloc::collections::btree_map::BTreeMap;
use alloc::vec::Vec;
use broadcast_common::ts_dup::{DuplicateVerdict, check_duplicate};
use crate::Diagnostic;
use crate::Report;
use crate::report::{Finding, Location, Severity};
use mpeg_ts::ts::{TS_PACKET_SIZE, TsPacket};
#[derive(Debug, Clone)]
struct CcState {
initialized: bool,
last_cc: u8,
last_packet: Vec<u8>,
dup_used: bool,
}
#[derive(Debug, Clone, Copy)]
pub struct CcAnomalyCheck;
impl Diagnostic for CcAnomalyCheck {
fn run(&self, ts: &[u8], report: &mut Report) {
let n_packets = ts.len() / TS_PACKET_SIZE;
let mut pid_states: BTreeMap<u16, CcState> = BTreeMap::new();
for i in 0..n_packets {
let offset = i * TS_PACKET_SIZE;
let raw = &ts[offset..offset + TS_PACKET_SIZE];
let Ok(pkt) = TsPacket::parse(raw) else {
continue;
};
let hdr = &pkt.header;
let pid = hdr.pid;
if pid == 0x1FFF {
continue;
}
if !hdr.has_payload {
continue;
}
let cc = hdr.continuity_counter;
let state = pid_states.entry(pid).or_insert(CcState {
initialized: false,
last_cc: 0,
last_packet: Vec::new(),
dup_used: false,
});
if !state.initialized {
state.initialized = true;
state.last_cc = cc;
state.last_packet = raw.to_vec();
continue;
}
let has_discontinuity = if hdr.has_adaptation {
let af_len = raw[4] as usize;
af_len > 0 && (raw[5] & 0x80) != 0
} else {
false
};
if has_discontinuity {
state.last_cc = cc;
state.last_packet = raw.to_vec();
state.dup_used = false;
continue;
}
match check_duplicate(&state.last_packet, raw, state.dup_used) {
DuplicateVerdict::Legal => {
state.dup_used = true;
}
DuplicateVerdict::IllegalThirdRepeat => {
report.push(Finding::new(
Severity::Error,
Location::new(i, pid),
"cc-anomaly",
alloc::format!(
"PID 0x{pid:04X}: third consecutive repeat of CC={cc} \
(§2.4.3.3 permits two, and only two)"
),
));
state.dup_used = true;
}
DuplicateVerdict::NotDuplicate => {
let expected = (state.last_cc + 1) & 0x0F;
if cc != expected {
report.push(Finding::new(
Severity::Error,
Location::new(i, pid),
"cc-anomaly",
alloc::format!(
"PID 0x{pid:04X}: expected CC={expected}, got CC={cc} \
(not a legal duplicate or signalled discontinuity)"
),
));
}
state.dup_used = false;
state.last_cc = cc;
state.last_packet = raw.to_vec();
}
_ => unreachable!("unhandled DuplicateVerdict variant"),
}
}
}
}