Skip to main content

dvb_si/tables/
sit.rs

1//! Selection Information Table — ETSI EN 300 468 §7.1.2.
2//!
3//! Carried on PID 0x001F with table_id 0x7F, only in partial transport streams
4//! (e.g. a recording). After the section header it has two loops:
5//!   1. `transmission_info_descriptors` — descriptors describing the whole
6//!      partial stream, prefixed by a 12-bit length;
7//!   2. a per-service loop: `service_id(16) + reserved_future_use(1) +
8//!      running_status(3) + service_descriptors_length(12) + descriptors`.
9//!
10//! Both loops are typed: the transmission-info loop is a [`DescriptorLoop`] and
11//! the service loop is a `Vec<SitService>`, each with its own descriptor loop.
12
13use crate::descriptors::DescriptorLoop;
14use crate::error::{Error, Result};
15use dvb_common::{Parse, Serialize};
16
17/// table_id for the Selection Information Table.
18pub const TABLE_ID: u8 = 0x7F;
19/// Well-known PID on which the SIT is carried.
20pub const PID: u16 = 0x001F;
21
22const MIN_HEADER_LEN: usize = 3;
23const EXTENSION_HEADER_LEN: usize = 5;
24const DESC_LOOP_LEN_FIELD: usize = 2;
25const CRC_LEN: usize = 4;
26/// Per-service fixed header: service_id(16) + reserved(1)/running_status(3)/
27/// service_descriptors_length(12) = 2 + 2 bytes.
28const SERVICE_HEADER_LEN: usize = 4;
29/// Maximum value of the 12-bit service_descriptors_length field.
30const MAX_SERVICE_DESC_LEN: usize = 0x0FFF;
31const MIN_SECTION_LEN: usize =
32    MIN_HEADER_LEN + EXTENSION_HEADER_LEN + DESC_LOOP_LEN_FIELD + CRC_LEN;
33
34/// One service entry in the SIT service loop (§7.1.2, Table 164).
35///
36/// Wire layout: `service_id(16) + reserved_future_use(1) + running_status(3) +
37/// service_descriptors_length(12) + descriptors`.
38#[derive(Debug, Clone, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize))]
40#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
41pub struct SitService<'a> {
42    /// service_id of this service.
43    pub service_id: u16,
44    /// 3-bit running_status (0=undefined .. 4=running).
45    pub running_status: u8,
46    /// Descriptor loop for this service. Serializes as the typed descriptor
47    /// sequence; `.raw()` yields the wire bytes.
48    pub descriptors: DescriptorLoop<'a>,
49}
50
51/// Selection Information Table (§7.1.2, Table 164).
52#[derive(Debug, Clone, PartialEq, Eq)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize))]
54#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
55pub struct SitSection<'a> {
56    /// 16-bit field after section_length — reserved_future_use for the SIT
57    /// (conventionally 0xFFFF); retained verbatim.
58    pub table_id_extension: u16,
59    /// 5-bit version_number.
60    pub version_number: u8,
61    /// current_next_indicator bit.
62    pub current_next_indicator: bool,
63    /// section_number in the sub-table sequence.
64    pub section_number: u8,
65    /// last_section_number in the sub-table sequence.
66    pub last_section_number: u8,
67    /// Transmission-info descriptor loop (the first loop). Serializes as the
68    /// typed descriptor sequence; `.raw()` yields the wire bytes.
69    pub transmission_info_descriptors: DescriptorLoop<'a>,
70    /// Per-service loop, in wire order.
71    pub services: Vec<SitService<'a>>,
72}
73
74impl<'a> Parse<'a> for SitSection<'a> {
75    type Error = crate::error::Error;
76
77    fn parse(bytes: &'a [u8]) -> Result<Self> {
78        let min_len = MIN_HEADER_LEN + EXTENSION_HEADER_LEN + DESC_LOOP_LEN_FIELD + CRC_LEN;
79        if bytes.len() < min_len {
80            return Err(Error::BufferTooShort {
81                need: min_len,
82                have: bytes.len(),
83                what: "SitSection",
84            });
85        }
86        if bytes[0] != TABLE_ID {
87            return Err(Error::UnexpectedTableId {
88                table_id: bytes[0],
89                what: "SitSection",
90                expected: &[TABLE_ID],
91            });
92        }
93        let section_length = ((bytes[1] & 0x0F) as usize) << 8 | bytes[2] as usize;
94        let total = super::check_section_length(
95            bytes.len(),
96            MIN_HEADER_LEN,
97            section_length,
98            MIN_SECTION_LEN,
99        )?;
100
101        let table_id_extension = u16::from_be_bytes([bytes[3], bytes[4]]);
102        let version_number = (bytes[5] >> 1) & 0x1F;
103        let current_next_indicator = (bytes[5] & 0x01) != 0;
104        let section_number = bytes[6];
105        let last_section_number = bytes[7];
106
107        let dl_pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN;
108        let ti_len = (((bytes[dl_pos] & 0x0F) as usize) << 8) | bytes[dl_pos + 1] as usize;
109        let ti_start = dl_pos + DESC_LOOP_LEN_FIELD;
110        let ti_end = ti_start + ti_len;
111        let crc_start = total - CRC_LEN;
112        if ti_end > crc_start {
113            return Err(Error::SectionLengthOverflow {
114                declared: ti_len,
115                available: crc_start.saturating_sub(ti_start),
116            });
117        }
118
119        // Everything between the transmission-info loop and the CRC is the
120        // per-service loop. Walk it entry by entry, boundary-checked.
121        let mut services = Vec::new();
122        let mut pos = ti_end;
123        while pos < crc_start {
124            if pos + SERVICE_HEADER_LEN > crc_start {
125                return Err(Error::BufferTooShort {
126                    need: pos + SERVICE_HEADER_LEN,
127                    have: crc_start,
128                    what: "SitService header",
129                });
130            }
131            let service_id = u16::from_be_bytes([bytes[pos], bytes[pos + 1]]);
132            // byte[pos+2]: reserved_future_use(1) | running_status(3) | len_hi(4)
133            let running_status = (bytes[pos + 2] >> 4) & 0x07;
134            let svc_desc_len = (((bytes[pos + 2] & 0x0F) as usize) << 8) | bytes[pos + 3] as usize;
135            let desc_start = pos + SERVICE_HEADER_LEN;
136            let desc_end = desc_start + svc_desc_len;
137            if desc_end > crc_start {
138                return Err(Error::SectionLengthOverflow {
139                    declared: svc_desc_len,
140                    available: crc_start.saturating_sub(desc_start),
141                });
142            }
143            services.push(SitService {
144                service_id,
145                running_status,
146                descriptors: DescriptorLoop::new(&bytes[desc_start..desc_end]),
147            });
148            pos = desc_end;
149        }
150
151        Ok(SitSection {
152            table_id_extension,
153            version_number,
154            current_next_indicator,
155            section_number,
156            last_section_number,
157            transmission_info_descriptors: DescriptorLoop::new(&bytes[ti_start..ti_end]),
158            services,
159        })
160    }
161}
162
163impl Serialize for SitSection<'_> {
164    type Error = crate::error::Error;
165
166    fn serialized_len(&self) -> usize {
167        let svc_bytes: usize = self
168            .services
169            .iter()
170            .map(|s| SERVICE_HEADER_LEN + s.descriptors.len())
171            .sum();
172        MIN_HEADER_LEN
173            + EXTENSION_HEADER_LEN
174            + DESC_LOOP_LEN_FIELD
175            + self.transmission_info_descriptors.len()
176            + svc_bytes
177            + CRC_LEN
178    }
179
180    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
181        // Reject over-range service descriptor loops up front — never truncate.
182        // service_descriptors_length is a 12-bit field (max 0x0FFF).
183        for svc in &self.services {
184            if svc.descriptors.len() > MAX_SERVICE_DESC_LEN {
185                return Err(Error::SectionLengthOverflow {
186                    declared: svc.descriptors.len(),
187                    available: MAX_SERVICE_DESC_LEN,
188                });
189            }
190        }
191        let len = self.serialized_len();
192        if buf.len() < len {
193            return Err(Error::OutputBufferTooSmall {
194                need: len,
195                have: buf.len(),
196            });
197        }
198        let section_length = (len - MIN_HEADER_LEN) as u16;
199        buf[0] = TABLE_ID;
200        buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
201        buf[2] = (section_length & 0xFF) as u8;
202        buf[3..5].copy_from_slice(&self.table_id_extension.to_be_bytes());
203        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
204        buf[6] = self.section_number;
205        buf[7] = self.last_section_number;
206
207        let dl_pos = MIN_HEADER_LEN + EXTENSION_HEADER_LEN;
208        let ti_len = self.transmission_info_descriptors.len() as u16;
209        buf[dl_pos] = 0xF0 | ((ti_len >> 8) as u8 & 0x0F);
210        buf[dl_pos + 1] = (ti_len & 0xFF) as u8;
211        let ti_start = dl_pos + DESC_LOOP_LEN_FIELD;
212        let ti_end = ti_start + self.transmission_info_descriptors.len();
213        buf[ti_start..ti_end].copy_from_slice(self.transmission_info_descriptors.raw());
214
215        let mut pos = ti_end;
216        for svc in &self.services {
217            buf[pos..pos + 2].copy_from_slice(&svc.service_id.to_be_bytes());
218            let dll = svc.descriptors.len() as u16;
219            // reserved_future_use(1) emitted as 1 (§5.1 convention) | running_status(3) | len_hi(4)
220            buf[pos + 2] = 0x80 | ((svc.running_status & 0x07) << 4) | ((dll >> 8) as u8 & 0x0F);
221            buf[pos + 3] = (dll & 0xFF) as u8;
222            let desc_start = pos + SERVICE_HEADER_LEN;
223            let desc_end = desc_start + svc.descriptors.len();
224            buf[desc_start..desc_end].copy_from_slice(svc.descriptors.raw());
225            pos = desc_end;
226        }
227
228        let crc_pos = len - CRC_LEN;
229        let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
230        buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
231        Ok(len)
232    }
233}
234impl<'a> crate::traits::TableDef<'a> for SitSection<'a> {
235    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
236    const NAME: &'static str = "SELECTION_INFORMATION";
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    fn build_sit(
244        table_id_extension: u16,
245        version: u8,
246        ti_desc: &[u8],
247        service_loop: &[u8],
248    ) -> Vec<u8> {
249        let section_length = (EXTENSION_HEADER_LEN
250            + DESC_LOOP_LEN_FIELD
251            + ti_desc.len()
252            + service_loop.len()
253            + CRC_LEN) as u16;
254        let mut v = vec![
255            TABLE_ID,
256            super::super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F),
257            (section_length & 0xFF) as u8,
258        ];
259        v.extend_from_slice(&table_id_extension.to_be_bytes());
260        v.push(0xC0 | ((version & 0x1F) << 1) | 0x01);
261        v.push(0x00);
262        v.push(0x00);
263        let dl = ti_desc.len() as u16;
264        v.push(0xF0 | ((dl >> 8) as u8 & 0x0F));
265        v.push((dl & 0xFF) as u8);
266        v.extend_from_slice(ti_desc);
267        v.extend_from_slice(service_loop);
268        v.extend_from_slice(&[0, 0, 0, 0]);
269        v
270    }
271
272    #[test]
273    fn parse_rejects_wrong_tag() {
274        let mut bytes = build_sit(0x1234, 5, &[], &[]);
275        bytes[0] = 0x7E;
276        assert!(matches!(
277            SitSection::parse(&bytes).unwrap_err(),
278            Error::UnexpectedTableId { table_id: 0x7E, .. }
279        ));
280    }
281
282    #[test]
283    fn parse_rejects_short_buffer() {
284        assert!(matches!(
285            SitSection::parse(&[0x7F, 0xB0]).unwrap_err(),
286            Error::BufferTooShort { .. }
287        ));
288    }
289
290    /// Encode one service entry: service_id + reserved(1)/running_status(3)/
291    /// svc_desc_len(12) + descriptor bytes.
292    fn service_entry(service_id: u16, running_status: u8, desc: &[u8]) -> Vec<u8> {
293        let dll = desc.len() as u16;
294        let mut v = Vec::new();
295        v.extend_from_slice(&service_id.to_be_bytes());
296        v.push(0x80 | ((running_status & 0x07) << 4) | ((dll >> 8) as u8 & 0x0F));
297        v.push((dll & 0xFF) as u8);
298        v.extend_from_slice(desc);
299        v
300    }
301
302    #[test]
303    fn parse_empty() {
304        let bytes = build_sit(0x1234, 5, &[], &[]);
305        let sit = SitSection::parse(&bytes).unwrap();
306        assert_eq!(sit.table_id_extension, 0x1234);
307        assert_eq!(sit.version_number, 5);
308        assert!(sit.current_next_indicator);
309        assert!(sit.transmission_info_descriptors.is_empty());
310        assert!(sit.services.is_empty());
311    }
312
313    #[test]
314    fn parse_two_services_typed() {
315        let ti = [0x4D, 0x02, 0x01, 0x02]; // a transmission-info descriptor
316        let mut sl = service_entry(0x0001, 4, &[0x48, 0x02, 0xAA, 0xBB]);
317        sl.extend(service_entry(0x0002, 2, &[]));
318        let bytes = build_sit(0xABCD, 7, &ti, &sl);
319        let sit = SitSection::parse(&bytes).unwrap();
320        assert_eq!(sit.transmission_info_descriptors.raw(), &ti[..]);
321        assert_eq!(sit.services.len(), 2);
322        assert_eq!(sit.services[0].service_id, 0x0001);
323        assert_eq!(sit.services[0].running_status, 4);
324        assert_eq!(
325            sit.services[0].descriptors.raw(),
326            &[0x48, 0x02, 0xAA, 0xBB][..]
327        );
328        assert_eq!(sit.services[1].service_id, 0x0002);
329        assert_eq!(sit.services[1].running_status, 2);
330        assert!(sit.services[1].descriptors.is_empty());
331    }
332
333    #[test]
334    fn parse_rejects_truncated_service_header() {
335        // Service loop has only 3 bytes — less than the 4-byte service header.
336        let ti = [0x4D, 0x00];
337        let bytes = build_sit(0x1234, 0, &ti, &[0x00, 0x01, 0xC0]);
338        assert!(matches!(
339            SitSection::parse(&bytes).unwrap_err(),
340            Error::BufferTooShort { .. }
341        ));
342    }
343
344    #[test]
345    fn parse_rejects_inner_descriptor_overflow() {
346        // service_descriptors_length declares 5 bytes but only 1 follows.
347        let service = [0x00, 0x01, 0x80, 0x05, 0xFF];
348        let bytes = build_sit(0x1234, 0, &[], &service);
349        assert!(matches!(
350            SitSection::parse(&bytes).unwrap_err(),
351            Error::SectionLengthOverflow { .. }
352        ));
353    }
354
355    #[test]
356    fn serialize_round_trip_two_services() {
357        let ti = [0x4D, 0x02, 0x01, 0x02];
358        let sit = SitSection {
359            table_id_extension: 0xCAFE,
360            version_number: 3,
361            current_next_indicator: true,
362            section_number: 0,
363            last_section_number: 0,
364            transmission_info_descriptors: DescriptorLoop::new(&ti),
365            services: vec![
366                SitService {
367                    service_id: 0x0001,
368                    running_status: 4,
369                    descriptors: DescriptorLoop::new(&[0x48, 0x02, 0xAA, 0xBB]),
370                },
371                SitService {
372                    service_id: 0x0002,
373                    running_status: 2,
374                    descriptors: DescriptorLoop::new(&[]),
375                },
376            ],
377        };
378        let mut buf = vec![0u8; sit.serialized_len()];
379        sit.serialize_into(&mut buf).unwrap();
380        // Byte-exact: re-parse must equal the original.
381        assert_eq!(SitSection::parse(&buf).unwrap(), sit);
382    }
383
384    #[test]
385    fn serialize_round_trip_empty() {
386        let bytes = build_sit(0x0001, 0, &[], &[]);
387        let sit = SitSection::parse(&bytes).unwrap();
388        let mut buf = vec![0u8; sit.serialized_len()];
389        sit.serialize_into(&mut buf).unwrap();
390        assert_eq!(SitSection::parse(&buf).unwrap(), sit);
391    }
392
393    #[test]
394    fn serialize_rejects_over_range_service_desc_len() {
395        // A service descriptor loop longer than the 12-bit field can hold.
396        let big = vec![0u8; MAX_SERVICE_DESC_LEN + 1];
397        let sit = SitSection {
398            table_id_extension: 0x0001,
399            version_number: 0,
400            current_next_indicator: true,
401            section_number: 0,
402            last_section_number: 0,
403            transmission_info_descriptors: DescriptorLoop::new(&[]),
404            services: vec![SitService {
405                service_id: 0x0001,
406                running_status: 4,
407                descriptors: DescriptorLoop::new(&big),
408            }],
409        };
410        let mut buf = vec![0u8; sit.serialized_len()];
411        assert!(matches!(
412            sit.serialize_into(&mut buf).unwrap_err(),
413            Error::SectionLengthOverflow { .. }
414        ));
415    }
416
417    #[test]
418    fn table_trait_constants() {
419        assert_eq!(TABLE_ID, 0x7F);
420        assert_eq!(PID, 0x001F);
421    }
422
423    #[test]
424    fn parse_rejects_zero_section_length() {
425        let mut buf = vec![0u8; 64];
426        buf[0] = TABLE_ID;
427        buf[1] = 0xF0;
428        buf[2] = 0x00;
429        for b in &mut buf[3..] {
430            *b = 0xFF;
431        }
432        assert!(matches!(
433            SitSection::parse(&buf).unwrap_err(),
434            Error::SectionLengthOverflow { .. }
435        ));
436    }
437
438    #[cfg(feature = "serde")]
439    #[test]
440    fn sit_serializes_typed_services() {
441        // Serialize-only (3.0). The transmission_info loop serializes as a typed
442        // descriptor sequence and each service exposes its typed descriptors.
443        let mut sl = service_entry(0x0001, 4, &[0x48, 0x02, 0xAA, 0xBB]);
444        sl.extend(service_entry(0x0002, 2, &[]));
445        let bytes = build_sit(0xDEAD, 9, &[0x4D, 0x00], &sl);
446        let sit = SitSection::parse(&bytes).unwrap();
447        let v = serde_json::to_value(&sit).unwrap();
448        assert!(
449            v["transmission_info_descriptors"].is_array(),
450            "transmission_info_descriptors must serialize as a typed sequence, got {v}"
451        );
452        assert!(
453            v["services"].is_array(),
454            "services must serialize as an array, got {v}"
455        );
456        assert_eq!(v["services"][0]["service_id"], 1);
457        assert_eq!(v["services"][0]["running_status"], 4);
458        // The service descriptor loop renders as a typed descriptor sequence.
459        assert!(
460            v["services"][0]["descriptors"].is_array(),
461            "service descriptors must serialize as a typed sequence, got {v}"
462        );
463    }
464}