Skip to main content

dvb_si/tables/
st.rs

1//! Stuffing Table — ETSI EN 300 468 §5.2.8.
2//!
3//! Short-form section on PID 0x0014 with table_id 0x72. Payload is stuffing
4//! bytes used to invalidate/replace sections; per §5.2.8 each `data_byte`
5//! "may take any value and has no meaning" (0xFF fill is common on real
6//! transponders). No CRC.
7
8use crate::error::{Error, Result};
9use alloc::vec::Vec;
10use dvb_common::{Parse, Serialize};
11
12/// table_id for Stuffing Table.
13pub const TABLE_ID: u8 = 0x72;
14/// Well-known PID on which ST is carried (shared with TDT/TOT).
15pub const PID: u16 = 0x0014;
16
17const HEADER_LEN: usize = 3;
18
19/// Stuffing Table.
20#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))]
22pub struct StSection {
23    /// Raw stuffing bytes — any value, no meaning (§5.2.8).
24    pub payload: Vec<u8>,
25}
26
27impl StSection {
28    /// Construct a new ST table with the given stuffing bytes.
29    #[inline]
30    #[must_use]
31    pub fn new(payload: Vec<u8>) -> Self {
32        Self { payload }
33    }
34
35    /// Number of stuffing bytes in the payload.
36    #[inline]
37    #[must_use]
38    pub fn len(&self) -> usize {
39        self.payload.len()
40    }
41
42    /// Returns `true` if the payload contains no stuffing bytes.
43    #[inline]
44    #[must_use]
45    pub fn is_empty(&self) -> bool {
46        self.payload.is_empty()
47    }
48}
49
50impl<'a> Parse<'a> for StSection {
51    type Error = crate::error::Error;
52
53    fn parse(bytes: &'a [u8]) -> Result<Self> {
54        if bytes.len() < HEADER_LEN {
55            return Err(Error::BufferTooShort {
56                need: HEADER_LEN,
57                have: bytes.len(),
58                what: "StSection",
59            });
60        }
61
62        if bytes[0] != TABLE_ID {
63            return Err(Error::UnexpectedTableId {
64                table_id: bytes[0],
65                what: "StSection",
66                expected: &[TABLE_ID],
67            });
68        }
69
70        let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
71        let payload_len = section_length as usize;
72
73        if bytes.len() < HEADER_LEN + payload_len {
74            return Err(Error::SectionLengthOverflow {
75                declared: payload_len,
76                available: bytes.len().saturating_sub(HEADER_LEN),
77            });
78        }
79
80        // §5.2.8: data_byte "may take any value and has no meaning" — no
81        // value constraint; preserve verbatim.
82        let payload = &bytes[HEADER_LEN..HEADER_LEN + payload_len];
83        Ok(Self {
84            payload: payload.to_vec(),
85        })
86    }
87}
88
89impl Serialize for StSection {
90    type Error = crate::error::Error;
91
92    fn serialized_len(&self) -> usize {
93        HEADER_LEN + self.payload.len()
94    }
95
96    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
97        let len = self.serialized_len();
98        if buf.len() < len {
99            return Err(Error::OutputBufferTooSmall {
100                need: len,
101                have: buf.len(),
102            });
103        }
104
105        // Byte 0: table_id = 0x72
106        buf[0] = TABLE_ID;
107
108        // Byte 1: SSI=0, reserved_future_use='1', reserved='11', upper nibble of
109        // section_length. Top nibble 0b0111 = 0x70, matching DIT/RST/TDT/TOT.
110        buf[1] = super::SECTION_B1_FLAGS_SHORT | ((self.payload.len() >> 8) as u8 & 0x0F);
111
112        // Byte 2: section_length low byte
113        buf[2] = (self.payload.len() & 0xFF) as u8;
114
115        // Payload: stuffing bytes
116        buf[HEADER_LEN..len].copy_from_slice(&self.payload);
117
118        Ok(len)
119    }
120}
121impl<'a> crate::traits::TableDef<'a> for StSection {
122    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
123    const NAME: &'static str = "STUFFING";
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    /// Build a minimal ST section byte vector.
131    fn make_st_section(payload: &[u8]) -> Vec<u8> {
132        let mut buf = vec![TABLE_ID, 0x70, payload.len() as u8];
133        buf.extend_from_slice(payload);
134        buf
135    }
136
137    #[test]
138    fn parse_rejects_wrong_tag() {
139        let bytes = [0x71, 0x70, 0x02, 0x00, 0x00];
140        assert!(matches!(
141            StSection::parse(&bytes).unwrap_err(),
142            Error::UnexpectedTableId { table_id: 0x71, .. }
143        ));
144    }
145
146    #[test]
147    fn parse_empty_payload() {
148        let bytes = make_st_section(&[]);
149        let st = StSection::parse(&bytes).unwrap();
150        assert!(st.is_empty());
151    }
152
153    /// §5.2.8: data_byte "may take any value and has no meaning" — 0xFF fill
154    /// (common on real transponders) must parse and round-trip.
155    #[test]
156    fn parse_accepts_any_data_byte_value() {
157        let bytes = make_st_section(&[0x00, 0xFF, 0xAA]);
158        let st = StSection::parse(&bytes).unwrap();
159        assert_eq!(st.payload, vec![0x00, 0xFF, 0xAA]);
160        let mut buf = vec![0u8; st.serialized_len()];
161        st.serialize_into(&mut buf).unwrap();
162        assert_eq!(buf, bytes);
163    }
164
165    #[test]
166    fn serialize_writes_correct_header() {
167        let st = StSection::new(vec![0x00, 0x00]);
168        let mut buf = vec![0u8; st.serialized_len()];
169        st.serialize_into(&mut buf).unwrap();
170        assert_eq!(buf[0], TABLE_ID);
171        assert_eq!(buf[1], 0x70 | ((2 >> 8) as u8 & 0x0F));
172        assert_eq!(buf[2], 2);
173    }
174
175    #[test]
176    fn serialize_empty_payload() {
177        let st = StSection::new(vec![]);
178        let mut buf = vec![0u8; st.serialized_len()];
179        st.serialize_into(&mut buf).unwrap();
180        assert_eq!(buf, [TABLE_ID, 0x70, 0x00]);
181    }
182
183    #[test]
184    fn serialize_rejects_too_small_buffer() {
185        let st = StSection::new(vec![0x00]);
186        let mut too_small = vec![0u8; st.serialized_len() - 1];
187        let err = st.serialize_into(&mut too_small).unwrap_err();
188        assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
189    }
190
191    #[test]
192    fn round_trip_preserves_all_fields() {
193        let st = StSection::new(vec![0x00, 0x00, 0x00]);
194        let mut buf = vec![0u8; st.serialized_len()];
195        st.serialize_into(&mut buf).unwrap();
196        let re = StSection::parse(&buf).unwrap();
197        assert_eq!(st, re);
198    }
199
200    #[test]
201    fn round_trip_empty() {
202        let st = StSection::new(vec![]);
203        let mut buf = vec![0u8; st.serialized_len()];
204        st.serialize_into(&mut buf).unwrap();
205        let re = StSection::parse(&buf).unwrap();
206        assert_eq!(st, re);
207    }
208
209    #[test]
210    fn round_trip_many_stuffs() {
211        let st = StSection::new(vec![0x00; 185]);
212        let mut buf = vec![0u8; st.serialized_len()];
213        st.serialize_into(&mut buf).unwrap();
214        let re = StSection::parse(&buf).unwrap();
215        assert_eq!(st, re);
216    }
217
218    #[test]
219    fn parse_rejects_buffer_too_short() {
220        let bytes = [0x72, 0x70]; // only 2 bytes
221        assert!(matches!(
222            StSection::parse(&bytes).unwrap_err(),
223            Error::BufferTooShort { need: 3, .. }
224        ));
225    }
226
227    #[test]
228    fn parse_rejects_section_length_exceeds_buffer() {
229        // section_length = 10 but only 2 payload bytes available after header
230        let bytes = [0x72, 0x70, 10, 0x00, 0x00];
231        assert!(matches!(
232            StSection::parse(&bytes).unwrap_err(),
233            Error::SectionLengthOverflow { .. }
234        ));
235    }
236
237    #[test]
238    fn table_trait_constants() {
239        assert_eq!(TABLE_ID, 0x72);
240        assert_eq!(PID, 0x0014);
241    }
242
243    #[test]
244    fn serialized_len_matches_wire_size() {
245        let st = StSection::new(vec![0x00; 50]);
246        assert_eq!(st.serialized_len(), HEADER_LEN + 50);
247    }
248
249    #[test]
250    fn to_bytes_produces_valid_section() {
251        let st = StSection::new(vec![0x00, 0x00]);
252        let bytes = st.to_bytes();
253        assert_eq!(bytes[0], TABLE_ID);
254        assert_eq!(bytes.len(), st.serialized_len());
255    }
256
257    #[test]
258    fn len_and_is_empty() {
259        let empty = StSection::new(vec![]);
260        assert!(empty.is_empty());
261        assert_eq!(empty.len(), 0);
262
263        let filled = StSection::new(vec![0x00; 10]);
264        assert!(!filled.is_empty());
265        assert_eq!(filled.len(), 10);
266    }
267
268    #[test]
269    fn new_constructor() {
270        let st = StSection::new(vec![0x00]);
271        assert_eq!(st.len(), 1);
272    }
273}