#![cfg(feature = "ts")]
use dvb_bbframe::header::{Bbheader, Matype, Mode, TsGs, BBHEADER_LEN};
use dvb_bbframe::packet::NM_UP_SIZE;
use dvb_common::crc32_mpeg2;
use dvb_si::demux::SiDemux;
use dvb_si::tables::AnyTable;
use dvb_t2mi::payload::AnyPayload;
use dvb_t2mi::pump::T2miPump;
const TS_SYNC: u8 = 0x47;
const TS_PACKET_SIZE: usize = 188;
const PUSI_MASK: u8 = 0x40;
const PID_HI_MASK: u8 = 0x1F;
const PAYLOAD_FLAG: u8 = 0x10;
fn build_pat_section() -> Vec<u8> {
use dvb_common::Serialize;
use dvb_si::tables::pat::{Pat, PatEntry};
let pat = Pat {
transport_stream_id: 0x0001,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
entries: vec![PatEntry {
program_number: 1,
pid: 0x0100,
}],
};
let mut buf = vec![0u8; pat.serialized_len()];
pat.serialize_into(&mut buf).expect("PAT serialize");
buf
}
fn inner_ts_packet(pid: u16, section: &[u8]) -> [u8; TS_PACKET_SIZE] {
assert!(
section.len() <= TS_PACKET_SIZE - 5,
"section too large to fit in one TS packet"
);
let mut pkt = [0xFFu8; TS_PACKET_SIZE];
pkt[0] = TS_SYNC;
pkt[1] = PUSI_MASK | (((pid >> 8) as u8) & PID_HI_MASK);
pkt[2] = (pid & 0xFF) as u8;
pkt[3] = PAYLOAD_FLAG;
pkt[4] = 0x00; pkt[5..5 + section.len()].copy_from_slice(section);
pkt
}
fn build_nm_bbframe(inner_ts: &[u8; TS_PACKET_SIZE]) -> Vec<u8> {
use dvb_bbframe::crc::crc8;
let upl_bits: u16 = 1504; let dfl_bits: u16 = 1504; let syncd_bits: u16 = 0;
let hdr = Bbheader {
matype: Matype {
ts_gs: TsGs::Ts,
sis: true,
ccm: true,
issyi: false,
npd: false,
ext: 0,
isi: 0,
},
upl: upl_bits,
sync: TS_SYNC,
dfl: dfl_bits,
syncd: syncd_bits,
mode: Mode::Normal,
issy_in_header: None,
};
let header_bytes = hdr.serialize();
let prev_up_all_zeros = [0u8; TS_PACKET_SIZE];
let crc_of_prev = crc8(&prev_up_all_zeros);
let mut data_field = [0u8; TS_PACKET_SIZE];
data_field[0] = crc_of_prev; data_field[1..].copy_from_slice(&inner_ts[1..]);
let mut frame = Vec::with_capacity(BBHEADER_LEN + TS_PACKET_SIZE);
frame.extend_from_slice(&header_bytes);
frame.extend_from_slice(&data_field);
frame
}
fn build_t2mi_packet(bbframe_data: &[u8]) -> Vec<u8> {
let mut payload = Vec::with_capacity(3 + bbframe_data.len());
payload.push(0x00); payload.push(0x05); payload.push(0x80);
payload.extend_from_slice(bbframe_data);
let payload_len_bits = (payload.len() * 8) as u16;
let mut pkt = Vec::with_capacity(6 + payload.len() + 4);
pkt.push(0x00); pkt.push(0x01); pkt.push(0x00); pkt.push(0x00); pkt.extend_from_slice(&payload_len_bits.to_be_bytes());
pkt.extend_from_slice(&payload);
let crc = crc32_mpeg2::compute(&pkt);
pkt.extend_from_slice(&crc.to_be_bytes());
pkt
}
const MAX_T2MI_IN_PUSI: usize = TS_PACKET_SIZE - 4 - 1;
const MAX_T2MI_IN_CONT: usize = TS_PACKET_SIZE - 4;
fn outer_ts_packets(pid: u16, t2mi_data: &[u8]) -> Vec<[u8; TS_PACKET_SIZE]> {
assert!(
t2mi_data.len() <= MAX_T2MI_IN_PUSI + MAX_T2MI_IN_CONT,
"T2-MI data too large ({} bytes) to fit in two outer TS packets",
t2mi_data.len()
);
let mut result = Vec::new();
{
let chunk = &t2mi_data[..t2mi_data.len().min(MAX_T2MI_IN_PUSI)];
let mut pkt = [0xFFu8; TS_PACKET_SIZE];
pkt[0] = TS_SYNC;
pkt[1] = PUSI_MASK | (((pid >> 8) as u8) & PID_HI_MASK);
pkt[2] = (pid & 0xFF) as u8;
pkt[3] = PAYLOAD_FLAG;
pkt[4] = 0x00; pkt[5..5 + chunk.len()].copy_from_slice(chunk);
result.push(pkt);
}
if t2mi_data.len() > MAX_T2MI_IN_PUSI {
let remainder = &t2mi_data[MAX_T2MI_IN_PUSI..];
let mut pkt = [0xFFu8; TS_PACKET_SIZE];
pkt[0] = TS_SYNC;
pkt[1] = ((pid >> 8) as u8) & PID_HI_MASK; pkt[2] = (pid & 0xFF) as u8;
pkt[3] = PAYLOAD_FLAG;
pkt[4..4 + remainder.len()].copy_from_slice(remainder);
result.push(pkt);
}
result
}
#[test]
fn chain_t2mi_bbframe_si_pat() {
let pat_section = build_pat_section();
let inner_ts = inner_ts_packet(0x0000, &pat_section);
let bbframe = build_nm_bbframe(&inner_ts);
let t2mi_pkt = build_t2mi_packet(&bbframe);
let outer_ts_pkts = outer_ts_packets(0x0006, &t2mi_pkt);
let mut pump = T2miPump::new(0x0006);
let mut all_events: Vec<_> = Vec::new();
for pkt in &outer_ts_pkts {
all_events.extend(pump.feed_ts(pkt));
}
let events = all_events;
assert_eq!(events.len(), 1, "expected exactly one T2-MI event");
let payload = events[0].payload().expect("payload parse");
let bb = match payload {
AnyPayload::Bbframe(ref bb) => bb,
other => panic!("expected Bbframe, got {other:?}"),
};
assert_eq!(bb.plp_id, 5, "plp_id");
assert!(bb.intl_frame_start, "intl_frame_start");
let bbheader = Bbheader::parse(bb.bbframe).expect("Bbheader::parse on inner BBFrame bytes");
assert_eq!(bbheader.mode, Mode::Normal, "mode");
assert_eq!(bbheader.dfl, 1504, "DFL bits");
assert_eq!(bbheader.syncd, 0, "SYNCD bits");
let dfl_bytes = (bbheader.dfl / 8) as usize;
let data_field_start = BBHEADER_LEN;
let data_field = &bb.bbframe[data_field_start..data_field_start + dfl_bytes];
let inner_pkts: Vec<[u8; NM_UP_SIZE]> =
dvb_bbframe::packet::up_iter(data_field, &bbheader).collect();
assert_eq!(inner_pkts.len(), 1, "expected one inner TS packet");
assert_eq!(inner_pkts[0][0], TS_SYNC, "inner TS sync byte restored");
let mut demux = SiDemux::builder().build();
let events: Vec<_> = demux.feed(&inner_pkts[0]).collect();
assert_eq!(events.len(), 1, "SiDemux must emit one section event");
let table = events[0].table().expect("table dispatch");
match table {
AnyTable::Pat(pat) => {
assert_eq!(pat.transport_stream_id, 0x0001, "TSID");
assert_eq!(pat.entries.len(), 1, "one program entry");
assert_eq!(pat.entries[0].program_number, 1, "program_number");
assert_eq!(pat.entries[0].pid, 0x0100, "PMT PID");
}
other => panic!("expected AnyTable::Pat, got {other:?}"),
}
}
struct Lcg(u64);
impl Lcg {
fn new(seed: u64) -> Self {
Self(seed)
}
fn next_u8(&mut self) -> u8 {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(self.0 >> 33) as u8
}
}
#[test]
fn hostility_t2mi_garbage_feed_ts() {
let mut pump = T2miPump::new(0x0006);
let mut lcg = Lcg::new(0xDEAD_BEEF_CAFE_BABEu64);
for _ in 0..10_000u32 {
let mut pkt = [0u8; TS_PACKET_SIZE];
for b in &mut pkt {
*b = lcg.next_u8();
}
let _: Vec<_> = pump.feed_ts(&pkt).collect();
}
let s = pump.stats();
assert_eq!(s.ts_packets, 10_000);
assert!(
s.malformed_packets > 9_000,
"garbage must be counted malformed (got {})",
s.malformed_packets
);
}
#[test]
fn hostility_t2mi_garbage_feed_raw() {
let mut pump = T2miPump::raw();
let mut lcg = Lcg::new(0xCAFE_F00D_1234_5678u64);
for _ in 0..10_000u32 {
let len = 1 + (lcg.next_u8() as usize % 256);
let data: Vec<u8> = (0..len).map(|_| lcg.next_u8()).collect();
let _: Vec<_> = pump.feed_raw(&data).collect();
}
let s = pump.stats();
assert!(
s.t2mi_packets + s.crc_failures + s.malformed_packets > 0,
"garbage feed_raw must move some counter; got stats: {s:?}"
);
}
#[test]
fn hostility_t2mi_truncation_feed_raw() {
let pat_section = build_pat_section();
let inner_ts = inner_ts_packet(0x0000, &pat_section);
let bbframe = build_nm_bbframe(&inner_ts);
let t2mi_pkt = build_t2mi_packet(&bbframe);
for len in 0..=t2mi_pkt.len() {
let mut pump = T2miPump::raw();
let _: Vec<_> = pump.feed_raw(&t2mi_pkt[..len]).collect();
}
}