Skip to main content

dvb_si/tables/
int.rs

1//! IP/MAC Notification Table — ETSI EN 301 192 v1.7.1 §8.4.
2//!
3//! INT is referenced by a `data_broadcast_id_descriptor` (data_broadcast_id 0x000B)
4//! in the PMT ES_info loop; there is no fixed PID.  table_id is 0x4C.
5//!
6//! The target/operational descriptor-loop pairs in the body loop are unfolded
7//! into [`IntLoopEntry`] instances (Tables 13/17/18, §8.4.4.1).
8
9use crate::descriptors::DescriptorLoop;
10use crate::error::{Error, Result};
11use dvb_common::{Parse, Serialize};
12
13/// `table_id` for IP/MAC Notification Table.
14pub const TABLE_ID: u8 = 0x4C;
15
16/// PID on which INT is carried.
17///
18/// INT does not have a fixed PID.  It is discovered through a
19/// `data_broadcast_id_descriptor` (data_broadcast_id 0x000B) inside the PMT
20/// ES_info loop.  This constant is therefore 0x0000 (unknown/variable).
21pub const PID: u16 = 0x0000;
22
23/// `action_type` value: IP/MAC stream announcement or location (Table 14).
24pub const ACTION_TYPE_STREAM_ANNOUNCEMENT: u8 = 0x01;
25
26const OUTER_HEADER_LEN: usize = 3;
27const INT_FIXED_LEN: usize = 9;
28const LOOP_LEN_FIELD: usize = 2;
29const CRC_LEN: usize = 4;
30const MIN_SECTION_LEN: usize = OUTER_HEADER_LEN + INT_FIXED_LEN + LOOP_LEN_FIELD + CRC_LEN;
31
32const OFF_ACTION_TYPE: usize = 3;
33const OFF_PLATFORM_ID_HASH: usize = 4;
34const OFF_VERSION_BYTE: usize = 5;
35const OFF_SECTION_NUMBER: usize = 6;
36const OFF_LAST_SECTION_NUMBER: usize = 7;
37const OFF_PLATFORM_ID: usize = 8;
38const OFF_PROCESSING_ORDER: usize = 11;
39const OFF_PLATFORM_DESC_LEN: usize = 12;
40
41const RESERVED_NIBBLE: u8 = 0xF0;
42
43/// A target/operational descriptor-loop pair in the INT body loop
44/// (Tables 17/18, §8.4.4.1).
45#[derive(Debug, Clone, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize))]
47pub struct IntLoopEntry<'a> {
48    /// Target descriptor loop — raw descriptor bytes (after the 12-bit length
49    /// field).  Serializes as the typed descriptor sequence; `.raw()` yields the
50    /// wire bytes.
51    pub target_descriptors: DescriptorLoop<'a>,
52    /// Operational descriptor loop — raw descriptor bytes (after the 12-bit
53    /// length field).  Serializes as the typed descriptor sequence; `.raw()`
54    /// yields the wire bytes.
55    pub operational_descriptors: DescriptorLoop<'a>,
56}
57
58fn int_loop_entry_serialized_len(e: &IntLoopEntry) -> usize {
59    LOOP_LEN_FIELD + e.target_descriptors.len() + LOOP_LEN_FIELD + e.operational_descriptors.len()
60}
61
62/// IP/MAC Notification Table (INT), ETSI EN 301 192 v1.7.1 §8.4, Table 13.
63///
64/// The `loops` field is unfolded into typed [`IntLoopEntry`] instances
65/// (target + operational descriptor-loop pairs).
66#[derive(Debug, Clone, PartialEq, Eq)]
67#[cfg_attr(feature = "serde", derive(serde::Serialize))]
68#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
69pub struct IntSection<'a> {
70    /// Semantics of this INT announcement — 0x01 = stream announcement/location.
71    pub action_type: u8,
72    /// 8-bit XOR hash over the 24-bit platform_id.
73    pub platform_id_hash: u8,
74    /// 5-bit version_number.
75    pub version_number: u8,
76    /// `current_next_indicator` bit.
77    pub current_next_indicator: bool,
78    /// section_number within this sub-table.
79    pub section_number: u8,
80    /// last_section_number in this sub-table.
81    pub last_section_number: u8,
82    /// 24-bit platform identifier (TS 101 162) in the low 24 bits of a u32.
83    pub platform_id: u32,
84    /// Processing order relative to other INT sections.
85    pub processing_order: u8,
86    /// The `platform_descriptor_loop` (descriptors only, not the 2-byte length
87    /// field). Serializes as the typed descriptor sequence; `.raw()` yields the
88    /// wire bytes.
89    pub platform_descriptors: DescriptorLoop<'a>,
90    /// Target/operational descriptor-loop pairs — unfolded per Tables 17/18.
91    pub loops: Vec<IntLoopEntry<'a>>,
92}
93
94impl<'a> Parse<'a> for IntSection<'a> {
95    type Error = crate::error::Error;
96
97    fn parse(bytes: &'a [u8]) -> Result<Self> {
98        if bytes.len() < MIN_SECTION_LEN {
99            return Err(Error::BufferTooShort {
100                need: MIN_SECTION_LEN,
101                have: bytes.len(),
102                what: "IntSection",
103            });
104        }
105        if bytes[0] != TABLE_ID {
106            return Err(Error::UnexpectedTableId {
107                table_id: bytes[0],
108                what: "IntSection",
109                expected: &[TABLE_ID],
110            });
111        }
112
113        let section_length = (((bytes[1] & 0x0F) as usize) << 8) | bytes[2] as usize;
114        let total = super::check_section_length(
115            bytes.len(),
116            OUTER_HEADER_LEN,
117            section_length,
118            MIN_SECTION_LEN,
119        )?;
120
121        let action_type = bytes[OFF_ACTION_TYPE];
122        let platform_id_hash = bytes[OFF_PLATFORM_ID_HASH];
123        let version_byte = bytes[OFF_VERSION_BYTE];
124        let version_number = (version_byte >> 1) & 0x1F;
125        let current_next_indicator = (version_byte & 0x01) != 0;
126        let section_number = bytes[OFF_SECTION_NUMBER];
127        let last_section_number = bytes[OFF_LAST_SECTION_NUMBER];
128        let platform_id = ((bytes[OFF_PLATFORM_ID] as u32) << 16)
129            | ((bytes[OFF_PLATFORM_ID + 1] as u32) << 8)
130            | bytes[OFF_PLATFORM_ID + 2] as u32;
131        let processing_order = bytes[OFF_PROCESSING_ORDER];
132
133        let plat_desc_len = (((bytes[OFF_PLATFORM_DESC_LEN] & 0x0F) as usize) << 8)
134            | bytes[OFF_PLATFORM_DESC_LEN + 1] as usize;
135        let plat_desc_start = OFF_PLATFORM_DESC_LEN + LOOP_LEN_FIELD;
136        let plat_desc_end = plat_desc_start + plat_desc_len;
137        if plat_desc_end > total - CRC_LEN {
138            return Err(Error::SectionLengthOverflow {
139                declared: plat_desc_len,
140                available: (total - CRC_LEN).saturating_sub(plat_desc_start),
141            });
142        }
143        let platform_descriptors = DescriptorLoop::new(&bytes[plat_desc_start..plat_desc_end]);
144
145        let payload_end = total - CRC_LEN;
146        let mut pos = plat_desc_end;
147        let mut loops = Vec::new();
148        while pos < payload_end {
149            if pos + LOOP_LEN_FIELD > payload_end {
150                return Err(Error::BufferTooShort {
151                    need: pos + LOOP_LEN_FIELD,
152                    have: payload_end,
153                    what: "IntSection target_descriptor_loop length",
154                });
155            }
156            let target_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
157            let target_start = pos + LOOP_LEN_FIELD;
158            let target_end = target_start + target_len;
159            if target_end > payload_end {
160                return Err(Error::SectionLengthOverflow {
161                    declared: target_len,
162                    available: payload_end.saturating_sub(target_start),
163                });
164            }
165            let target_descriptors = DescriptorLoop::new(&bytes[target_start..target_end]);
166            pos = target_end;
167
168            if pos + LOOP_LEN_FIELD > payload_end {
169                return Err(Error::BufferTooShort {
170                    need: pos + LOOP_LEN_FIELD,
171                    have: payload_end,
172                    what: "IntSection operational_descriptor_loop length",
173                });
174            }
175            let op_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
176            let op_start = pos + LOOP_LEN_FIELD;
177            let op_end = op_start + op_len;
178            if op_end > payload_end {
179                return Err(Error::SectionLengthOverflow {
180                    declared: op_len,
181                    available: payload_end.saturating_sub(op_start),
182                });
183            }
184            let operational_descriptors = DescriptorLoop::new(&bytes[op_start..op_end]);
185            pos = op_end;
186
187            loops.push(IntLoopEntry {
188                target_descriptors,
189                operational_descriptors,
190            });
191        }
192
193        Ok(IntSection {
194            action_type,
195            platform_id_hash,
196            version_number,
197            current_next_indicator,
198            section_number,
199            last_section_number,
200            platform_id,
201            processing_order,
202            platform_descriptors,
203            loops,
204        })
205    }
206}
207
208impl Serialize for IntSection<'_> {
209    type Error = crate::error::Error;
210
211    fn serialized_len(&self) -> usize {
212        OUTER_HEADER_LEN
213            + INT_FIXED_LEN
214            + LOOP_LEN_FIELD
215            + self.platform_descriptors.len()
216            + self
217                .loops
218                .iter()
219                .map(int_loop_entry_serialized_len)
220                .sum::<usize>()
221            + CRC_LEN
222    }
223
224    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
225        let len = self.serialized_len();
226        if buf.len() < len {
227            return Err(Error::OutputBufferTooSmall {
228                need: len,
229                have: buf.len(),
230            });
231        }
232
233        let section_length = (len - OUTER_HEADER_LEN) as u16;
234        buf[0] = TABLE_ID;
235        buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
236        buf[2] = (section_length & 0xFF) as u8;
237
238        buf[OFF_ACTION_TYPE] = self.action_type;
239        buf[OFF_PLATFORM_ID_HASH] = self.platform_id_hash;
240        buf[OFF_VERSION_BYTE] =
241            0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
242        buf[OFF_SECTION_NUMBER] = self.section_number;
243        buf[OFF_LAST_SECTION_NUMBER] = self.last_section_number;
244        buf[OFF_PLATFORM_ID] = ((self.platform_id >> 16) & 0xFF) as u8;
245        buf[OFF_PLATFORM_ID + 1] = ((self.platform_id >> 8) & 0xFF) as u8;
246        buf[OFF_PLATFORM_ID + 2] = (self.platform_id & 0xFF) as u8;
247        buf[OFF_PROCESSING_ORDER] = self.processing_order;
248
249        let pdl = self.platform_descriptors.len() as u16;
250        buf[OFF_PLATFORM_DESC_LEN] = RESERVED_NIBBLE | ((pdl >> 8) as u8 & 0x0F);
251        buf[OFF_PLATFORM_DESC_LEN + 1] = (pdl & 0xFF) as u8;
252
253        let plat_start = OFF_PLATFORM_DESC_LEN + LOOP_LEN_FIELD;
254        let plat_end = plat_start + self.platform_descriptors.len();
255        buf[plat_start..plat_end].copy_from_slice(self.platform_descriptors.raw());
256
257        let mut pos = plat_end;
258        for entry in &self.loops {
259            let tl = entry.target_descriptors.len() as u16;
260            buf[pos] = RESERVED_NIBBLE | ((tl >> 8) as u8 & 0x0F);
261            buf[pos + 1] = (tl & 0xFF) as u8;
262            pos += LOOP_LEN_FIELD;
263            buf[pos..pos + entry.target_descriptors.len()]
264                .copy_from_slice(entry.target_descriptors.raw());
265            pos += entry.target_descriptors.len();
266
267            let ol = entry.operational_descriptors.len() as u16;
268            buf[pos] = RESERVED_NIBBLE | ((ol >> 8) as u8 & 0x0F);
269            buf[pos + 1] = (ol & 0xFF) as u8;
270            pos += LOOP_LEN_FIELD;
271            buf[pos..pos + entry.operational_descriptors.len()]
272                .copy_from_slice(entry.operational_descriptors.raw());
273            pos += entry.operational_descriptors.len();
274        }
275
276        let crc_pos = len - CRC_LEN;
277        let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
278        buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
279        Ok(len)
280    }
281}
282impl<'a> crate::traits::TableDef<'a> for IntSection<'a> {
283    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
284    const NAME: &'static str = "IP_MAC_NOTIFICATION";
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn parse_happy_path_no_loops() {
293        let plat_desc: &[u8] = &[0x81, 0x02, 0xAB, 0xCD];
294        let int = IntSection {
295            action_type: ACTION_TYPE_STREAM_ANNOUNCEMENT,
296            platform_id_hash: 0x12 ^ 0x34,
297            version_number: 3,
298            current_next_indicator: true,
299            section_number: 0,
300            last_section_number: 0,
301            platform_id: 0x00_12_34,
302            processing_order: 0x00,
303            platform_descriptors: DescriptorLoop::new(plat_desc),
304            loops: Vec::new(),
305        };
306        let mut buf = vec![0u8; int.serialized_len()];
307        int.serialize_into(&mut buf).unwrap();
308        let parsed = IntSection::parse(&buf).unwrap();
309        assert_eq!(parsed.action_type, ACTION_TYPE_STREAM_ANNOUNCEMENT);
310        assert_eq!(parsed.platform_id, 0x00_12_34);
311        assert_eq!(parsed.platform_descriptors.raw(), plat_desc);
312        assert!(parsed.loops.is_empty());
313    }
314
315    #[test]
316    fn parse_happy_path_with_loops() {
317        let target_desc: &[u8] = &[0x09, 0x01, 0xAA];
318        let op_desc: &[u8] = &[0x0A, 0x01, 0xBB];
319        let int = IntSection {
320            action_type: 0x01,
321            platform_id_hash: 0x56,
322            version_number: 5,
323            current_next_indicator: false,
324            section_number: 1,
325            last_section_number: 1,
326            platform_id: 0x00_56_78,
327            processing_order: 0x01,
328            platform_descriptors: DescriptorLoop::new(&[]),
329            loops: vec![
330                IntLoopEntry {
331                    target_descriptors: DescriptorLoop::new(target_desc),
332                    operational_descriptors: DescriptorLoop::new(op_desc),
333                },
334                IntLoopEntry {
335                    target_descriptors: DescriptorLoop::new(&[]),
336                    operational_descriptors: DescriptorLoop::new(&[]),
337                },
338            ],
339        };
340        let mut buf = vec![0u8; int.serialized_len()];
341        int.serialize_into(&mut buf).unwrap();
342        let parsed = IntSection::parse(&buf).unwrap();
343        assert_eq!(parsed.loops.len(), 2);
344        assert_eq!(parsed.loops[0].target_descriptors.raw(), target_desc);
345        assert_eq!(parsed.loops[0].operational_descriptors.raw(), op_desc);
346        assert_eq!(parsed.loops[1].target_descriptors.len(), 0);
347        assert_eq!(parsed.loops[1].operational_descriptors.len(), 0);
348    }
349
350    #[test]
351    fn byte_exact_round_trip() {
352        let plat_desc: &[u8] = &[0x7C, 0x04, 0x01, 0x02, 0x03, 0x04];
353        let int = IntSection {
354            action_type: ACTION_TYPE_STREAM_ANNOUNCEMENT,
355            platform_id_hash: 0xAB,
356            version_number: 15,
357            current_next_indicator: true,
358            section_number: 2,
359            last_section_number: 3,
360            platform_id: 0x00_AB_CD,
361            processing_order: 0x00,
362            platform_descriptors: DescriptorLoop::new(plat_desc),
363            loops: vec![IntLoopEntry {
364                target_descriptors: DescriptorLoop::new(&[]),
365                operational_descriptors: DescriptorLoop::new(&[]),
366            }],
367        };
368        let mut buf = vec![0u8; int.serialized_len()];
369        int.serialize_into(&mut buf).unwrap();
370        let re = IntSection::parse(&buf).unwrap();
371        let mut buf2 = vec![0u8; re.serialized_len()];
372        re.serialize_into(&mut buf2).unwrap();
373        assert_eq!(buf, buf2, "byte-exact re-serialize");
374        assert_eq!(re, int);
375    }
376
377    #[test]
378    fn parse_rejects_wrong_table_id() {
379        let int = IntSection {
380            action_type: 0x01,
381            platform_id_hash: 0x00,
382            version_number: 0,
383            current_next_indicator: true,
384            section_number: 0,
385            last_section_number: 0,
386            platform_id: 0,
387            processing_order: 0,
388            platform_descriptors: DescriptorLoop::new(&[]),
389            loops: Vec::new(),
390        };
391        let mut buf = vec![0u8; int.serialized_len()];
392        int.serialize_into(&mut buf).unwrap();
393        buf[0] = 0x4B;
394        assert!(matches!(
395            IntSection::parse(&buf).unwrap_err(),
396            Error::UnexpectedTableId { table_id: 0x4B, .. }
397        ));
398    }
399
400    #[test]
401    fn parse_rejects_buffer_too_short() {
402        assert!(matches!(
403            IntSection::parse(&[TABLE_ID, 0xF0]).unwrap_err(),
404            Error::BufferTooShort {
405                what: "IntSection",
406                ..
407            }
408        ));
409    }
410
411    #[test]
412    fn serialize_rejects_too_small_output_buffer() {
413        let int = IntSection {
414            action_type: 0x01,
415            platform_id_hash: 0x00,
416            version_number: 0,
417            current_next_indicator: true,
418            section_number: 0,
419            last_section_number: 0,
420            platform_id: 0,
421            processing_order: 0,
422            platform_descriptors: DescriptorLoop::new(&[]),
423            loops: Vec::new(),
424        };
425        let mut buf = vec![0u8; 2];
426        assert!(matches!(
427            int.serialize_into(&mut buf).unwrap_err(),
428            Error::OutputBufferTooSmall { .. }
429        ));
430    }
431
432    #[test]
433    fn parse_rejects_zero_section_length() {
434        let mut buf = vec![0u8; 64];
435        buf[0] = TABLE_ID;
436        buf[1] = 0xF0;
437        buf[2] = 0x00;
438        for b in &mut buf[3..] {
439            *b = 0xFF;
440        }
441        assert!(matches!(
442            IntSection::parse(&buf).unwrap_err(),
443            Error::SectionLengthOverflow { .. }
444        ));
445    }
446
447    #[test]
448    fn platform_id_24bit_boundary() {
449        let int = IntSection {
450            action_type: 0x01,
451            platform_id_hash: 0xFF,
452            version_number: 0,
453            current_next_indicator: true,
454            section_number: 0,
455            last_section_number: 0,
456            platform_id: 0x00FF_FFFF,
457            processing_order: 0x00,
458            platform_descriptors: DescriptorLoop::new(&[]),
459            loops: Vec::new(),
460        };
461        let mut buf = vec![0u8; int.serialized_len()];
462        int.serialize_into(&mut buf).unwrap();
463        let parsed = IntSection::parse(&buf).unwrap();
464        assert_eq!(parsed.platform_id, 0x00FF_FFFF);
465    }
466
467    #[test]
468    fn parse_handwritten_int_no_loops() {
469        let mut bytes: Vec<u8> = vec![
470            0x4C, 0xF0, 0x0F, 0x01, 0x00, 0xC7, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xF0, 0x00,
471        ];
472        let crc = dvb_common::crc32_mpeg2::compute(&bytes);
473        bytes.extend_from_slice(&crc.to_be_bytes());
474        let int = IntSection::parse(&bytes).unwrap();
475        assert_eq!(int.action_type, 0x01);
476        assert_eq!(int.platform_id, 0x000001);
477        assert_eq!(int.version_number, 3);
478        assert!(int.current_next_indicator);
479        assert!(int.loops.is_empty());
480    }
481}