Skip to main content

dvb_si/tables/
rst.rs

1//! Running Status Table — ETSI EN 300 468 §5.2.8.
2//!
3//! Carried on PID `0x0013` with `table_id = 0x71`. A SHORT-FORM section — there
4//! is no version/section header and no CRC. The body is a flat loop of 9-byte
5//! entries, each giving the running status of one event:
6//!
7//! ```text
8//! transport_stream_id(16) original_network_id(16) service_id(16)
9//! event_id(16) reserved_future_use(5) running_status(3)
10//! ```
11
12use super::RunningStatus;
13use crate::error::{Error, Result};
14use alloc::vec::Vec;
15use dvb_common::{Parse, Serialize};
16
17/// table_id for the Running Status Table.
18pub const TABLE_ID: u8 = 0x71;
19/// Well-known PID on which the RST is carried.
20pub const PID: u16 = 0x0013;
21
22const HEADER_LEN: usize = 3;
23/// Each entry is 9 bytes: tsid(2) + onid(2) + sid(2) + evid(2) + status(1).
24const ENTRY_LEN: usize = 9;
25
26/// One RST entry — the running status of a single event.
27///
28/// `running_status` is the 3-bit code from EN 300 468 Table 6: 0 undefined,
29/// 1 not running, 2 starts in a few seconds, 3 pausing, 4 running,
30/// 5 service off-air, 6–7 reserved.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize))]
33pub struct RstEntry {
34    /// Transport stream carrying the event.
35    pub transport_stream_id: u16,
36    /// Originating network.
37    pub original_network_id: u16,
38    /// Service (matches `program_number` in the PAT).
39    pub service_id: u16,
40    /// Event identifier.
41    pub event_id: u16,
42    /// 3-bit running_status code (EN 300 468 Table 6).
43    pub running_status: RunningStatus,
44}
45
46/// Running Status Table (§5.2.8, Table 10).
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49pub struct RstSection {
50    /// Entries in wire order.
51    pub entries: Vec<RstEntry>,
52}
53
54impl<'a> Parse<'a> for RstSection {
55    type Error = crate::error::Error;
56
57    fn parse(bytes: &'a [u8]) -> Result<Self> {
58        if bytes.len() < HEADER_LEN {
59            return Err(Error::BufferTooShort {
60                need: HEADER_LEN,
61                have: bytes.len(),
62                what: "RstSection",
63            });
64        }
65        if bytes[0] != TABLE_ID {
66            return Err(Error::UnexpectedTableId {
67                table_id: bytes[0],
68                what: "RstSection",
69                expected: &[TABLE_ID],
70            });
71        }
72        let section_length = ((bytes[1] & 0x0F) as usize) << 8 | bytes[2] as usize;
73        let total = HEADER_LEN + section_length;
74        if bytes.len() < total {
75            return Err(Error::SectionLengthOverflow {
76                declared: section_length,
77                available: bytes.len() - HEADER_LEN,
78            });
79        }
80        if section_length % ENTRY_LEN != 0 {
81            return Err(Error::BufferTooShort {
82                need: (section_length / ENTRY_LEN + 1) * ENTRY_LEN,
83                have: section_length,
84                what: "RstSection entry alignment",
85            });
86        }
87        let mut entries = Vec::with_capacity(section_length / ENTRY_LEN);
88        let mut off = HEADER_LEN;
89        while off + ENTRY_LEN <= total {
90            let entry = &bytes[off..off + ENTRY_LEN];
91            entries.push(RstEntry {
92                transport_stream_id: u16::from_be_bytes(*entry[0..].first_chunk::<2>().unwrap()),
93                original_network_id: u16::from_be_bytes(*entry[2..].first_chunk::<2>().unwrap()),
94                service_id: u16::from_be_bytes(*entry[4..].first_chunk::<2>().unwrap()),
95                event_id: u16::from_be_bytes(*entry[6..].first_chunk::<2>().unwrap()),
96                running_status: RunningStatus::from_u8(entry[8] & 0x07),
97            });
98            off += ENTRY_LEN;
99        }
100        Ok(RstSection { entries })
101    }
102}
103
104impl Serialize for RstSection {
105    type Error = crate::error::Error;
106
107    fn serialized_len(&self) -> usize {
108        HEADER_LEN + self.entries.len() * ENTRY_LEN
109    }
110
111    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
112        let len = self.serialized_len();
113        if buf.len() < len {
114            return Err(Error::OutputBufferTooSmall {
115                need: len,
116                have: buf.len(),
117            });
118        }
119        let section_length = (len - HEADER_LEN) as u16;
120        buf[0] = TABLE_ID;
121        // section_syntax_indicator=0 (short form), reserved_future_use=1,
122        // reserved=11, section_length high nibble.
123        buf[1] = super::SECTION_B1_FLAGS_SHORT | ((section_length >> 8) as u8 & 0x0F);
124        buf[2] = (section_length & 0xFF) as u8;
125        let mut off = HEADER_LEN;
126        for e in &self.entries {
127            buf[off..off + 2].copy_from_slice(&e.transport_stream_id.to_be_bytes());
128            buf[off + 2..off + 4].copy_from_slice(&e.original_network_id.to_be_bytes());
129            buf[off + 4..off + 6].copy_from_slice(&e.service_id.to_be_bytes());
130            buf[off + 6..off + 8].copy_from_slice(&e.event_id.to_be_bytes());
131            // reserved_future_use(5)=1, running_status(3).
132            buf[off + 8] = 0xF8 | (e.running_status.to_u8() & 0x07);
133            off += ENTRY_LEN;
134        }
135        Ok(len)
136    }
137}
138impl<'a> crate::traits::TableDef<'a> for RstSection {
139    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
140    const NAME: &'static str = "RUNNING_STATUS";
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146
147    fn build_rst(entries: &[RstEntry]) -> Vec<u8> {
148        let section_length = (entries.len() * ENTRY_LEN) as u16;
149        let mut v = vec![
150            TABLE_ID,
151            0x70 | ((section_length >> 8) as u8 & 0x0F),
152            (section_length & 0xFF) as u8,
153        ];
154        for e in entries {
155            v.extend_from_slice(&e.transport_stream_id.to_be_bytes());
156            v.extend_from_slice(&e.original_network_id.to_be_bytes());
157            v.extend_from_slice(&e.service_id.to_be_bytes());
158            v.extend_from_slice(&e.event_id.to_be_bytes());
159            v.push(0xF8 | (e.running_status.to_u8() & 0x07));
160        }
161        v
162    }
163
164    fn entry(tsid: u16, onid: u16, sid: u16, evid: u16, rs: RunningStatus) -> RstEntry {
165        RstEntry {
166            transport_stream_id: tsid,
167            original_network_id: onid,
168            service_id: sid,
169            event_id: evid,
170            running_status: rs,
171        }
172    }
173
174    #[test]
175    fn parse_empty() {
176        let rst = RstSection::parse(&build_rst(&[])).unwrap();
177        assert!(rst.entries.is_empty());
178    }
179
180    #[test]
181    fn parse_single_entry() {
182        let e = entry(0x1234, 0x0001, 0xABCD, 0x4000, RunningStatus::Running);
183        let rst = RstSection::parse(&build_rst(&[e])).unwrap();
184        assert_eq!(rst.entries.len(), 1);
185        assert_eq!(rst.entries[0], e);
186        assert_eq!(rst.entries[0].running_status, RunningStatus::Running); // running
187    }
188
189    #[test]
190    fn parse_multiple_entries() {
191        let es = [
192            entry(0x0001, 0x1000, 0x0010, 0x0100, RunningStatus::NotRunning),
193            entry(0x0002, 0x2000, 0x0020, 0x0200, RunningStatus::Running),
194            entry(0x0003, 0x3000, 0x0030, 0x0300, RunningStatus::ServiceOffAir),
195        ];
196        let rst = RstSection::parse(&build_rst(&es)).unwrap();
197        assert_eq!(rst.entries, es);
198    }
199
200    #[test]
201    fn parse_rejects_wrong_tag() {
202        let mut bytes = build_rst(&[]);
203        bytes[0] = 0x72;
204        assert!(matches!(
205            RstSection::parse(&bytes).unwrap_err(),
206            Error::UnexpectedTableId { table_id: 0x72, .. }
207        ));
208    }
209
210    #[test]
211    fn parse_rejects_short_buffer() {
212        assert!(matches!(
213            RstSection::parse(&[0x71, 0x70]).unwrap_err(),
214            Error::BufferTooShort { .. }
215        ));
216    }
217
218    #[test]
219    fn parse_rejects_non_multiple_loop() {
220        let bytes = [TABLE_ID, 0x70, 0x04, 0x00, 0x00, 0x00, 0x00];
221        assert!(matches!(
222            RstSection::parse(&bytes).unwrap_err(),
223            Error::BufferTooShort {
224                what: "RstSection entry alignment",
225                ..
226            }
227        ));
228    }
229
230    #[test]
231    fn serialize_round_trip() {
232        let es = [
233            entry(0xCAFE, 0xBEEF, 0x1234, 0x5678, RunningStatus::Running),
234            entry(0x0001, 0x0002, 0x0003, 0x0004, RunningStatus::ServiceOffAir),
235        ];
236        let rst = RstSection::parse(&build_rst(&es)).unwrap();
237        let mut buf = vec![0u8; rst.serialized_len()];
238        rst.serialize_into(&mut buf).unwrap();
239        assert_eq!(buf, build_rst(&es));
240        assert_eq!(RstSection::parse(&buf).unwrap(), rst);
241    }
242
243    #[test]
244    fn serialize_empty_round_trip() {
245        let rst = RstSection { entries: vec![] };
246        let mut buf = vec![0u8; rst.serialized_len()];
247        rst.serialize_into(&mut buf).unwrap();
248        assert_eq!(RstSection::parse(&buf).unwrap(), rst);
249    }
250
251    #[test]
252    fn table_trait_constants() {
253        assert_eq!(TABLE_ID, 0x71);
254        assert_eq!(PID, 0x0013);
255    }
256
257    #[cfg(feature = "serde")]
258    #[test]
259    fn serde_json_serializes_fields() {
260        // Serialize-only: assert the emitted JSON re-parses (serialize-stable).
261        let rst =
262            RstSection::parse(&build_rst(&[entry(1, 2, 3, 4, RunningStatus::Running)])).unwrap();
263        let j = serde_json::to_string(&rst).unwrap();
264        let v: serde_json::Value = serde_json::from_str(&j).unwrap();
265        assert_eq!(v["entries"][0]["service_id"], 3);
266    }
267}