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