Skip to main content

dvb_si/tables/
mod.rs

1//! SI + PSI table-section parsers.
2//!
3//! Each `*Section` type parses and serializes one wire section. Use
4//! [`crate::collect`] to assemble complete logical tables that span multiple
5//! sections.
6
7/// Running status of an event or service — EN 300 468 Table 6.
8///
9/// Codes 6-7 are reserved for future use and round-tripped transparently via
10/// [`Reserved`](RunningStatus::Reserved).
11///
12/// # Examples
13/// ```
14/// use dvb_si::tables::RunningStatus;
15///
16/// let s = RunningStatus::from_u8(4);
17/// assert_eq!(s.name(), "running");
18/// assert_eq!(s.to_u8(), 4); // lossless back to the wire value
19///
20/// // Unallocated codes are preserved verbatim for byte-identical round-trip.
21/// assert_eq!(RunningStatus::from_u8(6).to_u8(), 6);
22/// ```
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize))]
25#[non_exhaustive]
26pub enum RunningStatus {
27    /// Value 0 — undefined.
28    Undefined,
29    /// Value 1 — not running.
30    NotRunning,
31    /// Value 2 — starts in a few seconds (e.g. for video recording).
32    StartsInAFewSeconds,
33    /// Value 3 — pausing.
34    Pausing,
35    /// Value 4 — running.
36    Running,
37    /// Value 5 — service off-air.
38    ServiceOffAir,
39    /// Reserved/unallocated wire value, preserved verbatim for round-trip.
40    Reserved(u8),
41}
42
43impl RunningStatus {
44    /// Map any 3-bit value to a `RunningStatus`.
45    #[must_use]
46    pub fn from_u8(v: u8) -> Self {
47        match v & 0x07 {
48            0 => Self::Undefined,
49            1 => Self::NotRunning,
50            2 => Self::StartsInAFewSeconds,
51            3 => Self::Pausing,
52            4 => Self::Running,
53            5 => Self::ServiceOffAir,
54            r => Self::Reserved(r),
55        }
56    }
57
58    /// Return the 3-bit wire value.
59    #[must_use]
60    pub fn to_u8(self) -> u8 {
61        match self {
62            Self::Undefined => 0,
63            Self::NotRunning => 1,
64            Self::StartsInAFewSeconds => 2,
65            Self::Pausing => 3,
66            Self::Running => 4,
67            Self::ServiceOffAir => 5,
68            Self::Reserved(v) => v & 0x07,
69        }
70    }
71
72    /// Human-readable spec name.
73    #[must_use]
74    pub fn name(self) -> &'static str {
75        match self {
76            Self::Undefined => "undefined",
77            Self::NotRunning => "not running",
78            Self::StartsInAFewSeconds => "starts in a few seconds",
79            Self::Pausing => "pausing",
80            Self::Running => "running",
81            Self::ServiceOffAir => "service off-air",
82            Self::Reserved(_) => "reserved",
83        }
84    }
85}
86dvb_common::impl_spec_display!(RunningStatus, Reserved);
87
88#[cfg(test)]
89mod running_status_tests {
90    use super::*;
91
92    #[test]
93    fn from_u8_maps_known_values() {
94        assert_eq!(RunningStatus::from_u8(0), RunningStatus::Undefined);
95        assert_eq!(RunningStatus::from_u8(1), RunningStatus::NotRunning);
96        assert_eq!(
97            RunningStatus::from_u8(2),
98            RunningStatus::StartsInAFewSeconds
99        );
100        assert_eq!(RunningStatus::from_u8(3), RunningStatus::Pausing);
101        assert_eq!(RunningStatus::from_u8(4), RunningStatus::Running);
102        assert_eq!(RunningStatus::from_u8(5), RunningStatus::ServiceOffAir);
103        assert_eq!(RunningStatus::from_u8(6), RunningStatus::Reserved(6));
104        assert_eq!(RunningStatus::from_u8(7), RunningStatus::Reserved(7));
105    }
106
107    #[test]
108    fn to_u8_from_u8_round_trips_all_byte_values() {
109        for v in 0u8..=0xFFu8 {
110            assert_eq!(RunningStatus::to_u8(RunningStatus::from_u8(v)), v & 0x07);
111        }
112    }
113
114    #[test]
115    fn name_returns_known_strings() {
116        assert_eq!(RunningStatus::Undefined.name(), "undefined");
117        assert_eq!(RunningStatus::NotRunning.name(), "not running");
118        assert_eq!(RunningStatus::Running.name(), "running");
119        assert_eq!(RunningStatus::ServiceOffAir.name(), "service off-air");
120        assert_eq!(RunningStatus::Reserved(6).name(), "reserved");
121    }
122
123    #[test]
124    fn running_status_wire_to_name() {
125        assert_eq!(RunningStatus::from_u8(4).name(), "running");
126        assert_eq!(RunningStatus::from_u8(2).name(), "starts in a few seconds");
127        assert_eq!(RunningStatus::from_u8(0).name(), "undefined");
128    }
129}
130
131/// Byte 1 flags nibble for MPEG-2 PSI long-form sections.
132///
133/// Layout: `section_syntax_indicator(1) | '0'(1) | reserved(2)`.
134/// Per ISO/IEC 13818-1 §2.4.4.10, the second bit is a spec-mandated
135/// zero in PSI tables (PAT, PMT, CAT, TSDT, DSM-CC).
136pub(crate) const SECTION_B1_FLAGS_PSI: u8 = 0xB0;
137
138/// Byte 1 flags nibble for EN 300 468 (DVB) long-form sections.
139///
140/// Layout: `section_syntax_indicator(1) | reserved_future_use(1) | reserved(2)`.
141/// Per ETSI EN 300 468 §5.1.1, the top nibble must be `F` — all four
142/// bits set (SSI=1, rfu=1, reserved=11).
143pub(crate) const SECTION_B1_FLAGS_DVB: u8 = 0xF0;
144
145/// `section_syntax_indicator` bit in long-form section byte 1.
146pub(crate) const SECTION_B1_SSI: u8 = 0x80;
147
148/// Reserved bits `[5:4]` in long-form section byte 1, set to `11`.
149pub(crate) const SECTION_B1_RESERVED_HI: u8 = 0x30;
150
151/// Byte 1 flags nibble for short-form sections (no extension header, no CRC).
152///
153/// Layout: `section_syntax_indicator(0) | reserved_future_use(1) | reserved(11)`.
154/// Top nibble `0b0111` = `0x70`. Used by RST, ST, DIT, TDT, TOT.
155pub(crate) const SECTION_B1_FLAGS_SHORT: u8 = 0x70;
156
157/// Validate a section_length field and compute the total encoded length.
158///
159/// Returns `total` (= `header_len + section_length`) on success, or
160/// `Err(SectionLengthOverflow)` when the declared `section_length` would
161/// make `total` smaller than `min_total` or larger than `bytes_len`.
162///
163/// Every table's `Parse` implementation should call this immediately after
164/// extracting `section_length` from bytes 1-2, passing the appropriate
165/// constants for that table type.
166pub(crate) fn check_section_length(
167    bytes_len: usize,
168    header_len: usize,
169    section_length: usize,
170    min_total: usize,
171) -> crate::Result<usize> {
172    let total = header_len + section_length;
173    if bytes_len < total || total < min_total {
174        return Err(crate::error::Error::SectionLengthOverflow {
175            declared: section_length,
176            available: bytes_len.saturating_sub(header_len),
177        });
178    }
179    Ok(total)
180}
181
182pub mod any;
183pub use any::AnyTableSection;
184
185pub mod registry;
186pub use registry::{TableObject, TableRegistry};
187
188pub mod ait;
189pub mod bat;
190pub mod cat;
191pub mod cit;
192pub mod container;
193pub mod dit;
194pub mod downloadable_font_info;
195pub mod dsmcc;
196pub mod eit;
197pub mod int;
198pub mod mpe;
199pub mod mpe_fec;
200pub mod mpe_ifec;
201pub mod nit;
202pub mod pat;
203pub mod pmt;
204pub mod protection_message;
205pub mod rct;
206pub mod rnt;
207pub mod rst;
208pub mod sat;
209pub mod sdt;
210pub mod sit;
211pub mod st;
212pub mod tdt;
213pub mod tot;
214pub mod tsdt;
215pub mod unt;