1#![cfg_attr(not(feature = "std"), no_std)]
29#![cfg_attr(docsrs, feature(doc_cfg))]
30extern crate alloc;
31
32use alloc::collections::BTreeMap;
33use alloc::format;
34use alloc::string::String;
35use alloc::vec::Vec;
36use core::time::Duration;
37
38use dvb_common::Parse;
39use dvb_si::section::Section;
40use dvb_si::tables::pat::{PatSection, TABLE_ID as PAT_TABLE_ID};
41use dvb_si::tables::pmt::PmtSection;
42use dvb_si::ts::{SectionReassembler, TsPacket};
43
44const PID_PAT: u16 = 0x0000;
48const PID_CAT: u16 = 0x0001;
50const PID_NIT: u16 = 0x0010;
52const PID_SDT_BAT: u16 = 0x0011;
54const PID_EIT: u16 = 0x0012;
56const PID_TDT_TOT: u16 = 0x0014;
58const PID_NULL: u16 = 0x1FFF;
60
61const SYNC_BYTE: u8 = 0x47;
63
64const SI_PIDS: [u16; 6] = [PID_PAT, PID_CAT, PID_NIT, PID_SDT_BAT, PID_EIT, PID_TDT_TOT];
66
67const DEFAULT_PAT_MAX_INTERVAL_MS: u64 = 500;
72
73const DEFAULT_PMT_MAX_INTERVAL_MS: u64 = 500;
75
76const DEFAULT_PID_ERROR_PERIOD_SECS: u64 = 5;
78
79const DEFAULT_SYNC_ACQUIRE_PACKETS: u8 = 5;
82
83const DEFAULT_SYNC_LOSS_PACKETS: u8 = 2;
86
87const DEFAULT_PCR_REPETITION_LIMIT_MS: u64 = 100;
90
91const DEFAULT_PCR_DISCONTINUITY_LIMIT_MS: u64 = 100;
94
95const DEFAULT_PTS_REPETITION_LIMIT_MS: u64 = 700;
98
99const DEFAULT_SI_NIT_INTERVAL_SECS: u64 = 10;
102
103const DEFAULT_SI_SDT_INTERVAL_SECS: u64 = 2;
106
107const DEFAULT_SI_EIT_PF_INTERVAL_SECS: u64 = 2;
110
111const DEFAULT_SI_TDT_INTERVAL_SECS: u64 = 30;
114
115const PCR_MODULUS_27MHZ: u64 = (1u64 << 33) * 300;
120
121const CLOCK_27MHZ: u64 = 27_000_000;
123
124const PES_PREFIX_0: u8 = 0x00;
126const PES_PREFIX_1: u8 = 0x00;
128const PES_PREFIX_2: u8 = 0x01;
130
131const PES_FLAGS_OFFSET: usize = 6;
134
135const PES_PTS_DTS_FLAGS_MASK: u8 = 0b1100_0000;
138
139const PES_PTS_PRESENT: u8 = 0b1000_0000;
141
142const CAT_TABLE_ID: u8 = dvb_si::table_id::TableId::Cat as u8;
144
145const NIT_ACTUAL_TABLE_ID: u8 = dvb_si::table_id::TableId::NetworkInformationActual as u8;
147
148const SDT_ACTUAL_TABLE_ID: u8 = dvb_si::table_id::TableId::ServiceDescriptionActual as u8;
150
151const EIT_PF_ACTUAL_TABLE_ID: u8 = dvb_si::table_id::TableId::EventInformationPfActual as u8;
153
154const TDT_TABLE_ID: u8 = dvb_si::table_id::TableId::TimeAndDate as u8;
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
161#[cfg_attr(feature = "serde", derive(serde::Serialize))]
162#[non_exhaustive]
163pub enum Priority {
164 First,
166 Second,
168 Third,
170}
171
172impl Priority {
173 #[must_use]
175 pub fn name(&self) -> &'static str {
176 match self {
177 Self::First => "first priority",
178 Self::Second => "second priority",
179 Self::Third => "third priority",
180 }
181 }
182}
183dvb_common::impl_spec_display!(Priority);
184
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
189#[cfg_attr(feature = "serde", derive(serde::Serialize))]
190#[non_exhaustive]
191pub enum Indicator {
192 TsSyncLoss,
196 SyncByteError,
198 PatError2,
200 ContinuityCountError,
202 PmtError2,
204 PidError,
206
207 TransportError,
210 CrcError,
212 PcrRepetitionError,
214 PcrDiscontinuityError,
217 PtsError,
219 CatError,
221
222 SiRepetitionError,
226}
227
228impl Indicator {
229 #[must_use]
231 pub fn priority(self) -> Priority {
232 match self {
233 Self::TsSyncLoss
234 | Self::SyncByteError
235 | Self::PatError2
236 | Self::ContinuityCountError
237 | Self::PmtError2
238 | Self::PidError => Priority::First,
239 Self::TransportError
240 | Self::CrcError
241 | Self::PcrRepetitionError
242 | Self::PcrDiscontinuityError
243 | Self::PtsError
244 | Self::CatError => Priority::Second,
245 Self::SiRepetitionError => Priority::Third,
246 }
247 }
248
249 #[must_use]
251 pub fn name(self) -> &'static str {
252 match self {
253 Self::TsSyncLoss => "TS_sync_loss",
254 Self::SyncByteError => "Sync_byte_error",
255 Self::PatError2 => "PAT_error_2",
256 Self::ContinuityCountError => "Continuity_count_error",
257 Self::PmtError2 => "PMT_error_2",
258 Self::PidError => "PID_error",
259 Self::TransportError => "Transport_error",
260 Self::CrcError => "CRC_error",
261 Self::PcrRepetitionError => "PCR_repetition_error",
262 Self::PcrDiscontinuityError => "PCR_discontinuity_indicator_error",
263 Self::PtsError => "PTS_error",
264 Self::CatError => "CAT_error",
265 Self::SiRepetitionError => "SI_repetition_error",
266 }
267 }
268
269 #[must_use]
271 pub fn clause(self) -> &'static str {
272 match self {
273 Self::TsSyncLoss => "TR 101 290 v1.4.1 Table 5.0a indicator 1.1",
274 Self::SyncByteError => "TR 101 290 v1.4.1 Table 5.0a indicator 1.2",
275 Self::PatError2 => "TR 101 290 v1.4.1 Table 5.0a indicator 1.3.a",
276 Self::ContinuityCountError => "TR 101 290 v1.4.1 Table 5.0a indicator 1.4",
277 Self::PmtError2 => "TR 101 290 v1.4.1 Table 5.0a indicator 1.5.a",
278 Self::PidError => "TR 101 290 v1.4.1 Table 5.0a indicator 1.6",
279 Self::TransportError => "TR 101 290 v1.4.1 Table 5.0b indicator 2.1",
280 Self::CrcError => "TR 101 290 v1.4.1 Table 5.0b indicator 2.2",
281 Self::PcrRepetitionError => "TR 101 290 v1.4.1 Table 5.0b indicator 2.3a",
282 Self::PcrDiscontinuityError => "TR 101 290 v1.4.1 Table 5.0b indicator 2.3b",
283 Self::PtsError => "TR 101 290 v1.4.1 Table 5.0b indicator 2.5",
284 Self::CatError => "TR 101 290 v1.4.1 Table 5.0b indicator 2.6",
285 Self::SiRepetitionError => "TR 101 290 v1.4.1 Table 5.0c indicator 3.2",
286 }
287 }
288}
289dvb_common::impl_spec_display!(Indicator);
290
291#[derive(Debug, Clone, PartialEq, Eq)]
293#[cfg_attr(feature = "serde", derive(serde::Serialize))]
294#[non_exhaustive]
295pub struct ConformanceEvent {
296 pub indicator: Indicator,
298 pub priority: Priority,
300 pub pid: Option<u16>,
302 pub at: Duration,
304 pub detail: String,
306}
307
308#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
310#[cfg_attr(feature = "serde", derive(serde::Serialize))]
311#[non_exhaustive]
312pub struct Stats {
313 pub packets: u64,
315 pub events: u64,
317 pub in_sync: bool,
319}
320
321#[derive(Debug, Clone)]
323#[non_exhaustive]
324pub struct Config {
325 pub pat_max_interval: Duration,
328 pub pmt_max_interval: Duration,
331 pub pid_error_period: Duration,
334 pub sync_acquire_packets: u8,
337 pub sync_loss_packets: u8,
340 pub pcr_repetition_limit: Duration,
343 pub pcr_discontinuity_limit: Duration,
346 pub pts_repetition_limit: Duration,
349 pub si_nit_interval: Duration,
352 pub si_sdt_interval: Duration,
355 pub si_eit_pf_interval: Duration,
358 pub si_tdt_interval: Duration,
361}
362
363impl Default for Config {
364 fn default() -> Self {
365 Self {
366 pat_max_interval: Duration::from_millis(DEFAULT_PAT_MAX_INTERVAL_MS),
367 pmt_max_interval: Duration::from_millis(DEFAULT_PMT_MAX_INTERVAL_MS),
368 pid_error_period: Duration::from_secs(DEFAULT_PID_ERROR_PERIOD_SECS),
369 sync_acquire_packets: DEFAULT_SYNC_ACQUIRE_PACKETS,
370 sync_loss_packets: DEFAULT_SYNC_LOSS_PACKETS,
371 pcr_repetition_limit: Duration::from_millis(DEFAULT_PCR_REPETITION_LIMIT_MS),
372 pcr_discontinuity_limit: Duration::from_millis(DEFAULT_PCR_DISCONTINUITY_LIMIT_MS),
373 pts_repetition_limit: Duration::from_millis(DEFAULT_PTS_REPETITION_LIMIT_MS),
374 si_nit_interval: Duration::from_secs(DEFAULT_SI_NIT_INTERVAL_SECS),
375 si_sdt_interval: Duration::from_secs(DEFAULT_SI_SDT_INTERVAL_SECS),
376 si_eit_pf_interval: Duration::from_secs(DEFAULT_SI_EIT_PF_INTERVAL_SECS),
377 si_tdt_interval: Duration::from_secs(DEFAULT_SI_TDT_INTERVAL_SECS),
378 }
379 }
380}
381
382struct CcState {
386 last_cc: u8,
387 had_payload: bool,
388 dup_used: bool,
389 initialised: bool,
390}
391
392struct PresenceTimer {
394 last_seen: Duration,
395 reported: bool,
396}
397
398struct PmtTracking {
400 timer: PresenceTimer,
401 reassembler: SectionReassembler,
402}
403
404struct EsTracking {
406 timer: PresenceTimer,
407}
408
409struct PcrState {
411 last_pcr_27mhz: u64,
412 last_pcr_time: Duration,
413 initialised: bool,
414}
415
416struct PtsState {
418 last_pts_time: Duration,
419 armed: bool,
420}
421
422struct SiReassembly {
424 reassembler: SectionReassembler,
425}
426
427struct SiRepetitionTimer {
431 last_seen: Duration,
432 reported: bool,
433 armed: bool,
434}
435
436pub struct ConformanceMonitor {
444 config: Config,
445 events: Vec<ConformanceEvent>,
446 stats: Stats,
447
448 in_sync: bool,
450 good_run: u8,
451 bad_run: u8,
452
453 cc_states: BTreeMap<u16, CcState>,
455
456 pat_reassembler: SectionReassembler,
458 pat_timer: PresenceTimer,
459
460 pmt_trackings: BTreeMap<u16, PmtTracking>,
462
463 es_trackings: BTreeMap<u16, EsTracking>,
465
466 si_reassemblies: BTreeMap<u16, SiReassembly>,
468
469 pcr_states: BTreeMap<u16, PcrState>,
471
472 pts_states: BTreeMap<u16, PtsState>,
474
475 cat_seen: bool,
477 scrambled_without_cat_reported: bool,
478
479 si_timers: BTreeMap<u8, SiRepetitionTimer>,
481}
482
483impl ConformanceMonitor {
484 pub fn new() -> Self {
486 Self::with_config(Config::default())
487 }
488
489 pub fn with_config(config: Config) -> Self {
491 let mut si_reassemblies = BTreeMap::new();
492 for &pid in &SI_PIDS {
493 si_reassemblies.insert(
494 pid,
495 SiReassembly {
496 reassembler: SectionReassembler::default(),
497 },
498 );
499 }
500 Self {
501 config,
502 events: Vec::new(),
503 stats: Stats {
504 packets: 0,
505 events: 0,
506 in_sync: false,
507 },
508 in_sync: false,
509 good_run: 0,
510 bad_run: 0,
511 cc_states: BTreeMap::new(),
512 pat_reassembler: SectionReassembler::default(),
513 pat_timer: PresenceTimer {
514 last_seen: Duration::ZERO,
515 reported: false,
516 },
517 pmt_trackings: BTreeMap::new(),
518 es_trackings: BTreeMap::new(),
519 si_reassemblies,
520 pcr_states: BTreeMap::new(),
521 pts_states: BTreeMap::new(),
522 cat_seen: false,
523 scrambled_without_cat_reported: false,
524 si_timers: BTreeMap::new(),
525 }
526 }
527
528 pub fn feed(&mut self, ts_packet: &[u8], t: Duration) -> &[ConformanceEvent] {
534 self.events.clear();
535 self.stats.packets += 1;
536
537 let sync_ok = !ts_packet.is_empty() && ts_packet[0] == SYNC_BYTE;
539 if !sync_ok {
540 self.emit(Indicator::SyncByteError, None, t, "sync_byte != 0x47");
541 }
542
543 if sync_ok {
545 self.good_run = self.good_run.saturating_add(1);
546 self.bad_run = 0;
547 if !self.in_sync && self.good_run >= self.config.sync_acquire_packets {
548 self.in_sync = true;
549 }
550 } else {
551 self.bad_run = self.bad_run.saturating_add(1);
552 self.good_run = 0;
553 if self.in_sync && self.bad_run >= self.config.sync_loss_packets {
554 self.in_sync = false;
555 self.emit(
556 Indicator::TsSyncLoss,
557 None,
558 t,
559 "sync lost after hysteresis threshold",
560 );
561 }
562 }
563
564 if !self.in_sync {
568 return &self.events;
569 }
570
571 let packet = match TsPacket::parse(ts_packet) {
573 Ok(p) => p,
574 Err(_) => return &self.events,
575 };
576 let header = &packet.header;
577 let pid = header.pid;
578
579 if header.tei {
581 self.emit(
582 Indicator::TransportError,
583 Some(pid),
584 t,
585 format!("transport_error_indicator set on PID 0x{:04X}", pid),
586 );
587 }
588
589 if pid != PID_NULL {
591 self.check_cc(
592 pid,
593 header.continuity_counter,
594 header.has_payload,
595 t,
596 ts_packet,
597 );
598 }
599
600 if pid == PID_PAT && header.scrambling != 0 {
602 self.emit(
603 Indicator::PatError2,
604 Some(PID_PAT),
605 t,
606 format!(
607 "scrambling_control_field != 00 on PID 0x0000 (got {})",
608 header.scrambling
609 ),
610 );
611 }
612
613 if self.pmt_trackings.contains_key(&pid) && header.scrambling != 0 {
615 self.emit(
616 Indicator::PmtError2,
617 Some(pid),
618 t,
619 format!(
620 "scrambling_control_field != 00 on program_map_PID 0x{:04X}",
621 pid
622 ),
623 );
624 }
625
626 if header.scrambling != 0 && !self.cat_seen && !self.scrambled_without_cat_reported {
633 self.scrambled_without_cat_reported = true;
634 self.emit(
635 Indicator::CatError,
636 Some(pid),
637 t,
638 format!(
639 "scrambled packet on PID 0x{:04X} but no CAT seen on PID 0x0001",
640 pid
641 ),
642 );
643 }
644
645 if pid == PID_PAT && header.has_payload {
647 if let Some(payload) = packet.payload {
648 self.pat_reassembler.feed(payload, header.pusi);
649 }
650 self.pat_timer.last_seen = t;
651 self.pat_timer.reported = false;
652 while let Some(section_bytes) = self.pat_reassembler.pop_section() {
653 self.check_crc_and_process_pat(§ion_bytes, pid, t);
654 }
655 }
656
657 if self.pmt_trackings.contains_key(&pid) && header.has_payload {
659 if let Some(payload) = packet.payload {
660 if let Some(tracking) = self.pmt_trackings.get_mut(&pid) {
661 tracking.reassembler.feed(payload, header.pusi);
662 }
663 }
664 let sections: Vec<_> = if let Some(tracking) = self.pmt_trackings.get_mut(&pid) {
665 tracking.timer.last_seen = t;
666 tracking.timer.reported = false;
667 core::iter::from_fn(|| tracking.reassembler.pop_section()).collect()
668 } else {
669 Vec::new()
670 };
671 for section_bytes in §ions {
672 self.check_crc_and_process_pmt(section_bytes, pid, t);
673 }
674 }
675
676 if pid != PID_PAT
680 && !self.pmt_trackings.contains_key(&pid)
681 && self.si_reassemblies.contains_key(&pid)
682 && header.has_payload
683 {
684 if let Some(payload) = packet.payload {
685 if let Some(si_ra) = self.si_reassemblies.get_mut(&pid) {
686 si_ra.reassembler.feed(payload, header.pusi);
687 }
688 }
689 let sections: Vec<_> = if let Some(si_ra) = self.si_reassemblies.get_mut(&pid) {
690 core::iter::from_fn(|| si_ra.reassembler.pop_section()).collect()
691 } else {
692 Vec::new()
693 };
694 for section_bytes in §ions {
695 self.check_crc_for_si(section_bytes, pid, t);
696 self.check_cat_table_id(section_bytes, pid, t);
697 self.update_si_repetition(section_bytes, pid, t);
698 }
699 }
700 if let Some(tracking) = self.es_trackings.get_mut(&pid) {
708 tracking.timer.last_seen = t;
709 tracking.timer.reported = false;
710 }
711
712 if let Some(Ok(af)) = packet.adaptation_field() {
714 if let Some(pcr) = af.pcr {
715 self.check_pcr(pid, pcr.as_27mhz(), af.discontinuity_indicator, t);
716 }
717 }
718
719 if header.pusi
721 && header.scrambling == 0
722 && self.es_trackings.contains_key(&pid)
723 && header.has_payload
724 {
725 if let Some(payload) = packet.payload {
726 self.check_pts(pid, payload, t);
727 }
728 }
729
730 self.check_presence_timeouts(t);
732
733 &self.events
734 }
735
736 pub fn stats(&self) -> Stats {
738 Stats {
739 in_sync: self.in_sync,
740 ..self.stats
741 }
742 }
743
744 fn emit(
747 &mut self,
748 indicator: Indicator,
749 pid: Option<u16>,
750 at: Duration,
751 detail: impl Into<String>,
752 ) {
753 let event = ConformanceEvent {
754 indicator,
755 priority: indicator.priority(),
756 pid,
757 at,
758 detail: detail.into(),
759 };
760 self.stats.events += 1;
761 self.events.push(event);
762 }
763
764 fn check_cc(&mut self, pid: u16, cc: u8, has_payload: bool, t: Duration, raw: &[u8]) {
766 let discontinuity = if raw.len() >= 5 {
769 let b3 = raw[3];
770 let has_adaptation = (b3 & 0x20) != 0;
771 if has_adaptation {
772 let af_len = raw[4] as usize;
773 if af_len > 0 && raw.len() > 5 {
774 (raw[5] & 0x80) != 0
775 } else {
776 false
777 }
778 } else {
779 false
780 }
781 } else {
782 false
783 };
784
785 let (expected, is_duplicate, should_emit_dup, should_emit_cc) = {
787 let state = self.cc_states.entry(pid).or_insert_with(|| CcState {
788 last_cc: cc,
789 had_payload: has_payload,
790 dup_used: false,
791 initialised: false,
792 });
793
794 if !state.initialised {
795 state.last_cc = cc;
796 state.had_payload = has_payload;
797 state.dup_used = false;
798 state.initialised = true;
799 return;
800 }
801
802 if discontinuity {
803 (0u8, false, false, false)
805 } else {
806 let is_duplicate = cc == state.last_cc && has_payload;
807 let mut should_emit_dup = false;
808 let mut should_emit_cc = false;
809
810 if is_duplicate {
811 if state.dup_used {
812 should_emit_dup = true;
813 }
814 } else {
815 state.dup_used = false;
816 let expected = if has_payload {
817 (state.last_cc.wrapping_add(1)) & 0x0F
818 } else {
819 state.last_cc
820 };
821 if cc != expected {
822 should_emit_cc = true;
823 }
824 }
825
826 (
827 if has_payload {
828 (state.last_cc.wrapping_add(1)) & 0x0F
829 } else {
830 state.last_cc
831 },
832 is_duplicate,
833 should_emit_dup,
834 should_emit_cc,
835 )
836 }
837 };
838
839 if should_emit_dup {
841 self.emit(
842 Indicator::ContinuityCountError,
843 Some(pid),
844 t,
845 format!(
846 "second consecutive duplicate on PID 0x{:04X} (cc={})",
847 pid, cc
848 ),
849 );
850 }
851 if should_emit_cc {
852 self.emit(
853 Indicator::ContinuityCountError,
854 Some(pid),
855 t,
856 format!("expected cc={}, got {} on PID 0x{:04X}", expected, cc, pid),
857 );
858 }
859
860 let state = self.cc_states.get_mut(&pid).unwrap();
862 if discontinuity {
863 state.last_cc = cc;
864 state.had_payload = has_payload;
865 state.dup_used = false;
866 } else if is_duplicate {
867 state.dup_used = true;
869 } else {
870 state.dup_used = false;
871 state.last_cc = cc;
872 state.had_payload = has_payload;
873 }
874 }
875
876 fn check_crc_and_process_pat(&mut self, section_bytes: &[u8], pid: u16, t: Duration) {
878 self.check_crc_for_section(section_bytes, pid, t);
880
881 self.process_pat_section(section_bytes, t);
882 }
883
884 fn check_crc_and_process_pmt(&mut self, section_bytes: &[u8], pid: u16, t: Duration) {
886 self.check_crc_for_section(section_bytes, pid, t);
888
889 self.process_pmt_section(section_bytes, pid, t);
890 }
891
892 fn check_crc_for_section(&mut self, section_bytes: &[u8], pid: u16, t: Duration) {
897 let section = match Section::parse(section_bytes) {
898 Ok(s) => s,
899 Err(_) => return,
900 };
901
902 if let Err(dvb_si::error::Error::CrcMismatch { .. }) = section.validate_crc(section_bytes) {
904 self.emit(
905 Indicator::CrcError,
906 Some(pid),
907 t,
908 format!(
909 "CRC-32 mismatch on PID 0x{:04X} (table_id 0x{:02X})",
910 pid, section.table_id
911 ),
912 );
913 }
914 }
915
916 fn check_crc_for_si(&mut self, section_bytes: &[u8], pid: u16, t: Duration) {
918 self.check_crc_for_section(section_bytes, pid, t);
919 }
920
921 fn check_cat_table_id(&mut self, section_bytes: &[u8], pid: u16, t: Duration) {
924 if pid != PID_CAT {
925 return;
926 }
927 let section = match Section::parse(section_bytes) {
928 Ok(s) => s,
929 Err(_) => return,
930 };
931 if section.table_id == CAT_TABLE_ID {
932 self.cat_seen = true;
934 self.scrambled_without_cat_reported = false;
937 } else {
938 self.emit(
939 Indicator::CatError,
940 Some(PID_CAT),
941 t,
942 format!(
943 "section with table_id 0x{:02X} on PID 0x0001 (expected 0x01 for CAT)",
944 section.table_id
945 ),
946 );
947 }
948 }
949
950 fn process_pat_section(&mut self, section_bytes: &[u8], t: Duration) {
952 let section = match Section::parse(section_bytes) {
953 Ok(s) => s,
954 Err(_) => return,
955 };
956
957 if section.table_id != PAT_TABLE_ID {
959 self.emit(
960 Indicator::PatError2,
961 Some(PID_PAT),
962 t,
963 format!(
964 "section with table_id 0x{:02X} on PID 0x0000 (expected 0x00)",
965 section.table_id
966 ),
967 );
968 return;
969 }
970
971 let pat = match PatSection::parse(section_bytes) {
973 Ok(p) => p,
974 Err(_) => return,
975 };
976
977 for entry in pat.programmes() {
979 let pmt_pid = entry.pid;
980 self.pmt_trackings
981 .entry(pmt_pid)
982 .or_insert_with(|| PmtTracking {
983 timer: PresenceTimer {
984 last_seen: t,
985 reported: false,
986 },
987 reassembler: SectionReassembler::default(),
988 });
989 }
990 }
991
992 fn process_pmt_section(&mut self, section_bytes: &[u8], _pid: u16, t: Duration) {
994 let section = match Section::parse(section_bytes) {
995 Ok(s) => s,
996 Err(_) => return,
997 };
998
999 let pmt_table_id: u8 = dvb_si::tables::pmt::TABLE_ID;
1004 if section.table_id != pmt_table_id {
1005 return;
1006 }
1007
1008 let pmt = match PmtSection::parse(section_bytes) {
1010 Ok(p) => p,
1011 Err(_) => return,
1012 };
1013
1014 let mut new_es_pids: Vec<u16> = Vec::new();
1016 if pmt.pcr_pid != PID_NULL && !self.es_trackings.contains_key(&pmt.pcr_pid) {
1017 new_es_pids.push(pmt.pcr_pid);
1018 }
1019 for stream in &pmt.streams {
1020 let es_pid = stream.elementary_pid;
1021 if !self.es_trackings.contains_key(&es_pid) {
1022 new_es_pids.push(es_pid);
1023 }
1024 }
1025
1026 for es_pid in new_es_pids {
1027 self.es_trackings.insert(
1028 es_pid,
1029 EsTracking {
1030 timer: PresenceTimer {
1031 last_seen: t,
1032 reported: false,
1033 },
1034 },
1035 );
1036 }
1037 }
1038
1039 fn check_pcr(&mut self, pid: u16, pcr_27mhz: u64, discontinuity: bool, t: Duration) {
1041 let state = self.pcr_states.entry(pid).or_insert_with(|| PcrState {
1042 last_pcr_27mhz: 0,
1043 last_pcr_time: Duration::ZERO,
1044 initialised: false,
1045 });
1046
1047 if !state.initialised {
1048 state.last_pcr_27mhz = pcr_27mhz;
1049 state.last_pcr_time = t;
1050 state.initialised = true;
1051 return;
1052 }
1053
1054 let last_pcr_time = state.last_pcr_time;
1056 let last_pcr_27mhz = state.last_pcr_27mhz;
1057
1058 let rep_interval = t.saturating_sub(last_pcr_time);
1061 let should_emit_rep = rep_interval > self.config.pcr_repetition_limit;
1062
1063 let delta =
1066 (pcr_27mhz.wrapping_add(PCR_MODULUS_27MHZ) - last_pcr_27mhz) % PCR_MODULUS_27MHZ;
1067 let delta_ms = delta * 1000 / CLOCK_27MHZ;
1068 let limit_ms = self.config.pcr_discontinuity_limit.as_millis() as u64;
1069 let should_emit_disc = delta_ms > limit_ms && !discontinuity;
1070
1071 if should_emit_rep {
1073 self.emit(
1074 Indicator::PcrRepetitionError,
1075 Some(pid),
1076 t,
1077 format!(
1078 "PCR interval {} ms exceeds limit {} ms on PID 0x{:04X}",
1079 rep_interval.as_millis(),
1080 self.config.pcr_repetition_limit.as_millis(),
1081 pid
1082 ),
1083 );
1084 }
1085 if should_emit_disc {
1086 self.emit(
1087 Indicator::PcrDiscontinuityError,
1088 Some(pid),
1089 t,
1090 format!(
1091 "PCR delta {} ms exceeds limit {} ms on PID 0x{:04X} without discontinuity_indicator",
1092 delta_ms, limit_ms, pid
1093 ),
1094 );
1095 }
1096
1097 let state = self.pcr_states.get_mut(&pid).unwrap();
1099 state.last_pcr_27mhz = pcr_27mhz;
1100 state.last_pcr_time = t;
1101 }
1102
1103 fn check_pts(&mut self, pid: u16, payload: &[u8], t: Duration) {
1108 if payload.len() < PES_FLAGS_OFFSET + 2 {
1110 return;
1111 }
1112 if payload[0] != PES_PREFIX_0 || payload[1] != PES_PREFIX_1 || payload[2] != PES_PREFIX_2 {
1113 return;
1114 }
1115
1116 let flags_byte = payload[PES_FLAGS_OFFSET];
1118 if (flags_byte >> 6) != 0b10 {
1119 return;
1120 }
1121
1122 let pts_dts_flags = payload[PES_FLAGS_OFFSET + 1] & PES_PTS_DTS_FLAGS_MASK;
1124 let pts_present = (pts_dts_flags & PES_PTS_PRESENT) != 0;
1125 if !pts_present {
1126 return;
1127 }
1128
1129 let state = self.pts_states.entry(pid).or_insert_with(|| PtsState {
1130 last_pts_time: Duration::ZERO,
1131 armed: false,
1132 });
1133
1134 if !state.armed {
1135 state.last_pts_time = t;
1137 state.armed = true;
1138 return;
1139 }
1140
1141 let last_pts_time = state.last_pts_time;
1143 let pts_interval = t.saturating_sub(last_pts_time);
1144 let should_emit = pts_interval > self.config.pts_repetition_limit;
1145
1146 if should_emit {
1147 self.emit(
1148 Indicator::PtsError,
1149 Some(pid),
1150 t,
1151 format!(
1152 "PTS interval {} ms exceeds limit {} ms on PID 0x{:04X}",
1153 pts_interval.as_millis(),
1154 self.config.pts_repetition_limit.as_millis(),
1155 pid
1156 ),
1157 );
1158 }
1159
1160 let state = self.pts_states.get_mut(&pid).unwrap();
1162 state.last_pts_time = t;
1163 }
1164
1165 fn update_si_repetition(&mut self, section_bytes: &[u8], _pid: u16, t: Duration) {
1169 let table_id = match Section::parse(section_bytes) {
1170 Ok(s) => s.table_id,
1171 Err(_) => return,
1172 };
1173
1174 let is_tracked = table_id == NIT_ACTUAL_TABLE_ID
1175 || table_id == SDT_ACTUAL_TABLE_ID
1176 || table_id == EIT_PF_ACTUAL_TABLE_ID
1177 || table_id == TDT_TABLE_ID;
1178
1179 if !is_tracked {
1180 return;
1181 }
1182
1183 let timer = self
1184 .si_timers
1185 .entry(table_id)
1186 .or_insert_with(|| SiRepetitionTimer {
1187 last_seen: Duration::ZERO,
1188 reported: false,
1189 armed: false,
1190 });
1191
1192 timer.last_seen = t;
1193 timer.reported = false;
1194 timer.armed = true;
1195 }
1196
1197 fn check_presence_timeouts(&mut self, t: Duration) {
1199 if t.saturating_sub(self.pat_timer.last_seen) > self.config.pat_max_interval
1201 && !self.pat_timer.reported
1202 {
1203 self.pat_timer.reported = true;
1204 self.emit(
1205 Indicator::PatError2,
1206 Some(PID_PAT),
1207 t,
1208 format!(
1209 "no PAT section within {} ms",
1210 self.config.pat_max_interval.as_millis()
1211 ),
1212 );
1213 }
1214
1215 let pmt_timeouts: Vec<(u16, u64)> = self
1218 .pmt_trackings
1219 .iter()
1220 .filter_map(|(&pid, tracking)| {
1221 if t.saturating_sub(tracking.timer.last_seen) > self.config.pmt_max_interval
1222 && !tracking.timer.reported
1223 {
1224 Some((pid, self.config.pmt_max_interval.as_millis() as u64))
1225 } else {
1226 None
1227 }
1228 })
1229 .collect();
1230 for (pid, interval_ms) in pmt_timeouts {
1231 if let Some(tracking) = self.pmt_trackings.get_mut(&pid) {
1232 tracking.timer.reported = true;
1233 }
1234 self.emit(
1235 Indicator::PmtError2,
1236 Some(pid),
1237 t,
1238 format!(
1239 "no PMT section on program_map_PID 0x{:04X} within {} ms",
1240 pid, interval_ms
1241 ),
1242 );
1243 }
1244
1245 let pid_timeouts: Vec<(u16, u64)> = self
1247 .es_trackings
1248 .iter()
1249 .filter_map(|(&pid, tracking)| {
1250 if t.saturating_sub(tracking.timer.last_seen) > self.config.pid_error_period
1251 && !tracking.timer.reported
1252 {
1253 Some((pid, self.config.pid_error_period.as_secs()))
1254 } else {
1255 None
1256 }
1257 })
1258 .collect();
1259 for (pid, period_secs) in pid_timeouts {
1260 if let Some(tracking) = self.es_trackings.get_mut(&pid) {
1261 tracking.timer.reported = true;
1262 }
1263 self.emit(
1264 Indicator::PidError,
1265 Some(pid),
1266 t,
1267 format!(
1268 "referenced PID 0x{:04X} absent for > {} s",
1269 pid, period_secs
1270 ),
1271 );
1272 }
1273
1274 let si_timeouts: Vec<(u8, u64, u16, u64)> = self
1277 .si_timers
1278 .iter()
1279 .filter_map(|(&table_id, timer)| {
1280 if !timer.armed || timer.reported {
1281 return None;
1282 }
1283 let (limit, pid) = match table_id {
1284 NIT_ACTUAL_TABLE_ID => (self.config.si_nit_interval, PID_NIT),
1285 SDT_ACTUAL_TABLE_ID => (self.config.si_sdt_interval, PID_SDT_BAT),
1286 EIT_PF_ACTUAL_TABLE_ID => (self.config.si_eit_pf_interval, PID_EIT),
1287 TDT_TABLE_ID => (self.config.si_tdt_interval, PID_TDT_TOT),
1288 _ => return None,
1289 };
1290 let interval = t.saturating_sub(timer.last_seen);
1291 if interval > limit {
1292 Some((
1293 table_id,
1294 interval.as_millis() as u64,
1295 pid,
1296 limit.as_millis() as u64,
1297 ))
1298 } else {
1299 None
1300 }
1301 })
1302 .collect();
1303 for (table_id, interval_ms, pid, limit_ms) in si_timeouts {
1304 if let Some(timer) = self.si_timers.get_mut(&table_id) {
1305 timer.reported = true;
1306 }
1307 let table_name = match table_id {
1308 NIT_ACTUAL_TABLE_ID => "NIT_actual",
1309 SDT_ACTUAL_TABLE_ID => "SDT_actual",
1310 EIT_PF_ACTUAL_TABLE_ID => "EIT_P/F_actual",
1311 TDT_TABLE_ID => "TDT",
1312 _ => "unknown",
1313 };
1314 self.emit(
1315 Indicator::SiRepetitionError,
1316 Some(pid),
1317 t,
1318 format!(
1319 "{} repetition interval {} ms exceeds {} ms",
1320 table_name, interval_ms, limit_ms
1321 ),
1322 );
1323 }
1324 }
1325}
1326
1327impl Default for ConformanceMonitor {
1328 fn default() -> Self {
1329 Self::new()
1330 }
1331}
1332
1333#[cfg(test)]
1334mod tests;