use alloc::collections::btree_map::BTreeMap;
use alloc::vec::Vec;
use crate::Diagnostic;
use crate::Report;
use crate::report::{Finding, Location, Severity};
use mpeg_ts::ts::SectionReassembler;
use mpeg_ts::ts::{TS_PACKET_SIZE, TsPacket};
#[derive(Debug, Clone, Copy)]
pub struct PatPmtVersionCheck;
impl PatPmtVersionCheck {
fn process_section(
&self,
pid: u16,
section_data: &[u8],
versions: &mut BTreeMap<(u16, u8), u8>,
report: &mut Report,
) {
if section_data.len() < 8 {
return;
}
let table_id = section_data[0];
let _syntax = (section_data[1] & 0x80) != 0;
let _section_length = (((section_data[1] & 0x0F) as u16) << 8) | section_data[2] as u16;
let table_id_ext = ((section_data[3] as u16) << 8) | section_data[4] as u16;
let version = (section_data[5] >> 1) & 0x1F;
let _current_next = (section_data[5] & 0x01) != 0;
let _section_number = section_data[6];
let _last_section_number = section_data[7];
let key = (pid, table_id);
let prev = versions.get(&key).copied();
if let Some(prev_ver) = prev
&& prev_ver != version
{
let severity = Severity::Info;
let rule_id = if table_id == 0x00 {
"pat-version"
} else if table_id == 0x02 {
"pmt-version"
} else {
return;
};
let table_name = if table_id == 0x00 { "PAT" } else { "PMT" };
report.push(Finding::new(
severity,
Location::new(0, pid),
rule_id,
alloc::format!(
"{table_name} version_number changed: {prev_ver} โ {version} \
(table_id_ext=0x{table_id_ext:04X})",
),
));
}
versions.insert(key, version);
}
}
impl Diagnostic for PatPmtVersionCheck {
fn run(&self, ts: &[u8], report: &mut Report) {
let n_packets = ts.len() / TS_PACKET_SIZE;
let mut reassemblers: BTreeMap<u16, SectionReassembler> = BTreeMap::new();
let mut versions: BTreeMap<(u16, u8), u8> = BTreeMap::new();
let mut pmt_pids: Vec<u16> = Vec::new();
reassemblers.entry(0x0000).or_default();
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 pid = pkt.header.pid;
if !reassemblers.contains_key(&pid) {
continue;
}
let payload = match pkt.payload {
Some(pl) => pl,
None => continue,
};
let pusi = pkt.header.pusi;
reassemblers.get_mut(&pid).unwrap().feed(payload, pusi);
let mut new_pmt_pids: Vec<u16> = Vec::new();
while let Some(section) = reassemblers.get_mut(&pid).unwrap().pop_section() {
let section_data = §ion[..];
self.process_section(pid, section_data, &mut versions, report);
if section_data.len() > 8 && section_data[0] == 0x00 {
let mut off = 8usize;
while off + 4 <= section_data.len() {
let pmt_pid = (((section_data[off + 2] as u16) & 0x1F) << 8)
| section_data[off + 3] as u16;
if !pmt_pids.contains(&pmt_pid) && !new_pmt_pids.contains(&pmt_pid) {
new_pmt_pids.push(pmt_pid);
}
off += 4;
}
}
}
for pmt_pid in new_pmt_pids {
pmt_pids.push(pmt_pid);
reassemblers.entry(pmt_pid).or_default();
}
}
}
}