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