use dvb_si::tables::AnyTable;
fn crc32_mpeg2(data: &[u8]) -> u32 {
dvb_common::crc32_mpeg2::compute(data)
}
fn push_crc(v: &mut Vec<u8>) {
let crc = crc32_mpeg2(v);
v.extend_from_slice(&crc.to_be_bytes());
}
fn build_pat(tsid: u16, version: u8, entries: &[(u16, u16)]) -> Vec<u8> {
let section_length = (5 + entries.len() * 4 + 4) as u16; let mut v = vec![
0x00u8,
0xB0 | ((section_length >> 8) as u8 & 0x0F),
(section_length & 0xFF) as u8,
(tsid >> 8) as u8,
(tsid & 0xFF) as u8,
0xC0 | ((version & 0x1F) << 1) | 0x01,
0x00,
0x00,
];
for &(pn, pid) in entries {
v.extend_from_slice(&pn.to_be_bytes());
v.push(0xE0 | ((pid >> 8) as u8 & 0x1F));
v.push((pid & 0xFF) as u8);
}
push_crc(&mut v);
v
}
fn build_pmt(program_number: u16, version: u8, pcr_pid: u16) -> Vec<u8> {
let section_length = (5 + 2 + 2 + 4) as u16; let mut v = vec![
0x02u8,
0xB0 | ((section_length >> 8) as u8 & 0x0F),
(section_length & 0xFF) as u8,
(program_number >> 8) as u8,
(program_number & 0xFF) as u8,
0xC0 | ((version & 0x1F) << 1) | 0x01,
0x00,
0x00,
0xE0 | ((pcr_pid >> 8) as u8 & 0x1F),
(pcr_pid & 0xFF) as u8,
0xF0, 0x00, ];
push_crc(&mut v);
v
}
fn build_sdt(table_id: u8, tsid: u16, onid: u16) -> Vec<u8> {
let section_length: u16 = 12;
let mut v = vec![
table_id,
0xB0 | ((section_length >> 8) as u8 & 0x0F),
(section_length & 0xFF) as u8,
(tsid >> 8) as u8,
(tsid & 0xFF) as u8,
0xC0 | 0x01, 0x00,
0x00,
(onid >> 8) as u8,
(onid & 0xFF) as u8,
0xFF, ];
push_crc(&mut v);
v
}
fn build_eit(table_id: u8, service_id: u16, tsid: u16, onid: u16) -> Vec<u8> {
let section_length: u16 = 15;
let mut v = vec![
table_id,
0xB0 | ((section_length >> 8) as u8 & 0x0F),
(section_length & 0xFF) as u8,
(service_id >> 8) as u8,
(service_id & 0xFF) as u8,
0xC0 | 0x01, 0x00,
0x00,
(tsid >> 8) as u8,
(tsid & 0xFF) as u8,
(onid >> 8) as u8,
(onid & 0xFF) as u8,
0x00, table_id, ];
push_crc(&mut v);
v
}
fn build_tdt() -> Vec<u8> {
vec![0x70, 0x70, 0x05, 0xE4, 0x09, 0x12, 0x34, 0x56]
}
fn build_tot() -> Vec<u8> {
let section_length: u16 = (5 + 2 + 4) as u16;
let mut v = vec![
0x73u8,
0x70 | ((section_length >> 8) as u8 & 0x0F), (section_length & 0xFF) as u8,
0xE4,
0x09,
0x12,
0x34,
0x56, 0xF0,
0x00, ];
push_crc(&mut v);
v
}
fn build_dsmcc(table_id: u8) -> Vec<u8> {
let section_length: u16 = 9;
let mut v = vec![
table_id,
0xB0 | ((section_length >> 8) as u8 & 0x0F),
(section_length & 0xFF) as u8,
0x00,
0x01, 0xC0 | 0x01, 0x00, 0x00, ];
push_crc(&mut v);
v
}
fn build_protection_message() -> Vec<u8> {
let reference = [0x01u8];
let hash = [0xAAu8, 0xBB, 0xCC, 0xDD];
let mut hashes_loop: Vec<u8> = vec![(1u8 << 4) | (reference.len() as u8)];
hashes_loop.extend_from_slice(&reference);
hashes_loop.extend_from_slice(&hash);
let loop_len = hashes_loop.len();
let mut body: Vec<u8> = vec![
0x00, hash.len() as u8, 0x01, 0xF0 | ((loop_len >> 8) as u8 & 0x0F), (loop_len & 0xFF) as u8, ];
body.extend_from_slice(&hashes_loop);
body.push(2);
body.extend_from_slice(&[0xDE, 0xAD]); body.push(3);
body.extend_from_slice(&[0x11, 0x22, 0x33]); body.extend_from_slice(&[0x90, 0x91, 0x92, 0x93, 0x94, 0x95]);
let section_length = (5 + body.len() + 4) as u16; let mut v = vec![
0x7Bu8,
0xB0 | ((section_length >> 8) as u8 & 0x0F),
(section_length & 0xFF) as u8,
0x00,
0x01, 0xC0 | 0x01, 0x00, 0x00, ];
v.extend_from_slice(&body);
push_crc(&mut v);
v
}
#[test]
fn dispatch_pat_table_id_0x00() {
let bytes = build_pat(0x1234, 0, &[(1, 0x0100)]);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Pat(_)),
"expected Pat, got {parsed:?}"
);
}
#[test]
fn dispatch_pmt_table_id_0x02() {
let bytes = build_pmt(42, 0, 0x0100);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Pmt(_)),
"expected Pmt, got {parsed:?}"
);
}
#[test]
fn dispatch_sdt_actual_table_id_0x42() {
let bytes = build_sdt(0x42, 1, 0x0020);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Sdt(_)),
"expected Sdt, got {parsed:?}"
);
}
#[test]
fn dispatch_sdt_other_table_id_0x46() {
let bytes = build_sdt(0x46, 1, 0x0020);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Sdt(_)),
"expected Sdt (other), got {parsed:?}"
);
}
#[test]
fn dispatch_eit_pf_actual_0x4e() {
let bytes = build_eit(0x4E, 100, 1, 0x0020);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Eit(_)),
"expected Eit (p/f actual), got {parsed:?}"
);
}
#[test]
fn dispatch_eit_schedule_segment_0x50() {
let bytes = build_eit(0x50, 100, 1, 0x0020);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Eit(_)),
"expected Eit (schedule 0x50), got {parsed:?}"
);
}
#[test]
fn dispatch_tdt_short_section_0x70() {
let bytes = build_tdt();
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Tdt(_)),
"expected Tdt, got {parsed:?}"
);
}
#[test]
fn dispatch_tot_0x73() {
let bytes = build_tot();
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::Tot(_)),
"expected Tot, got {parsed:?}"
);
}
#[test]
fn dispatch_dsmcc_0x3b() {
let bytes = build_dsmcc(0x3B);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::DsmccSection(_)),
"expected DsmccSection, got {parsed:?}"
);
}
#[test]
fn dispatch_0x3e_routes_to_dsmcc_not_mpe() {
let bytes = build_dsmcc(0x3E);
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::DsmccSection(_)),
"0x3E should dispatch to DsmccSection, got {parsed:?}"
);
assert!(
!matches!(parsed, AnyTable::MpeDatagram(_)),
"0x3E must not auto-dispatch to MpeDatagram"
);
}
#[test]
fn dispatch_protection_message_0x7b() {
let bytes = build_protection_message();
let parsed = AnyTable::parse(&bytes).unwrap();
assert!(
matches!(parsed, AnyTable::ProtectionMessage(_)),
"expected ProtectionMessage, got {parsed:?}"
);
}
#[test]
fn dispatch_unknown_table_id_0x90() {
let bytes = [0x90u8, 0x01, 0x00]; let parsed = AnyTable::parse(&bytes).unwrap();
match parsed {
AnyTable::Unknown { table_id, raw } => {
assert_eq!(table_id, 0x90);
assert_eq!(raw, &[0x90u8, 0x01, 0x00]);
}
other => panic!("expected Unknown, got {other:?}"),
}
}
#[test]
fn dispatch_empty_input_returns_buffer_too_short() {
let err = AnyTable::parse(&[]).unwrap_err();
assert!(
matches!(err, dvb_si::error::Error::BufferTooShort { .. }),
"expected BufferTooShort, got {err:?}"
);
}
#[test]
fn dispatched_ranges_are_sorted_and_disjoint() {
let ranges = AnyTable::DISPATCHED_RANGES;
for &(lo, hi) in ranges {
assert!(lo <= hi, "malformed range ({lo:#04x}, {hi:#04x})");
}
let mut sorted = ranges.to_vec();
sorted.sort_by_key(|r| r.0);
for w in sorted.windows(2) {
let (_, prev_hi) = w[0];
let (next_lo, _) = w[1];
assert!(next_lo > prev_hi, "overlapping dispatch ranges: {w:?}");
}
}
#[test]
fn parse_as_mpe_datagram_for_0x3e() {
use dvb_si::tables::mpe::MpeDatagramSection;
let section_length = 9u16 + 4; let mut v = vec![
0x3Eu8,
0x70 | ((section_length >> 8) as u8 & 0x0F), (section_length & 0xFF) as u8,
0xAA, 0xBB, 0xC1, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, ];
v.extend_from_slice(&[0xDE, 0xAD, 0xBE, 0xEF]);
let mpe = AnyTable::parse_as::<MpeDatagramSection>(&v).expect("valid MPE section must parse");
assert_eq!(mpe.mac_address, [0x44, 0x33, 0x22, 0x11, 0xBB, 0xAA]);
assert!(mpe.payload.is_empty());
}
#[test]
fn every_table_id_variant_is_dispatched() {
let ranges = dvb_si::tables::AnyTable::DISPATCHED_RANGES;
for b in 0u8..=0xFF {
if dvb_si::TableId::try_from(b).is_ok() {
let covered = ranges.iter().any(|&(lo, hi)| b >= lo && b <= hi);
assert!(
covered,
"TableId byte {b:#04x} is a known variant but is not covered by \
AnyTable::DISPATCHED_RANGES"
);
}
}
}
#[test]
fn name_maps_variant_to_tabledef_name() {
let pat = build_pat(0x0001, 0, &[(1, 0x0100)]);
let table = AnyTable::parse(&pat).expect("valid PAT");
assert_eq!(table.name(), "PROGRAM_ASSOCIATION");
let unknown = AnyTable::parse(&[0x90, 0x01, 0x00]).expect("unknown ok");
assert_eq!(unknown.name(), "UNKNOWN");
}