use core::time::Duration;
use broadcast_common::Serialize;
use dvb_si::descriptors::any::DescriptorLoop;
use dvb_si::tables::eit::{EitKind, EitSection};
use dvb_si::tables::nit::{NitKind, NitSection};
use dvb_si::tables::pat::{PatEntry, PatSection};
use dvb_si::tables::pmt::{PmtSection, PmtStream};
use dvb_si::tables::rst::RstSection;
use dvb_si::tables::sdt::{SdtKind, SdtSection};
use dvb_si::tables::tdt::TdtSection;
use mpeg_ts::mux::SectionPacketiser;
use mpeg_ts::ts::{TS_PACKET_SIZE, TsHeader};
use crate::{Config, ConformanceMonitor, Indicator};
const PID_PAT: u16 = 0x0000;
const PID_CAT: u16 = 0x0001;
const PID_NIT: u16 = 0x0010;
const PID_SDT_BAT: u16 = 0x0011;
const PID_EIT: u16 = 0x0012;
const PID_RST: u16 = 0x0013;
const PID_TDT_TOT: u16 = 0x0014;
const PID_NULL: u16 = 0x1FFF;
fn ms(millis: u64) -> Duration {
Duration::from_millis(millis)
}
fn secs(seconds: u64) -> Duration {
Duration::from_secs(seconds)
}
fn make_ts_packet(
pid: u16,
cc: u8,
pusi: bool,
has_adaptation: bool,
adaptation: &[u8],
payload: &[u8],
) -> [u8; TS_PACKET_SIZE] {
let mut pkt = [0xFFu8; TS_PACKET_SIZE];
let header = TsHeader {
tei: false,
pusi,
pid,
scrambling: 0,
has_adaptation: has_adaptation || !adaptation.is_empty(),
has_payload: !payload.is_empty(),
continuity_counter: cc & 0x0F,
};
header.serialize_into(&mut pkt[..4]).unwrap();
let mut pos = 4usize;
if has_adaptation || !adaptation.is_empty() {
let af_len = adaptation.len() as u8;
pkt[pos] = af_len;
pos += 1;
if af_len > 0 {
pkt[pos..pos + adaptation.len()].copy_from_slice(adaptation);
pos += adaptation.len();
}
}
if !payload.is_empty() {
pkt[pos..pos + payload.len().min(TS_PACKET_SIZE - pos)]
.copy_from_slice(&payload[..payload.len().min(TS_PACKET_SIZE - pos)]);
}
pkt
}
fn make_ts_packet_with_tei(pid: u16, cc: u8) -> [u8; TS_PACKET_SIZE] {
let mut pkt = [0xFFu8; TS_PACKET_SIZE];
let header = TsHeader {
tei: true,
pusi: false,
pid,
scrambling: 0,
has_adaptation: false,
has_payload: false,
continuity_counter: cc & 0x0F,
};
header.serialize_into(&mut pkt[..4]).unwrap();
pkt
}
fn make_ts_packet_with_scrambling(pid: u16, cc: u8, scrambling: u8) -> [u8; TS_PACKET_SIZE] {
let mut pkt = [0xFFu8; TS_PACKET_SIZE];
let header = TsHeader {
tei: false,
pusi: false,
pid,
scrambling,
has_adaptation: false,
has_payload: true,
continuity_counter: cc & 0x0F,
};
header.serialize_into(&mut pkt[..4]).unwrap();
pkt[4] = 0xAB; pkt
}
fn encode_pcr(base: u64, extension: u16) -> [u8; 6] {
let mut out = [0u8; 6];
out[0] = (base >> 25) as u8;
out[1] = (base >> 17) as u8;
out[2] = (base >> 9) as u8;
out[3] = (base >> 1) as u8;
out[4] = ((base & 1) << 7) as u8 | 0x7E | ((extension >> 8) & 1) as u8;
out[5] = (extension & 0xFF) as u8;
out
}
fn make_pcr_adaptation(
pcr_base: u64,
pcr_extension: u16,
discontinuity: bool,
no_payload: bool,
) -> Vec<u8> {
let flags: u8 = 0x10 | if discontinuity { 0x80 } else { 0x00 };
let pcr_bytes = encode_pcr(pcr_base, pcr_extension);
let data_len: usize = 7;
let af_data_len = if no_payload {
183usize
} else {
data_len
};
let mut af = vec![0xFF; af_data_len];
af[0] = flags;
af[1..7].copy_from_slice(&pcr_bytes);
af
}
fn build_pat_section(program_map_pids: &[(u16, u16)]) -> Vec<u8> {
let pat = PatSection {
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
entries: program_map_pids
.iter()
.map(|&(pn, pid)| PatEntry {
program_number: pn,
pid,
})
.collect(),
};
let mut buf = vec![0u8; pat.serialized_len()];
pat.serialize_into(&mut buf).unwrap();
buf
}
fn build_pmt_section(program_number: u16, pcr_pid: u16, es_pids: &[u16]) -> Vec<u8> {
let pmt = PmtSection::new(
program_number,
0,
true,
0,
0,
pcr_pid,
Default::default(),
es_pids
.iter()
.map(|&pid| PmtStream {
stream_type: dvb_si::tables::pmt::StreamType::Mpeg2Video,
elementary_pid: pid,
es_info: Default::default(),
})
.collect(),
);
let mut buf = vec![0u8; pmt.serialized_len()];
pmt.serialize_into(&mut buf).unwrap();
buf
}
fn packetise_section(pid: u16, section: &[u8]) -> Vec<[u8; TS_PACKET_SIZE]> {
let mut pktizer = SectionPacketiser::new(pid);
pktizer.packetise(&[section])
}
fn feed_all(
monitor: &mut ConformanceMonitor,
packets: &[[u8; TS_PACKET_SIZE]],
base_t: Duration,
delta: Duration,
) -> Vec<crate::ConformanceEvent> {
let mut all = Vec::new();
for (i, pkt) in packets.iter().enumerate() {
let t = base_t + delta * i as u32;
let events = monitor.feed(pkt, t);
all.extend(events.to_vec());
}
all
}
fn has_indicator(events: &[crate::ConformanceEvent], indicator: Indicator) -> bool {
events.iter().any(|e| e.indicator == indicator)
}
fn acquire_sync(monitor: &mut ConformanceMonitor) {
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, false, false, &[], &[]);
monitor.feed(&pkt, ms(i as u64));
}
}
fn make_pes_header_with_pts(stream_id: u8, pes_packet_length: u16) -> Vec<u8> {
vec![
0x00, 0x00, 0x01, stream_id,
(pes_packet_length >> 8) as u8,
(pes_packet_length & 0xFF) as u8,
0x80,
0x80,
5,
0x21, 0x00, 0x01, 0x00, 0x01, ]
}
fn make_pes_header_without_pts(stream_id: u8, pes_packet_length: u16) -> Vec<u8> {
vec![
0x00,
0x00,
0x01,
stream_id,
(pes_packet_length >> 8) as u8,
(pes_packet_length & 0xFF) as u8,
0x80, 0x00, 0, ]
}
fn setup_monitor_with_es(monitor: &mut ConformanceMonitor, es_pid: u16) -> u16 {
let pmt_pid: u16 = 0x0100;
acquire_sync(monitor);
let pat_section = build_pat_section(&[(1, pmt_pid)]);
let pat_packets = packetise_section(PID_PAT, &pat_section);
feed_all(monitor, &pat_packets, ms(5), ms(1));
let pmt_section = build_pmt_section(1, 0x1FFF, &[es_pid]);
let pmt_packets = packetise_section(pmt_pid, &pmt_section);
feed_all(monitor, &pmt_packets, ms(10), ms(1));
pmt_pid
}
#[test]
fn sync_byte_error_trips_on_bad_sync() {
let mut monitor = ConformanceMonitor::new();
let mut pkt = make_ts_packet(PID_PAT, 0, true, false, &[], &[0x00]);
pkt[0] = 0x00;
let events = monitor.feed(&pkt, ms(0));
assert!(has_indicator(events, Indicator::SyncByteError));
}
#[test]
fn sync_byte_error_absent_on_good_sync() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..6 {
let pkt = make_ts_packet(0x100, i, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(i as u64));
assert!(!has_indicator(events, Indicator::SyncByteError));
}
}
#[test]
fn ts_sync_loss_after_bad_run_then_reacquire() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, false, false, &[], &[]);
monitor.feed(&pkt, ms(i as u64));
}
assert!(monitor.stats().in_sync);
let mut bad = [0u8; TS_PACKET_SIZE];
bad[0] = 0x00;
let events1 = monitor.feed(&bad, ms(5));
assert!(!has_indicator(events1, Indicator::TsSyncLoss));
let events2 = monitor.feed(&bad, ms(6));
assert!(has_indicator(events2, Indicator::TsSyncLoss));
assert!(!monitor.stats().in_sync);
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, (5 + i) & 0x0F, false, false, &[], &[]);
monitor.feed(&pkt, ms(7 + i as u64));
}
assert!(monitor.stats().in_sync);
}
#[test]
fn ts_sync_loss_not_emitted_while_in_sync() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..10 {
let pkt = make_ts_packet(0x100, i, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(i as u64));
assert!(!has_indicator(events, Indicator::TsSyncLoss));
}
}
#[test]
fn cc_error_trips_on_jump() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let init = make_ts_packet(0x200, 0, false, false, &[], &[0xAB]);
monitor.feed(&init, ms(5));
let ok = make_ts_packet(0x200, 1, false, false, &[], &[0xAB]);
assert!(
!has_indicator(monitor.feed(&ok, ms(6)), Indicator::ContinuityCountError),
"a correct +1 increment on a payload packet must not trip"
);
let jump = make_ts_packet(0x200, 5, false, false, &[], &[0xAB]);
let events = monitor.feed(&jump, ms(7));
assert!(has_indicator(events, Indicator::ContinuityCountError));
}
#[test]
fn cc_correct_increment_no_error() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, true, false, &[], &[0xAB]);
monitor.feed(&pkt, ms(i as u64));
}
let pkt = make_ts_packet(0x100, 5, true, false, &[], &[0xAB]);
let events = monitor.feed(&pkt, ms(5));
assert!(!has_indicator(events, Indicator::ContinuityCountError));
}
#[test]
fn cc_single_duplicate_is_legal() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, true, false, &[], &[0xAB]);
monitor.feed(&pkt, ms(i as u64));
}
let pkt = make_ts_packet(0x100, 4, true, false, &[], &[0xAB]);
let events = monitor.feed(&pkt, ms(5));
assert!(!has_indicator(events, Indicator::ContinuityCountError));
}
#[test]
fn cc_same_cc_different_payload_is_error() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, true, false, &[], &[0xAB]);
monitor.feed(&pkt, ms(i as u64));
}
let pkt = make_ts_packet(0x100, 4, true, false, &[], &[0xCD]);
let events = monitor.feed(&pkt, ms(5));
assert!(
has_indicator(events, Indicator::ContinuityCountError),
"same CC with a genuinely different payload must raise Continuity_count_error"
);
}
#[test]
fn cc_double_duplicate_is_error() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, true, false, &[], &[0xAB]);
monitor.feed(&pkt, ms(i as u64));
}
let pkt1 = make_ts_packet(0x100, 4, true, false, &[], &[0xAB]);
let events1 = monitor.feed(&pkt1, ms(5));
assert!(
!has_indicator(events1, Indicator::ContinuityCountError),
"the first legal duplicate must not raise an error"
);
let pkt2 = make_ts_packet(0x100, 4, true, false, &[], &[0xAB]);
let events2 = monitor.feed(&pkt2, ms(6));
assert!(
has_indicator(events2, Indicator::ContinuityCountError),
"a second consecutive duplicate of the same CC must raise Continuity_count_error"
);
}
#[test]
fn cc_no_payload_holds_cc() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, false, false, &[], &[]);
monitor.feed(&pkt, ms(i as u64));
}
let pkt = make_ts_packet(0x100, 4, false, true, &[0x00], &[]);
let events = monitor.feed(&pkt, ms(5));
assert!(!has_indicator(events, Indicator::ContinuityCountError));
}
#[test]
fn cc_discontinuity_indicator_skips_check() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, false, false, &[], &[]);
monitor.feed(&pkt, ms(i as u64));
}
let af = [0x80]; let pkt = make_ts_packet(0x100, 7, false, true, &af, &[]);
let events = monitor.feed(&pkt, ms(5));
assert!(!has_indicator(events, Indicator::ContinuityCountError));
}
#[test]
fn cc_null_pid_skipped() {
let mut monitor = ConformanceMonitor::new();
for i in 0u8..5 {
let pkt = make_ts_packet(0x100, i, false, false, &[], &[]);
monitor.feed(&pkt, ms(i as u64));
}
let pkt = make_ts_packet(PID_NULL, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(5));
assert!(!has_indicator(events, Indicator::ContinuityCountError));
}
#[test]
fn pat_error_wrong_table_id() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_pat_section(&[(1, 0x100)]);
section[0] = 0x01;
let packets = packetise_section(PID_PAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::PatError2));
}
#[test]
fn pat_error_scrambling() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut pkt = make_ts_packet(PID_PAT, 0, true, false, &[], &[0x00]);
pkt[3] = (pkt[3] & !0xC0) | 0x40;
let events = monitor.feed(&pkt, ms(5));
assert!(has_indicator(events, Indicator::PatError2));
}
#[test]
fn pat_error_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(600));
assert!(has_indicator(events, Indicator::PatError2));
}
#[test]
fn pat_compliant_no_error() {
let mut monitor = ConformanceMonitor::new();
let section = build_pat_section(&[(1, 0x100)]);
let packets = packetise_section(PID_PAT, §ion);
acquire_sync(&mut monitor);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::PatError2));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(20));
assert!(!has_indicator(events, Indicator::PatError2));
}
#[test]
fn pmt_error_timeout() {
let mut monitor = ConformanceMonitor::new();
let section = build_pat_section(&[(1, 0x100)]);
let packets = packetise_section(PID_PAT, §ion);
acquire_sync(&mut monitor);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(600));
assert!(has_indicator(events, Indicator::PmtError2));
}
#[test]
fn pmt_error_scrambling() {
let mut monitor = ConformanceMonitor::new();
let section = build_pat_section(&[(1, 0x100)]);
let packets = packetise_section(PID_PAT, §ion);
acquire_sync(&mut monitor);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let mut pkt = make_ts_packet(0x100, 5, false, false, &[], &[]);
pkt[3] = (pkt[3] & !0xC0) | 0x40; let events = monitor.feed(&pkt, ms(10));
assert!(has_indicator(events, Indicator::PmtError2));
}
#[test]
fn pmt_compliant_no_error() {
let mut monitor = ConformanceMonitor::new();
let pat_section = build_pat_section(&[(1, 0x100)]);
let pat_packets = packetise_section(PID_PAT, &pat_section);
acquire_sync(&mut monitor);
feed_all(&mut monitor, &pat_packets, ms(5), ms(1));
let pmt_section = build_pmt_section(1, 0x1FFF, &[]);
let pmt_packets = packetise_section(0x100, &pmt_section);
let events = feed_all(&mut monitor, &pmt_packets, ms(10), ms(1));
assert!(!has_indicator(&events, Indicator::PmtError2));
}
#[test]
fn pid_error_timeout() {
let config = Config {
pid_error_period: secs(1),
..Config::default()
};
let mut monitor = ConformanceMonitor::with_config(config);
let pat_section = build_pat_section(&[(1, 0x100)]);
let pat_packets = packetise_section(PID_PAT, &pat_section);
acquire_sync(&mut monitor);
feed_all(&mut monitor, &pat_packets, ms(5), ms(1));
let pmt_section = build_pmt_section(1, 0x1FFF, &[0x200]);
let pmt_packets = packetise_section(0x100, &pmt_section);
feed_all(&mut monitor, &pmt_packets, ms(10), ms(1));
let pkt = make_ts_packet(0x300, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(2));
assert!(has_indicator(events, Indicator::PidError));
}
#[test]
fn pid_compliant_no_error() {
let config = Config {
pid_error_period: secs(5),
..Config::default()
};
let mut monitor = ConformanceMonitor::with_config(config);
let pat_section = build_pat_section(&[(1, 0x100)]);
let pat_packets = packetise_section(PID_PAT, &pat_section);
acquire_sync(&mut monitor);
feed_all(&mut monitor, &pat_packets, ms(5), ms(1));
let pmt_section = build_pmt_section(1, 0x1FFF, &[0x200]);
let pmt_packets = packetise_section(0x100, &pmt_section);
feed_all(&mut monitor, &pmt_packets, ms(10), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(500));
assert!(!has_indicator(events, Indicator::PidError));
}
#[test]
fn pat_timeout_emits_once_not_per_packet() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let e1 = monitor.feed(&pkt, ms(600)).to_vec();
let e2 = monitor.feed(&pkt, ms(700)).to_vec();
let e3 = monitor.feed(&pkt, ms(800)).to_vec();
assert_eq!(
e1.iter()
.filter(|e| e.indicator == Indicator::PatError2)
.count(),
1
);
assert!(!has_indicator(&e2, Indicator::PatError2));
assert!(!has_indicator(&e3, Indicator::PatError2));
}
#[test]
fn other_indicators_suppressed_while_not_in_sync() {
let mut monitor = ConformanceMonitor::new();
let bad = [0u8; TS_PACKET_SIZE]; for i in 0u8..10 {
let events = monitor.feed(&bad, ms(i as u64));
for e in events {
assert!(
e.indicator == Indicator::SyncByteError || e.indicator == Indicator::TsSyncLoss,
"unexpected indicator {:?} while not in sync",
e.indicator,
);
}
}
}
#[test]
fn stats_track_packets_and_events() {
let mut monitor = ConformanceMonitor::new();
assert_eq!(monitor.stats().packets, 0);
assert_eq!(monitor.stats().events, 0);
let bad = [0u8; TS_PACKET_SIZE];
monitor.feed(&bad, ms(0));
assert_eq!(monitor.stats().packets, 1);
assert!(monitor.stats().events >= 1);
}
#[test]
fn transport_error_trips_on_tei_set() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pkt = make_ts_packet_with_tei(0x100, 5);
let events = monitor.feed(&pkt, ms(5));
assert!(has_indicator(events, Indicator::TransportError));
}
#[test]
fn transport_error_absent_on_tei_clear() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pkt = make_ts_packet(0x100, 5, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(5));
assert!(!has_indicator(events, Indicator::TransportError));
}
#[test]
fn crc_error_trips_on_corrupted_pat() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_pat_section(&[(1, 0x100)]);
if section.len() > 12 {
section[10] ^= 0xFF;
}
let packets = packetise_section(PID_PAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::CrcError));
}
#[test]
fn crc_error_absent_on_valid_pat() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_pat_section(&[(1, 0x100)]);
let packets = packetise_section(PID_PAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::CrcError));
}
#[test]
fn pcr_repetition_error_trips_on_large_gap() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pcr_pid: u16 = 0x0200;
let af1 = make_pcr_adaptation(90000, 0, false, true);
let pkt1 = make_ts_packet(pcr_pid, 0, false, true, &af1, &[]);
let af2 = make_pcr_adaptation(180000, 0, false, true);
let pkt2 = make_ts_packet(pcr_pid, 1, false, true, &af2, &[]);
monitor.feed(&pkt1, ms(0));
let events = monitor.feed(&pkt2, ms(150));
assert!(has_indicator(events, Indicator::PcrRepetitionError));
}
#[test]
fn pcr_repetition_error_absent_within_limit() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pcr_pid: u16 = 0x0200;
let af1 = make_pcr_adaptation(90000, 0, false, true);
let pkt1 = make_ts_packet(pcr_pid, 0, false, true, &af1, &[]);
let af2 = make_pcr_adaptation(180000, 0, false, true);
let pkt2 = make_ts_packet(pcr_pid, 1, false, true, &af2, &[]);
monitor.feed(&pkt1, ms(0));
let events = monitor.feed(&pkt2, ms(50));
assert!(!has_indicator(events, Indicator::PcrRepetitionError));
}
#[test]
fn pcr_discontinuity_error_trips_on_large_delta_without_indicator() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pcr_pid: u16 = 0x0200;
let af1 = make_pcr_adaptation(90000, 0, false, true);
let pkt1 = make_ts_packet(pcr_pid, 0, false, true, &af1, &[]);
let af2 = make_pcr_adaptation(360000, 0, false, true);
let pkt2 = make_ts_packet(pcr_pid, 1, false, true, &af2, &[]);
monitor.feed(&pkt1, ms(0));
let events = monitor.feed(&pkt2, ms(50));
assert!(has_indicator(events, Indicator::PcrDiscontinuityError));
}
#[test]
fn pcr_discontinuity_error_suppressed_with_indicator() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pcr_pid: u16 = 0x0200;
let af1 = make_pcr_adaptation(90000, 0, false, true);
let pkt1 = make_ts_packet(pcr_pid, 0, false, true, &af1, &[]);
let af2 = make_pcr_adaptation(360000, 0, true, true); let pkt2 = make_ts_packet(pcr_pid, 1, false, true, &af2, &[]);
monitor.feed(&pkt1, ms(0));
let events = monitor.feed(&pkt2, ms(50));
assert!(!has_indicator(events, Indicator::PcrDiscontinuityError));
}
#[test]
fn pcr_discontinuity_error_absent_within_range() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pcr_pid: u16 = 0x0200;
let af1 = make_pcr_adaptation(90000, 0, false, true);
let pkt1 = make_ts_packet(pcr_pid, 0, false, true, &af1, &[]);
let af2 = make_pcr_adaptation(90000 + 4500, 0, false, true);
let pkt2 = make_ts_packet(pcr_pid, 1, false, true, &af2, &[]);
monitor.feed(&pkt1, ms(0));
let events = monitor.feed(&pkt2, ms(50));
assert!(!has_indicator(events, Indicator::PcrDiscontinuityError));
}
#[test]
fn pts_error_trips_on_large_gap() {
let mut monitor = ConformanceMonitor::new();
let es_pid: u16 = 0x0200;
setup_monitor_with_es(&mut monitor, es_pid);
let pes1 = make_pes_header_with_pts(0xE0, 10);
let pkt1 = make_ts_packet(es_pid, 0, true, false, &[], &pes1);
let events1 = monitor.feed(&pkt1, ms(20));
assert!(!has_indicator(events1, Indicator::PtsError));
let pes2 = make_pes_header_with_pts(0xE0, 10);
let pkt2 = make_ts_packet(es_pid, 1, true, false, &[], &pes2);
let events2 = monitor.feed(&pkt2, ms(800));
assert!(has_indicator(events2, Indicator::PtsError));
}
#[test]
fn pts_error_absent_within_limit() {
let mut monitor = ConformanceMonitor::new();
let es_pid: u16 = 0x0200;
setup_monitor_with_es(&mut monitor, es_pid);
let pes1 = make_pes_header_with_pts(0xE0, 10);
let pkt1 = make_ts_packet(es_pid, 0, true, false, &[], &pes1);
monitor.feed(&pkt1, ms(20));
let pes2 = make_pes_header_with_pts(0xE0, 10);
let pkt2 = make_ts_packet(es_pid, 1, true, false, &[], &pes2);
let events = monitor.feed(&pkt2, ms(500));
assert!(!has_indicator(events, Indicator::PtsError));
}
#[test]
fn pts_error_not_armed_without_first_pts() {
let mut monitor = ConformanceMonitor::new();
let es_pid: u16 = 0x0200;
setup_monitor_with_es(&mut monitor, es_pid);
let pes_no_pts = make_pes_header_without_pts(0xE0, 10);
let pkt1 = make_ts_packet(es_pid, 0, true, false, &[], &pes_no_pts);
monitor.feed(&pkt1, ms(20));
let pes2 = make_pes_header_with_pts(0xE0, 10);
let pkt2 = make_ts_packet(es_pid, 1, true, false, &[], &pes2);
let events = monitor.feed(&pkt2, ms(800));
assert!(!has_indicator(events, Indicator::PtsError));
}
#[test]
fn cat_error_wrong_table_id_on_pid_cat() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_pat_section(&[(1, 0x100)]);
section[0] = 0x02;
let packets = packetise_section(PID_CAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::CatError));
}
#[test]
fn cat_error_scrambled_without_cat() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let pkt = make_ts_packet_with_scrambling(0x0100, 0, 0x03);
let events = monitor.feed(&pkt, ms(10));
assert!(has_indicator(events, Indicator::CatError));
}
fn build_valid_cat_section() -> Vec<u8> {
let mut cat = vec![
0x01, 0xB0, 0x09, 0xFF, 0xFF, 0xC1, 0x00, 0x00, ];
let crc = broadcast_common::crc32_mpeg2::compute(&cat);
cat.extend_from_slice(&crc.to_be_bytes());
cat
}
#[test]
fn cat_error_absent_when_cat_seen_then_scrambled() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let packets = packetise_section(PID_CAT, &build_valid_cat_section());
let cat_events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(
!has_indicator(&cat_events, Indicator::CrcError),
"a CRC-valid CAT must not raise CRC_error"
);
assert!(
!has_indicator(&cat_events, Indicator::CatError),
"a valid CAT on PID 0x0001 must not raise CAT_error"
);
let pkt = make_ts_packet_with_scrambling(0x0100, 0, 0x03);
let events = monitor.feed(&pkt, ms(10));
assert!(
!has_indicator(events, Indicator::CatError),
"CAT_error must not fire for a scrambled packet once a CAT has been seen"
);
}
fn build_sdt_actual_section() -> Vec<u8> {
let sdt = SdtSection {
kind: SdtKind::Actual,
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
original_network_id: 1,
services: vec![],
};
let mut buf = vec![0u8; sdt.serialized_len()];
sdt.serialize_into(&mut buf).unwrap();
buf
}
fn build_nit_actual_section() -> Vec<u8> {
let nit = NitSection {
kind: NitKind::Actual,
network_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
network_descriptors: DescriptorLoop::new(&[]),
transport_streams: vec![],
};
let mut buf = vec![0u8; nit.serialized_len()];
nit.serialize_into(&mut buf).unwrap();
buf
}
fn build_eit_pf_actual_section() -> Vec<u8> {
let eit = EitSection {
kind: EitKind::PresentFollowingActual,
table_id: dvb_si::tables::eit::TABLE_ID_PF_ACTUAL,
service_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
transport_stream_id: 1,
original_network_id: 1,
segment_last_section_number: 0,
last_table_id: dvb_si::tables::eit::TABLE_ID_PF_ACTUAL,
events: vec![],
};
let mut buf = vec![0u8; eit.serialized_len()];
eit.serialize_into(&mut buf).unwrap();
buf
}
fn build_tdt_section() -> Vec<u8> {
let tdt = TdtSection::new([0x00, 0x00, 0x00, 0x00, 0x00]);
let mut buf = vec![0u8; tdt.serialized_len()];
tdt.serialize_into(&mut buf).unwrap();
buf
}
fn build_rst_section() -> Vec<u8> {
let rst = RstSection { entries: vec![] };
let mut buf = vec![0u8; rst.serialized_len()];
rst.serialize_into(&mut buf).unwrap();
buf
}
#[test]
fn si_repetition_sdt_trips_on_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_sdt_actual_section();
let packets = packetise_section(PID_SDT_BAT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(3));
assert!(has_indicator(events, Indicator::SiRepetitionError));
let si_events: Vec<_> = events
.iter()
.filter(|e| e.indicator == Indicator::SiRepetitionError)
.collect();
assert_eq!(si_events.len(), 1);
assert_eq!(si_events[0].pid, Some(PID_SDT_BAT));
assert!(si_events[0].detail.contains("SDT_actual"));
}
#[test]
fn si_repetition_sdt_compliant_within_interval() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_sdt_actual_section();
let packets = packetise_section(PID_SDT_BAT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_SDT_BAT, §ion);
let events = feed_all(&mut monitor, &packets2, ms(1000), ms(1));
assert!(!has_indicator(&events, Indicator::SiRepetitionError));
}
#[test]
fn si_repetition_tdt_trips_on_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_tdt_section();
let packets = packetise_section(PID_TDT_TOT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(35));
assert!(has_indicator(events, Indicator::SiRepetitionError));
let si_events: Vec<_> = events
.iter()
.filter(|e| e.indicator == Indicator::SiRepetitionError)
.collect();
assert_eq!(si_events.len(), 1);
assert_eq!(si_events[0].pid, Some(PID_TDT_TOT));
assert!(si_events[0].detail.contains("TDT"));
}
#[test]
fn si_repetition_tdt_compliant_within_interval() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_tdt_section();
let packets = packetise_section(PID_TDT_TOT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_TDT_TOT, §ion);
let events = feed_all(&mut monitor, &packets2, secs(20), ms(1));
assert!(!has_indicator(&events, Indicator::SiRepetitionError));
}
#[test]
fn si_repetition_nit_trips_on_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_nit_actual_section();
let packets = packetise_section(PID_NIT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(12));
assert!(has_indicator(events, Indicator::SiRepetitionError));
let si_events: Vec<_> = events
.iter()
.filter(|e| e.indicator == Indicator::SiRepetitionError)
.collect();
assert_eq!(si_events.len(), 1);
assert_eq!(si_events[0].pid, Some(PID_NIT));
assert!(si_events[0].detail.contains("NIT_actual"));
}
#[test]
fn si_repetition_nit_compliant_within_interval() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_nit_actual_section();
let packets = packetise_section(PID_NIT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_NIT, §ion);
let events = feed_all(&mut monitor, &packets2, secs(5), ms(1));
assert!(!has_indicator(&events, Indicator::SiRepetitionError));
}
#[test]
fn si_repetition_eit_pf_trips_on_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_eit_pf_actual_section();
let packets = packetise_section(PID_EIT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(3));
assert!(has_indicator(events, Indicator::SiRepetitionError));
let si_events: Vec<_> = events
.iter()
.filter(|e| e.indicator == Indicator::SiRepetitionError)
.collect();
assert_eq!(si_events.len(), 1);
assert_eq!(si_events[0].pid, Some(PID_EIT));
assert!(si_events[0].detail.contains("EIT_P/F_actual"));
}
#[test]
fn si_repetition_eit_pf_compliant_within_interval() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_eit_pf_actual_section();
let packets = packetise_section(PID_EIT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_EIT, §ion);
let events = feed_all(&mut monitor, &packets2, secs(1), ms(1));
assert!(!has_indicator(&events, Indicator::SiRepetitionError));
}
#[test]
fn si_repetition_not_armed_before_first_section() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
for i in 0..5u8 {
let pkt = make_ts_packet(0x200, i, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(60 + i as u64));
assert!(
!has_indicator(events, Indicator::SiRepetitionError),
"SiRepetitionError should not fire before any SI section is seen"
);
}
}
#[test]
fn si_repetition_emits_once_not_per_packet() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_sdt_actual_section();
let packets = packetise_section(PID_SDT_BAT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let e1 = monitor.feed(&pkt, secs(3)).to_vec();
let e2 = monitor.feed(&pkt, secs(4)).to_vec();
let e3 = monitor.feed(&pkt, secs(5)).to_vec();
assert_eq!(
e1.iter()
.filter(|e| e.indicator == Indicator::SiRepetitionError)
.count(),
1
);
assert!(!has_indicator(&e2, Indicator::SiRepetitionError));
assert!(!has_indicator(&e3, Indicator::SiRepetitionError));
}
#[test]
fn nit_error_trips_on_wrong_table_id() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_nit_actual_section();
section[0] = 0x02;
let packets = packetise_section(PID_NIT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::NitError));
}
#[test]
fn nit_error_trips_on_actual_absence_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_nit_actual_section();
let packets = packetise_section(PID_NIT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x0500, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(12));
assert!(has_indicator(events, Indicator::NitError));
}
#[test]
fn nit_error_absent_on_compliant_stream() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_nit_actual_section();
let packets = packetise_section(PID_NIT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::NitError));
let packets2 = packetise_section(PID_NIT, §ion);
let events2 = feed_all(&mut monitor, &packets2, secs(5), ms(1));
assert!(!has_indicator(&events2, Indicator::NitError));
}
#[test]
fn unreferenced_pid_trips_after_persistence_threshold() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let stray_pid: u16 = 0x0500;
let pkt1 = make_ts_packet(stray_pid, 0, false, false, &[], &[]);
monitor.feed(&pkt1, ms(5));
let pkt2 = make_ts_packet(stray_pid, 1, false, false, &[], &[]);
let events = monitor.feed(&pkt2, ms(600));
let unref: Vec<_> = events
.iter()
.filter(|e| e.indicator == Indicator::UnreferencedPid && e.pid == Some(stray_pid))
.collect();
assert_eq!(unref.len(), 1);
}
#[test]
fn unreferenced_pid_absent_within_persistence_threshold() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let stray_pid: u16 = 0x0500;
let pkt1 = make_ts_packet(stray_pid, 0, false, false, &[], &[]);
monitor.feed(&pkt1, ms(5));
let pkt2 = make_ts_packet(stray_pid, 1, false, false, &[], &[]);
let events = monitor.feed(&pkt2, ms(200));
let unref: Vec<_> = events
.iter()
.filter(|e| e.indicator == Indicator::UnreferencedPid && e.pid == Some(stray_pid))
.collect();
assert!(unref.is_empty());
}
#[test]
fn unreferenced_pid_absent_when_referenced_by_pmt() {
let mut monitor = ConformanceMonitor::new();
let es_pid: u16 = 0x0500;
setup_monitor_with_es(&mut monitor, es_pid);
let pkt = make_ts_packet(es_pid, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(2));
assert!(!has_indicator(events, Indicator::UnreferencedPid));
}
#[test]
fn unreferenced_pid_absent_for_well_known_si_pids() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
for &pid in &[PID_NIT, PID_SDT_BAT, PID_EIT, PID_RST, PID_TDT_TOT] {
let pkt1 = make_ts_packet(pid, 0, false, false, &[], &[]);
monitor.feed(&pkt1, ms(5));
let pkt2 = make_ts_packet(pid, 1, false, false, &[], &[]);
let events = monitor.feed(&pkt2, secs(2));
let flagged = events
.iter()
.any(|e| e.indicator == Indicator::UnreferencedPid && e.pid == Some(pid));
assert!(
!flagged,
"well-known SI PID 0x{pid:04X} must never be flagged Unreferenced_PID"
);
}
}
#[test]
fn sdt_error_trips_on_wrong_table_id() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_sdt_actual_section();
section[0] = 0x02;
let packets = packetise_section(PID_SDT_BAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::SdtError));
}
#[test]
fn sdt_error_trips_on_actual_absence_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_sdt_actual_section();
let packets = packetise_section(PID_SDT_BAT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x0500, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(3));
assert!(has_indicator(events, Indicator::SdtError));
}
#[test]
fn sdt_error_absent_on_compliant_stream() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_sdt_actual_section();
let packets = packetise_section(PID_SDT_BAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::SdtError));
let packets2 = packetise_section(PID_SDT_BAT, §ion);
let events2 = feed_all(&mut monitor, &packets2, ms(1000), ms(1));
assert!(!has_indicator(&events2, Indicator::SdtError));
}
#[test]
fn eit_error_trips_on_wrong_table_id() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_eit_pf_actual_section();
section[0] = 0x02;
let packets = packetise_section(PID_EIT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::EitError));
}
#[test]
fn eit_error_schedule_table_id_is_allowed() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_eit_pf_actual_section();
section[0] = 0x55;
let packets = packetise_section(PID_EIT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::EitError));
}
#[test]
fn eit_error_trips_on_pf_actual_absence_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_eit_pf_actual_section();
let packets = packetise_section(PID_EIT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x0500, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(3));
assert!(has_indicator(events, Indicator::EitError));
}
#[test]
fn eit_error_absent_on_compliant_stream() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_eit_pf_actual_section();
let packets = packetise_section(PID_EIT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::EitError));
let packets2 = packetise_section(PID_EIT, §ion);
let events2 = feed_all(&mut monitor, &packets2, secs(1), ms(1));
assert!(!has_indicator(&events2, Indicator::EitError));
}
#[test]
fn rst_error_trips_on_wrong_table_id() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_rst_section();
section[0] = 0x40;
let packets = packetise_section(PID_RST, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::RstError));
}
#[test]
fn rst_error_absent_on_valid_rst() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_rst_section();
let packets = packetise_section(PID_RST, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::RstError));
}
#[test]
fn rst_error_absent_on_stuffing_table_id() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_rst_section();
section[0] = 0x72;
let packets = packetise_section(PID_RST, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::RstError));
}
#[test]
fn tdt_error_trips_on_wrong_table_id() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut section = build_tdt_section();
section[0] = 0x02;
let packets = packetise_section(PID_TDT_TOT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(has_indicator(&events, Indicator::TdtError));
}
#[test]
fn tdt_error_trips_on_absence_timeout() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_tdt_section();
let packets = packetise_section(PID_TDT_TOT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x0500, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, secs(35));
assert!(has_indicator(events, Indicator::TdtError));
}
#[test]
fn tdt_error_absent_on_compliant_stream() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_tdt_section();
let packets = packetise_section(PID_TDT_TOT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events, Indicator::TdtError));
let packets2 = packetise_section(PID_TDT_TOT, §ion);
let events2 = feed_all(&mut monitor, &packets2, secs(20), ms(1));
assert!(!has_indicator(&events2, Indicator::TdtError));
}
fn build_large_pat_section() -> Vec<u8> {
let mut entries = Vec::new();
for i in 0u16..200 {
entries.push(PatEntry {
program_number: i,
pid: 0x0100 + i,
});
}
let pat = PatSection {
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number: 0,
last_section_number: 0,
entries,
};
let mut buf = vec![0u8; pat.serialized_len()];
pat.serialize_into(&mut buf).unwrap();
buf
}
#[test]
fn buffer_error_trips_on_tbsys_overflow() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_large_pat_section();
let section_len = section.len();
assert!(
section_len > 512,
"test precondition: section {section_len} bytes > TBsys 512 bytes"
);
let packets = packetise_section(PID_PAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(1), ms(1));
assert!(
has_indicator(&events, Indicator::BufferError),
"TBsys overflow should fire for a >512 byte section with minimal drain"
);
}
#[test]
fn buffer_error_absent_on_small_sections() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_pat_section(&[(0x0001, 0x0100)]);
let packets = packetise_section(PID_PAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(1), ms(1));
assert!(
!has_indicator(&events, Indicator::BufferError),
"normal PAT section should not overflow TBsys"
);
}
#[test]
fn empty_buffer_error_trips_when_tbsys_never_empties_for_1s() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_large_pat_section();
let section_packets = packetise_section(PID_PAT, §ion);
let section_interval_ms = 5u64;
let mut events = Vec::new();
for section_idx in 0..450u64 {
for (pkt_idx, pkt) in section_packets.iter().enumerate() {
let t = Duration::from_millis(section_idx * section_interval_ms + pkt_idx as u64);
let evts = monitor.feed(pkt, t);
let evts_slice = evts.to_vec();
if !evts_slice.is_empty() {
events.extend(evts_slice);
}
}
}
assert!(
has_indicator(&events, Indicator::EmptyBufferError),
"EmptyBufferError should fire when TBsys never empties for >1 s"
);
}
#[test]
fn empty_buffer_error_absent_when_buffer_empties_normally() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_pat_section(&[(0x0001, 0x0100)]);
let packets = packetise_section(PID_PAT, §ion);
let _events = feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_PAT, §ion);
let events2 = feed_all(&mut monitor, &packets2, ms(600), ms(1));
assert!(
!has_indicator(&events2, Indicator::EmptyBufferError),
"EmptyBufferError should not fire when buffer empties normally"
);
}
#[test]
fn data_delay_error_trips_when_data_lingers_over_1s() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_large_pat_section();
let section_packets = packetise_section(PID_PAT, §ion);
let section_interval_ms = 5u64;
let mut events = Vec::new();
for section_idx in 0..250u64 {
for (pkt_idx, pkt) in section_packets.iter().enumerate() {
let t = Duration::from_millis(section_idx * section_interval_ms + pkt_idx as u64);
let evts = monitor.feed(pkt, t);
let evts_slice = evts.to_vec();
if !evts_slice.is_empty() {
events.extend(evts_slice);
}
}
}
assert!(
has_indicator(&events, Indicator::DataDelayError),
"DataDelayError should fire when data stays in TBsys for >1 s"
);
}
#[test]
fn data_delay_error_absent_with_rapid_drain() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_pat_section(&[(0x0001, 0x0100)]);
let packets = packetise_section(PID_PAT, §ion);
let events = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(
!has_indicator(&events, Indicator::DataDelayError),
"no delay on normal section"
);
}
fn build_nit_actual_section_numbered(section_number: u8) -> Vec<u8> {
let nit = NitSection {
kind: NitKind::Actual,
network_id: 1,
version_number: 0,
current_next_indicator: true,
section_number,
last_section_number: 0,
network_descriptors: DescriptorLoop::new(&[]),
transport_streams: vec![],
};
let mut buf = vec![0u8; nit.serialized_len()];
nit.serialize_into(&mut buf).unwrap();
buf
}
fn build_sdt_actual_section_numbered(section_number: u8) -> Vec<u8> {
let sdt = SdtSection {
kind: SdtKind::Actual,
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number,
last_section_number: 0,
original_network_id: 1,
services: vec![],
};
let mut buf = vec![0u8; sdt.serialized_len()];
sdt.serialize_into(&mut buf).unwrap();
buf
}
fn build_eit_pf_actual_section_keyed(
section_number: u8,
service_id: u16,
ts_id: u16,
onw_id: u16,
) -> Vec<u8> {
let eit = EitSection {
kind: EitKind::PresentFollowingActual,
table_id: dvb_si::tables::eit::TABLE_ID_PF_ACTUAL,
service_id,
version_number: 0,
current_next_indicator: true,
section_number,
last_section_number: 0,
transport_stream_id: ts_id,
original_network_id: onw_id,
segment_last_section_number: 0,
last_table_id: dvb_si::tables::eit::TABLE_ID_PF_ACTUAL,
events: vec![],
};
let mut buf = vec![0u8; eit.serialized_len()];
eit.serialize_into(&mut buf).unwrap();
buf
}
fn build_nit_other_section(section_number: u8) -> Vec<u8> {
let nit = NitSection {
kind: NitKind::Other,
network_id: 1,
version_number: 0,
current_next_indicator: true,
section_number,
last_section_number: 0,
network_descriptors: DescriptorLoop::new(&[]),
transport_streams: vec![],
};
let mut buf = vec![0u8; nit.serialized_len()];
nit.serialize_into(&mut buf).unwrap();
buf
}
fn build_sdt_other_section(section_number: u8) -> Vec<u8> {
let sdt = SdtSection {
kind: SdtKind::Other,
transport_stream_id: 1,
version_number: 0,
current_next_indicator: true,
section_number,
last_section_number: 0,
original_network_id: 1,
services: vec![],
};
let mut buf = vec![0u8; sdt.serialized_len()];
sdt.serialize_into(&mut buf).unwrap();
buf
}
fn build_eit_other_section(section_number: u8) -> Vec<u8> {
let eit = EitSection {
kind: EitKind::PresentFollowingOther,
table_id: dvb_si::tables::eit::TABLE_ID_PF_OTHER,
service_id: 1,
version_number: 0,
current_next_indicator: true,
section_number,
last_section_number: 0,
transport_stream_id: 1,
original_network_id: 1,
segment_last_section_number: 0,
last_table_id: dvb_si::tables::eit::TABLE_ID_PF_OTHER,
events: vec![],
};
let mut buf = vec![0u8; eit.serialized_len()];
eit.serialize_into(&mut buf).unwrap();
buf
}
#[test]
fn si_min_gap_trips_when_same_section_number_too_close() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_sdt_actual_section_numbered(0);
let packets = packetise_section(PID_SDT_BAT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_SDT_BAT, §ion);
let events = feed_all(&mut monitor, &packets2, ms(10), ms(1));
assert!(has_indicator(&events, Indicator::SiMinGapError));
}
#[test]
fn si_min_gap_absent_when_25ms_or_more() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let section = build_sdt_actual_section_numbered(0);
let packets = packetise_section(PID_SDT_BAT, §ion);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_SDT_BAT, §ion);
let events = feed_all(&mut monitor, &packets2, ms(30), ms(1));
assert!(!has_indicator(&events, Indicator::SiMinGapError));
}
#[test]
fn si_min_gap_multi_section_no_false_positive() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec0 = build_nit_actual_section_numbered(0);
let p0 = packetise_section(PID_NIT, &sec0);
feed_all(&mut monitor, &p0, ms(5), ms(1));
let sec1 = build_nit_actual_section_numbered(1);
let p1 = packetise_section(PID_NIT, &sec1);
let events = feed_all(&mut monitor, &p1, ms(6), ms(1));
assert!(!has_indicator(&events, Indicator::SiMinGapError));
}
#[test]
fn other_repetition_trips_on_interval_exceeded() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec = build_sdt_other_section(0);
let packets = packetise_section(PID_SDT_BAT, &sec);
let events1 = feed_all(&mut monitor, &packets, ms(5), ms(1));
assert!(!has_indicator(&events1, Indicator::SdtOtherError));
let packets2 = packetise_section(PID_SDT_BAT, &sec);
let events2 = feed_all(&mut monitor, &packets2, secs(15), ms(1));
assert!(has_indicator(&events2, Indicator::SdtOtherError));
}
#[test]
fn other_repetition_silent_when_within_interval() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec = build_nit_other_section(0);
let packets = packetise_section(PID_NIT, &sec);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_NIT, &sec);
let events = feed_all(&mut monitor, &packets2, secs(5), ms(1));
assert!(!has_indicator(&events, Indicator::NitOtherError));
}
#[test]
fn other_repetition_no_false_positive_if_table_absent() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec = build_nit_actual_section_numbered(0);
for i in 0..3 {
let packets = packetise_section(PID_NIT, &sec);
let events = feed_all(&mut monitor, &packets, secs(i as u64), ms(1));
assert!(!has_indicator(&events, Indicator::NitOtherError));
}
}
#[test]
fn eit_pf_error_trips_when_only_section_0_present() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec0 = build_eit_pf_actual_section_keyed(0, 0x42, 1, 1);
let packets = packetise_section(PID_EIT, &sec0);
let mut all_events = feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
all_events.extend(monitor.feed(&pkt, secs(3)).to_vec());
assert!(
has_indicator(&all_events, Indicator::EitPfError),
"events: {all_events:#?}"
);
}
#[test]
fn eit_pf_error_trips_when_only_section_1_present() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec1 = build_eit_pf_actual_section_keyed(1, 0x99, 1, 1);
let packets = packetise_section(PID_EIT, &sec1);
let mut all_events = feed_all(&mut monitor, &packets, ms(5), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
all_events.extend(monitor.feed(&pkt, secs(3)).to_vec());
assert!(has_indicator(&all_events, Indicator::EitPfError));
}
#[test]
fn eit_pf_error_absent_when_both_sections_present() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec0 = build_eit_pf_actual_section_keyed(0, 1, 1, 1);
let packets0 = packetise_section(PID_EIT, &sec0);
feed_all(&mut monitor, &packets0, ms(5), ms(1));
let sec1 = build_eit_pf_actual_section_keyed(1, 1, 1, 1);
let packets1 = packetise_section(PID_EIT, &sec1);
feed_all(&mut monitor, &packets1, ms(10), ms(1));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
let events = monitor.feed(&pkt, ms(15));
assert!(!has_indicator(events, Indicator::EitPfError));
}
#[test]
fn eit_pf_error_per_sub_table_not_global() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let mut all_events = Vec::new();
let sec0_a = build_eit_pf_actual_section_keyed(0, 0x10, 1, 1);
let p0_a = packetise_section(PID_EIT, &sec0_a);
all_events.extend(feed_all(&mut monitor, &p0_a, ms(5), ms(1)));
let sec0_b = build_eit_pf_actual_section_keyed(0, 0x20, 1, 1);
let p0_b = packetise_section(PID_EIT, &sec0_b);
all_events.extend(feed_all(&mut monitor, &p0_b, ms(10), ms(1)));
let sec1_b = build_eit_pf_actual_section_keyed(1, 0x20, 1, 1);
let p1_b = packetise_section(PID_EIT, &sec1_b);
all_events.extend(feed_all(&mut monitor, &p1_b, ms(15), ms(1)));
let pkt = make_ts_packet(0x200, 0, false, false, &[], &[]);
all_events.extend(monitor.feed(&pkt, secs(3)).to_vec());
assert!(has_indicator(&all_events, Indicator::EitPfError));
let pf_events: Vec<_> = all_events
.iter()
.filter(|e| e.indicator == Indicator::EitPfError)
.collect();
assert_eq!(
pf_events.len(),
1,
"expected exactly 1 EitPfError (service 0x10 only), got {}: {pf_events:?}",
pf_events.len()
);
assert!(pf_events[0].detail.contains("0x0010"));
}
#[test]
fn nit_other_on_eit_pid_no_error() {
let mut monitor = ConformanceMonitor::new();
acquire_sync(&mut monitor);
let sec = build_eit_other_section(0);
let packets = packetise_section(PID_EIT, &sec);
feed_all(&mut monitor, &packets, ms(5), ms(1));
let packets2 = packetise_section(PID_EIT, &sec);
let events = feed_all(&mut monitor, &packets2, secs(15), ms(1));
assert!(has_indicator(&events, Indicator::EitOtherError));
}