#![cfg(feature = "ts")]
use dvb_common::crc32_mpeg2;
use dvb_t2mi::payload::AnyPayload;
use dvb_t2mi::pump::{Stats, T2miPump};
const TS_SYNC: u8 = 0x47;
const PUSI_MASK: u8 = 0x40;
const PID_HI_MASK: u8 = 0x1F;
const PAYLOAD_FLAG: u8 = 0x10;
fn make_t2mi_packet(packet_type: u8, payload: &[u8]) -> Vec<u8> {
let payload_len_bits = (payload.len() * 8) as u16;
let mut pkt = Vec::with_capacity(6 + payload.len() + 4);
pkt.push(packet_type);
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
}
fn ts_packet(pid: u16, t2mi_data: &[u8], pusi: bool, pointer_field: u8) -> [u8; 188] {
let mut pkt = [0xFFu8; 188];
pkt[0] = TS_SYNC;
pkt[1] = if pusi { PUSI_MASK } else { 0 };
pkt[1] |= ((pid >> 8) as u8) & PID_HI_MASK;
pkt[2] = (pid & 0xFF) as u8;
pkt[3] = PAYLOAD_FLAG;
if pusi {
pkt[4] = pointer_field;
let start = 5 + pointer_field as usize;
assert!(
start + t2mi_data.len() <= 188,
"T2-MI data too large for one TS packet"
);
pkt[start..start + t2mi_data.len()].copy_from_slice(t2mi_data);
} else {
let start = 4;
assert!(
start + t2mi_data.len() <= 188,
"T2-MI data too large for one TS packet"
);
pkt[start..start + t2mi_data.len()].copy_from_slice(t2mi_data);
}
pkt
}
#[test]
fn ts_valid_packet_emits_one_event_with_typed_payload() {
let bbframe_payload = [0x00u8, 0x05, 0x00]; let t2mi = make_t2mi_packet(0x00, &bbframe_payload);
let pkt = ts_packet(0x0006, &t2mi, true, 0);
let mut pump = T2miPump::new(0x0006);
let events: Vec<_> = pump.feed_ts(&pkt).collect();
assert_eq!(events.len(), 1);
assert_eq!(events[0].packet_type(), 0x00);
let payload = events[0].payload().expect("payload parse");
match payload {
AnyPayload::Bbframe(bb) => assert_eq!(bb.plp_id, 5),
other => panic!("expected Bbframe, got {other:?}"),
}
let s = pump.stats();
assert_eq!(s.ts_packets, 1);
assert_eq!(s.t2mi_packets, 1);
assert_eq!(s.crc_failures, 0);
assert_eq!(s.malformed_packets, 0);
}
#[test]
fn corrupted_crc_produces_no_event() {
let payload = [0x00u8, 0x00, 0x00];
let mut t2mi = make_t2mi_packet(0x00, &payload);
*t2mi.last_mut().unwrap() ^= 0xFF;
let pkt = ts_packet(0x0006, &t2mi, true, 0);
let mut pump = T2miPump::new(0x0006);
let events: Vec<_> = pump.feed_ts(&pkt).collect();
assert_eq!(events.len(), 0, "corrupted packet must not emit");
assert_eq!(pump.stats().crc_failures, 1);
assert_eq!(pump.stats().t2mi_packets, 1);
}
#[test]
fn feed_raw_packet_split_yields_one_event() {
let ts_payload = [0x00u8; 11];
let t2mi = make_t2mi_packet(0x20, &ts_payload);
let (first, second) = t2mi.split_at(6);
let mut pump = T2miPump::raw();
let ev1: Vec<_> = pump.feed_raw(first).collect();
assert_eq!(ev1.len(), 0, "first half: no complete packet yet");
let ev2: Vec<_> = pump.feed_raw(second).collect();
assert_eq!(ev2.len(), 1, "second half: completes the packet");
let s = pump.stats();
assert_eq!(s.t2mi_packets, 1);
assert_eq!(s.crc_failures, 0);
assert_eq!(s.malformed_packets, 0);
}
#[test]
fn garbage_ts_packet_counted_no_panic() {
let mut pump = T2miPump::new(0x0006);
let garbage = [0x00u8; 188]; let events: Vec<_> = pump.feed_ts(&garbage).collect();
assert_eq!(events.len(), 0);
assert_eq!(pump.stats().malformed_packets, 1);
assert_eq!(pump.stats().ts_packets, 1);
}
#[test]
fn wrong_pid_packet_ignored_no_stats_movement() {
let payload = [0x00u8, 0x00, 0x00];
let t2mi = make_t2mi_packet(0x00, &payload);
let pkt = ts_packet(0x0200, &t2mi, true, 0);
let mut pump = T2miPump::new(0x0006);
let events: Vec<_> = pump.feed_ts(&pkt).collect();
assert_eq!(events.len(), 0);
assert_eq!(
pump.stats(),
Stats {
ts_packets: 1,
t2mi_packets: 0,
crc_failures: 0,
malformed_packets: 0,
}
);
}
#[test]
fn stats_default_is_zero() {
let pump = T2miPump::new(0x0100);
assert_eq!(pump.stats(), Stats::default());
}
#[test]
fn event_header_and_payload_agree() {
let l1_payload = [0x07u8, 0x00]; let t2mi = make_t2mi_packet(0x10, &l1_payload);
let pkt = ts_packet(0x0006, &t2mi, true, 0);
let mut pump = T2miPump::new(0x0006);
let events: Vec<_> = pump.feed_ts(&pkt).collect();
assert_eq!(events.len(), 1);
let hdr = events[0].header().expect("header");
assert_eq!(hdr.packet_type as u8, 0x10);
assert_eq!(hdr.payload_len_bits, 16);
let payload = events[0].payload().expect("payload");
assert!(
matches!(payload, AnyPayload::L1Current(_)),
"expected L1Current, got {payload:?}"
);
}
#[test]
fn two_t2mi_packets_in_one_ts_payload_both_emit() {
let p1 = make_t2mi_packet(0x00, &[0x01u8, 0x00, 0x00]); let p2 = make_t2mi_packet(0x00, &[0x02u8, 0x00, 0x00]);
let mut combined = Vec::new();
combined.extend_from_slice(&p1);
combined.extend_from_slice(&p2);
let pkt = ts_packet(0x0006, &combined, true, 0);
let mut pump = T2miPump::new(0x0006);
let events: Vec<_> = pump.feed_ts(&pkt).collect();
assert_eq!(events.len(), 2, "both packets must emit");
assert_eq!(pump.stats().t2mi_packets, 2);
}
#[test]
fn truncated_ts_input_counted_no_panic() {
let mut pump = T2miPump::new(0x0006);
let truncated = [0x47u8; 10]; let events: Vec<_> = pump.feed_ts(&truncated).collect();
assert_eq!(events.len(), 0, "truncated packet must not emit");
assert_eq!(pump.stats().ts_packets, 1);
assert_eq!(pump.stats().malformed_packets, 1);
}