Skip to main content

dvb_si/tables/
rnt.rs

1//! Resolution provider Notification Table — ETSI TS 102 323 v1.4.1 §5.2.2.
2//!
3//! Carries the locations of CRI (Content Referencing Information) and metadata
4//! for CRID authorities. Carried on PID 0x0016 with table_id 0x79.
5//!
6//! The resolution-provider loop internals (name bytes, per-provider descriptors,
7//! CRID authority sub-loops) are kept as raw bytes — callers that need them
8//! can walk `resolution_providers` directly.
9
10use crate::error::{Error, Result};
11use crate::traits::Table;
12use dvb_common::{Parse, Serialize};
13
14/// table_id for the Resolution provider Notification Table.
15pub const TABLE_ID: u8 = 0x79;
16/// Well-known PID on which RNT sections are carried.
17pub const PID: u16 = 0x0016;
18
19/// Byte offset of the 3-byte outer header (table_id + section_length word).
20const HEADER_LEN: usize = 3;
21/// Bytes in the extension header: context_id(2) + version/cni byte(1)
22/// + section_number(1) + last_section_number(1) + context_id_type(1) = 6.
23const EXTENSION_HEADER_LEN: usize = 6;
24/// Bytes for the common_descriptors_length field (2, carries a reserved nibble + 12-bit length).
25const COMMON_DESC_LEN_FIELD: usize = 2;
26/// Bytes consumed by the CRC-32 trailer.
27const CRC_LEN: usize = 4;
28/// Minimum total bytes required to attempt parsing.
29const MIN_LEN: usize = HEADER_LEN + EXTENSION_HEADER_LEN + COMMON_DESC_LEN_FIELD + CRC_LEN;
30
31/// Resolution provider Notification Table (ETSI TS 102 323 v1.4.1 §5.2.2).
32///
33/// Variable-length fields are kept as borrowed byte slices so no allocation is
34/// required. The `resolution_providers` slice contains everything between the
35/// end of the common-descriptor loop and the CRC-32 trailer; the internal
36/// sub-structure (provider names, per-provider descriptors, CRID authority
37/// loops) is not parsed further.
38#[derive(Debug, Clone, PartialEq, Eq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct Rnt<'a> {
41    /// 16-bit context identifier (table_id_extension at bytes 3–4).
42    pub context_id: u16,
43    /// 5-bit version_number.
44    pub version_number: u8,
45    /// current_next_indicator bit.
46    pub current_next_indicator: bool,
47    /// section_number in the sub-table sequence.
48    pub section_number: u8,
49    /// last_section_number in the sub-table sequence.
50    pub last_section_number: u8,
51    /// context_id_type byte (0x00 = bouquet_id, 0x01 = original_network_id,
52    /// 0x02 = network_id, 0x03–0x7F DVB reserved, 0x80–0xFF user defined).
53    pub context_id_type: u8,
54    /// Raw bytes of the common descriptor loop (`common_descriptors_length` bytes).
55    #[cfg_attr(feature = "serde", serde(borrow))]
56    pub common_descriptors: &'a [u8],
57    /// Raw bytes of the resolution-provider loop (everything after the common
58    /// descriptors up to, but not including, the CRC-32 trailer).
59    #[cfg_attr(feature = "serde", serde(borrow))]
60    pub resolution_providers: &'a [u8],
61}
62
63impl<'a> Parse<'a> for Rnt<'a> {
64    type Error = crate::error::Error;
65
66    fn parse(bytes: &'a [u8]) -> Result<Self> {
67        if bytes.len() < MIN_LEN {
68            return Err(Error::BufferTooShort {
69                need: MIN_LEN,
70                have: bytes.len(),
71                what: "Rnt",
72            });
73        }
74
75        if bytes[0] != TABLE_ID {
76            return Err(Error::UnexpectedTableId {
77                table_id: bytes[0],
78                what: "Rnt",
79                expected: &[TABLE_ID],
80            });
81        }
82
83        // bytes[1] = section_syntax_indicator(1) | reserved(1) | reserved(2) | section_length[11:8](4)
84        // bytes[2] = section_length[7:0]
85        let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
86        let total = HEADER_LEN + section_length as usize;
87        if bytes.len() < total {
88            return Err(Error::SectionLengthOverflow {
89                declared: section_length as usize,
90                available: bytes.len() - HEADER_LEN,
91            });
92        }
93
94        // Extension header (bytes 3–8):
95        //   bytes[3..5]  = context_id (table_id_extension)
96        //   bytes[5]     = reserved(2) | version_number(5) | current_next_indicator(1)
97        //   bytes[6]     = section_number
98        //   bytes[7]     = last_section_number
99        //   bytes[8]     = context_id_type
100        let context_id = u16::from_be_bytes([bytes[3], bytes[4]]);
101        let version_number = (bytes[5] >> 1) & 0x1F;
102        let current_next_indicator = (bytes[5] & 0x01) != 0;
103        let section_number = bytes[6];
104        let last_section_number = bytes[7];
105        let context_id_type = bytes[8];
106
107        // bytes[9..11] = reserved(4) | common_descriptors_length(12)
108        let common_desc_len_pos = HEADER_LEN + EXTENSION_HEADER_LEN;
109        let common_descriptors_length = (((bytes[common_desc_len_pos] & 0x0F) as usize) << 8)
110            | bytes[common_desc_len_pos + 1] as usize;
111
112        let common_desc_start = common_desc_len_pos + COMMON_DESC_LEN_FIELD;
113        let common_desc_end = common_desc_start + common_descriptors_length;
114
115        if common_desc_end > total - CRC_LEN {
116            return Err(Error::SectionLengthOverflow {
117                declared: common_descriptors_length,
118                available: (total - CRC_LEN).saturating_sub(common_desc_start),
119            });
120        }
121
122        let common_descriptors = &bytes[common_desc_start..common_desc_end];
123
124        // Everything from the end of the common descriptor loop up to (but not
125        // including) the 4-byte CRC trailer is the resolution-provider loop.
126        let rp_start = common_desc_end;
127        let rp_end = total - CRC_LEN;
128        let resolution_providers = &bytes[rp_start..rp_end];
129
130        Ok(Rnt {
131            context_id,
132            version_number,
133            current_next_indicator,
134            section_number,
135            last_section_number,
136            context_id_type,
137            common_descriptors,
138            resolution_providers,
139        })
140    }
141}
142
143impl Serialize for Rnt<'_> {
144    type Error = crate::error::Error;
145
146    fn serialized_len(&self) -> usize {
147        HEADER_LEN
148            + EXTENSION_HEADER_LEN
149            + COMMON_DESC_LEN_FIELD
150            + self.common_descriptors.len()
151            + self.resolution_providers.len()
152            + CRC_LEN
153    }
154
155    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
156        let len = self.serialized_len();
157        if buf.len() < len {
158            return Err(Error::OutputBufferTooSmall {
159                need: len,
160                have: buf.len(),
161            });
162        }
163
164        // Outer header.
165        let section_length = (len - HEADER_LEN) as u16;
166        buf[0] = TABLE_ID;
167        // section_syntax_indicator=1, reserved=1, reserved=2, section_length top nibble.
168        buf[1] = 0xB0 | ((section_length >> 8) as u8 & 0x0F);
169        buf[2] = (section_length & 0xFF) as u8;
170
171        // Extension header.
172        buf[3..5].copy_from_slice(&self.context_id.to_be_bytes());
173        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
174        buf[6] = self.section_number;
175        buf[7] = self.last_section_number;
176        buf[8] = self.context_id_type;
177
178        // common_descriptors_length field (reserved nibble = 0xF).
179        let cdl = self.common_descriptors.len() as u16;
180        let cdl_pos = HEADER_LEN + EXTENSION_HEADER_LEN;
181        buf[cdl_pos] = 0xF0 | ((cdl >> 8) as u8 & 0x0F);
182        buf[cdl_pos + 1] = (cdl & 0xFF) as u8;
183
184        // Common descriptors.
185        let cd_start = cdl_pos + COMMON_DESC_LEN_FIELD;
186        let cd_end = cd_start + self.common_descriptors.len();
187        buf[cd_start..cd_end].copy_from_slice(self.common_descriptors);
188
189        // Resolution-provider loop (opaque bytes).
190        let rp_end = cd_end + self.resolution_providers.len();
191        buf[cd_end..rp_end].copy_from_slice(self.resolution_providers);
192
193        // CRC-32: compute over everything up to (but not including) the CRC slot.
194        let crc_pos = len - CRC_LEN;
195        let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
196        buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
197
198        Ok(len)
199    }
200}
201
202impl<'a> Table<'a> for Rnt<'a> {
203    const TABLE_ID: u8 = TABLE_ID;
204    const PID: u16 = PID;
205}
206
207impl<'a> crate::traits::TableDef<'a> for Rnt<'a> {
208    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
209    const NAME: &'static str = "RELATED_AND_NEIGHBOURING";
210}
211
212#[cfg(test)]
213mod tests {
214    use super::*;
215
216    /// Build a complete RNT section byte vector with the given field values.
217    /// `common_desc` and `resolution_providers` are pasted in verbatim; the
218    /// CRC slot is zeroed (matching the serialize contract).
219    #[allow(clippy::too_many_arguments)]
220    fn build_rnt(
221        context_id: u16,
222        version: u8,
223        current_next: bool,
224        section_number: u8,
225        last_section_number: u8,
226        context_id_type: u8,
227        common_desc: &[u8],
228        resolution_providers: &[u8],
229    ) -> Vec<u8> {
230        let rnt = Rnt {
231            context_id,
232            version_number: version,
233            current_next_indicator: current_next,
234            section_number,
235            last_section_number,
236            context_id_type,
237            common_descriptors: common_desc,
238            resolution_providers,
239        };
240        let mut buf = vec![0u8; rnt.serialized_len()];
241        rnt.serialize_into(&mut buf).unwrap();
242        buf
243    }
244
245    #[test]
246    fn parse_happy_path() {
247        // context_id 0x0042, version 3, CNI=true, section 0/0,
248        // context_id_type 0x01 (original_network_id),
249        // one dummy common descriptor (tag=0x83, length=2, data=[0xAB, 0xCD]),
250        // one minimal resolution-provider stub (6 opaque bytes).
251        let common_desc = [0x83u8, 0x02, 0xAB, 0xCD];
252        let rp_bytes = [0xF0u8, 0x00, 0x02, b'b', b'b', 0xF0, 0x00];
253        let bytes = build_rnt(0x0042, 3, true, 0, 0, 0x01, &common_desc, &rp_bytes);
254
255        let rnt = Rnt::parse(&bytes).unwrap();
256        assert_eq!(rnt.context_id, 0x0042);
257        assert_eq!(rnt.version_number, 3);
258        assert!(rnt.current_next_indicator);
259        assert_eq!(rnt.section_number, 0);
260        assert_eq!(rnt.last_section_number, 0);
261        assert_eq!(rnt.context_id_type, 0x01);
262        assert_eq!(rnt.common_descriptors, &common_desc[..]);
263        assert_eq!(rnt.resolution_providers, &rp_bytes[..]);
264    }
265
266    #[test]
267    fn parse_no_descriptors_no_providers() {
268        let bytes = build_rnt(0x0000, 0, false, 0, 0, 0x00, &[], &[]);
269        let rnt = Rnt::parse(&bytes).unwrap();
270        assert_eq!(rnt.common_descriptors.len(), 0);
271        assert_eq!(rnt.resolution_providers.len(), 0);
272    }
273
274    #[test]
275    fn parse_rejects_wrong_table_id() {
276        let mut bytes = build_rnt(0x0001, 0, true, 0, 0, 0x00, &[], &[]);
277        bytes[0] = 0x70; // not 0x79
278        let err = Rnt::parse(&bytes).unwrap_err();
279        assert!(matches!(
280            err,
281            Error::UnexpectedTableId { table_id: 0x70, .. }
282        ));
283    }
284
285    #[test]
286    fn parse_rejects_short_buffer() {
287        let err = Rnt::parse(&[0x79, 0x00]).unwrap_err();
288        assert!(matches!(err, Error::BufferTooShort { .. }));
289    }
290
291    #[test]
292    fn serialize_round_trip() {
293        let common_desc = [0x40u8, 0x03, b'R', b'N', b'T'];
294        // Minimal resolution-provider entry: 2-byte length header (0 bytes length),
295        // 1-byte name length (0), 2-byte provider descriptors length (0).
296        let rp_bytes = [0xF0u8, 0x03, 0x00, 0xF0, 0x00];
297        let rnt = Rnt {
298            context_id: 0xABCD,
299            version_number: 15,
300            current_next_indicator: true,
301            section_number: 1,
302            last_section_number: 2,
303            context_id_type: 0x02,
304            common_descriptors: &common_desc,
305            resolution_providers: &rp_bytes,
306        };
307
308        let mut buf = vec![0u8; rnt.serialized_len()];
309        rnt.serialize_into(&mut buf).unwrap();
310        let parsed = Rnt::parse(&buf).unwrap();
311        assert_eq!(rnt, parsed);
312    }
313
314    #[test]
315    fn serialize_rejects_too_small_buffer() {
316        let rnt = Rnt {
317            context_id: 0x0001,
318            version_number: 0,
319            current_next_indicator: true,
320            section_number: 0,
321            last_section_number: 0,
322            context_id_type: 0x00,
323            common_descriptors: &[],
324            resolution_providers: &[],
325        };
326        let mut buf = vec![0u8; 2];
327        let err = rnt.serialize_into(&mut buf).unwrap_err();
328        assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
329    }
330}