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 is unfolded into [`ResolutionProvider`] and
7//! [`CridAuthority`] entries (Table 1, §5.2.2).
8
9use crate::descriptors::DescriptorLoop;
10use crate::error::{Error, Result};
11use crate::text::DvbText;
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
19const HEADER_LEN: usize = 3;
20const EXTENSION_HEADER_LEN: usize = 6;
21const COMMON_DESC_LEN_FIELD: usize = 2;
22const CRC_LEN: usize = 4;
23const MIN_LEN: usize = HEADER_LEN + EXTENSION_HEADER_LEN + COMMON_DESC_LEN_FIELD + CRC_LEN;
24
25const RP_INFO_LEN_FIELD: usize = 2;
26const RP_NAME_LEN_FIELD: usize = 1;
27const RP_DESC_LEN_FIELD: usize = 2;
28const CA_NAME_LEN_FIELD: usize = 1;
29const CA_HEADER_LEN: usize = 2;
30
31const RESERVED_NIBBLE: u8 = 0xF0;
32
33/// A CRID authority entry within a resolution provider (Table 1, §5.2.2).
34#[derive(Debug, Clone, PartialEq, Eq)]
35#[cfg_attr(feature = "serde", derive(serde::Serialize))]
36pub struct CridAuthority<'a> {
37    /// CRID authority name (EN 300 468 Annex A text).
38    pub name: DvbText<'a>,
39    /// `CRID_authority_policy` — 2-bit value (Table 3):
40    /// 0 = permanent, 1 = transient, 2 = either, 3 = reserved.
41    pub crid_authority_policy: u8,
42    /// CRID authority descriptor loop.
43    pub descriptors: DescriptorLoop<'a>,
44}
45
46/// A resolution-provider entry (Table 1, §5.2.2).
47#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49pub struct ResolutionProvider<'a> {
50    /// Resolution provider name (EN 300 468 Annex A text).
51    pub name: DvbText<'a>,
52    /// Per-provider descriptor loop.
53    pub descriptors: DescriptorLoop<'a>,
54    /// CRID authority sub-entries.
55    pub crid_authorities: Vec<CridAuthority<'a>>,
56}
57
58fn crid_authority_serialized_len(ca: &CridAuthority) -> usize {
59    CA_NAME_LEN_FIELD + ca.name.len() + CA_HEADER_LEN + ca.descriptors.len()
60}
61
62fn resolution_provider_serialized_len(rp: &ResolutionProvider) -> usize {
63    RP_NAME_LEN_FIELD
64        + rp.name.len()
65        + RP_DESC_LEN_FIELD
66        + rp.descriptors.len()
67        + rp.crid_authorities
68            .iter()
69            .map(crid_authority_serialized_len)
70            .sum::<usize>()
71}
72
73/// Resolution provider Notification Table (ETSI TS 102 323 v1.4.1 §5.2.2,
74/// Table 1).
75///
76/// The resolution-provider loop is unfolded into typed
77/// [`ResolutionProvider`] entries.
78#[derive(Debug, Clone, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize))]
80#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
81pub struct RntSection<'a> {
82    /// 16-bit context identifier (table_id_extension).
83    pub context_id: u16,
84    /// 5-bit version_number.
85    pub version_number: u8,
86    /// `current_next_indicator` bit.
87    pub current_next_indicator: bool,
88    /// section_number in the sub-table sequence.
89    pub section_number: u8,
90    /// last_section_number in the sub-table sequence.
91    pub last_section_number: u8,
92    /// `context_id_type` byte (Table 2).
93    pub context_id_type: u8,
94    /// Common descriptor loop. Serializes as the typed descriptor sequence;
95    /// `.raw()` yields the wire bytes.
96    pub common_descriptors: DescriptorLoop<'a>,
97    /// Resolution-provider entries — unfolded per Table 1.
98    pub resolution_providers: Vec<ResolutionProvider<'a>>,
99}
100
101impl<'a> Parse<'a> for RntSection<'a> {
102    type Error = crate::error::Error;
103
104    fn parse(bytes: &'a [u8]) -> Result<Self> {
105        if bytes.len() < MIN_LEN {
106            return Err(Error::BufferTooShort {
107                need: MIN_LEN,
108                have: bytes.len(),
109                what: "RntSection",
110            });
111        }
112        if bytes[0] != TABLE_ID {
113            return Err(Error::UnexpectedTableId {
114                table_id: bytes[0],
115                what: "RntSection",
116                expected: &[TABLE_ID],
117            });
118        }
119
120        let section_length = ((bytes[1] & 0x0F) as u16) << 8 | bytes[2] as u16;
121        let total =
122            super::check_section_length(bytes.len(), HEADER_LEN, section_length as usize, MIN_LEN)?;
123
124        let context_id = u16::from_be_bytes([bytes[3], bytes[4]]);
125        let version_number = (bytes[5] >> 1) & 0x1F;
126        let current_next_indicator = (bytes[5] & 0x01) != 0;
127        let section_number = bytes[6];
128        let last_section_number = bytes[7];
129        let context_id_type = bytes[8];
130
131        let common_desc_len_pos = HEADER_LEN + EXTENSION_HEADER_LEN;
132        let common_descriptors_length = (((bytes[common_desc_len_pos] & 0x0F) as usize) << 8)
133            | bytes[common_desc_len_pos + 1] as usize;
134        let common_desc_start = common_desc_len_pos + COMMON_DESC_LEN_FIELD;
135        let common_desc_end = common_desc_start + common_descriptors_length;
136        if common_desc_end > total - CRC_LEN {
137            return Err(Error::SectionLengthOverflow {
138                declared: common_descriptors_length,
139                available: (total - CRC_LEN).saturating_sub(common_desc_start),
140            });
141        }
142        let common_descriptors = DescriptorLoop::new(&bytes[common_desc_start..common_desc_end]);
143
144        let payload_end = total - CRC_LEN;
145        let mut pos = common_desc_end;
146        let mut resolution_providers = Vec::new();
147
148        while pos < payload_end {
149            if pos + RP_INFO_LEN_FIELD > payload_end {
150                return Err(Error::BufferTooShort {
151                    need: pos + RP_INFO_LEN_FIELD,
152                    have: payload_end,
153                    what: "RntSection resolution_provider_info_length",
154                });
155            }
156            let rp_info_length = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
157            pos += RP_INFO_LEN_FIELD;
158            let rp_end = pos + rp_info_length;
159            if rp_end > payload_end {
160                return Err(Error::SectionLengthOverflow {
161                    declared: rp_info_length,
162                    available: payload_end.saturating_sub(pos),
163                });
164            }
165
166            if pos + RP_NAME_LEN_FIELD > rp_end {
167                return Err(Error::BufferTooShort {
168                    need: pos + RP_NAME_LEN_FIELD,
169                    have: rp_end,
170                    what: "RntSection resolution_provider_name_length",
171                });
172            }
173            let name_len = bytes[pos] as usize;
174            pos += RP_NAME_LEN_FIELD;
175            if pos + name_len > rp_end {
176                return Err(Error::BufferTooShort {
177                    need: pos + name_len,
178                    have: rp_end,
179                    what: "RntSection resolution_provider_name",
180                });
181            }
182            let name = DvbText::new(&bytes[pos..pos + name_len]);
183            pos += name_len;
184
185            if pos + RP_DESC_LEN_FIELD > rp_end {
186                return Err(Error::BufferTooShort {
187                    need: pos + RP_DESC_LEN_FIELD,
188                    have: rp_end,
189                    what: "RntSection resolution_provider_descriptors_length",
190                });
191            }
192            let rp_desc_len = (((bytes[pos] & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
193            pos += RP_DESC_LEN_FIELD;
194            let rp_desc_start = pos;
195            let rp_desc_end = rp_desc_start + rp_desc_len;
196            if rp_desc_end > rp_end {
197                return Err(Error::SectionLengthOverflow {
198                    declared: rp_desc_len,
199                    available: rp_end.saturating_sub(rp_desc_start),
200                });
201            }
202            let descriptors = DescriptorLoop::new(&bytes[rp_desc_start..rp_desc_end]);
203            pos = rp_desc_end;
204
205            let mut crid_authorities = Vec::new();
206            while pos < rp_end {
207                if pos + CA_NAME_LEN_FIELD > rp_end {
208                    return Err(Error::BufferTooShort {
209                        need: pos + CA_NAME_LEN_FIELD,
210                        have: rp_end,
211                        what: "RntSection CRID_authority_name_length",
212                    });
213                }
214                let ca_name_len = bytes[pos] as usize;
215                pos += CA_NAME_LEN_FIELD;
216                if pos + ca_name_len > rp_end {
217                    return Err(Error::BufferTooShort {
218                        need: pos + ca_name_len,
219                        have: rp_end,
220                        what: "RntSection CRID_authority_name",
221                    });
222                }
223                let ca_name = DvbText::new(&bytes[pos..pos + ca_name_len]);
224                pos += ca_name_len;
225
226                if pos + CA_HEADER_LEN > rp_end {
227                    return Err(Error::BufferTooShort {
228                        need: pos + CA_HEADER_LEN,
229                        have: rp_end,
230                        what: "RntSection CRID_authority header",
231                    });
232                }
233                let ca_packed = bytes[pos];
234                let crid_authority_policy = (ca_packed >> 4) & 0x03;
235                let ca_desc_len = (((ca_packed & 0x0F) as usize) << 8) | bytes[pos + 1] as usize;
236                pos += CA_HEADER_LEN;
237                let ca_desc_start = pos;
238                let ca_desc_end = ca_desc_start + ca_desc_len;
239                if ca_desc_end > rp_end {
240                    return Err(Error::SectionLengthOverflow {
241                        declared: ca_desc_len,
242                        available: rp_end.saturating_sub(ca_desc_start),
243                    });
244                }
245                let ca_descriptors = DescriptorLoop::new(&bytes[ca_desc_start..ca_desc_end]);
246                pos = ca_desc_end;
247
248                crid_authorities.push(CridAuthority {
249                    name: ca_name,
250                    crid_authority_policy,
251                    descriptors: ca_descriptors,
252                });
253            }
254
255            resolution_providers.push(ResolutionProvider {
256                name,
257                descriptors,
258                crid_authorities,
259            });
260            pos = rp_end;
261        }
262
263        Ok(RntSection {
264            context_id,
265            version_number,
266            current_next_indicator,
267            section_number,
268            last_section_number,
269            context_id_type,
270            common_descriptors,
271            resolution_providers,
272        })
273    }
274}
275
276impl Serialize for RntSection<'_> {
277    type Error = crate::error::Error;
278
279    fn serialized_len(&self) -> usize {
280        HEADER_LEN
281            + EXTENSION_HEADER_LEN
282            + COMMON_DESC_LEN_FIELD
283            + self.common_descriptors.len()
284            + self
285                .resolution_providers
286                .iter()
287                .map(|rp| RP_INFO_LEN_FIELD + resolution_provider_serialized_len(rp))
288                .sum::<usize>()
289            + CRC_LEN
290    }
291
292    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
293        let len = self.serialized_len();
294        if buf.len() < len {
295            return Err(Error::OutputBufferTooSmall {
296                need: len,
297                have: buf.len(),
298            });
299        }
300
301        let section_length = (len - HEADER_LEN) as u16;
302        if section_length > 0x0FFF {
303            return Err(Error::SectionLengthOverflow {
304                declared: section_length as usize,
305                available: 0x0FFF,
306            });
307        }
308        buf[0] = TABLE_ID;
309        buf[1] = super::SECTION_B1_FLAGS_DVB | ((section_length >> 8) as u8 & 0x0F);
310        buf[2] = (section_length & 0xFF) as u8;
311
312        buf[3..5].copy_from_slice(&self.context_id.to_be_bytes());
313        buf[5] = 0xC0 | ((self.version_number & 0x1F) << 1) | u8::from(self.current_next_indicator);
314        buf[6] = self.section_number;
315        buf[7] = self.last_section_number;
316        buf[8] = self.context_id_type;
317
318        let cdl = self.common_descriptors.len() as u16;
319        let cdl_pos = HEADER_LEN + EXTENSION_HEADER_LEN;
320        buf[cdl_pos] = RESERVED_NIBBLE | ((cdl >> 8) as u8 & 0x0F);
321        buf[cdl_pos + 1] = (cdl & 0xFF) as u8;
322
323        let cd_start = cdl_pos + COMMON_DESC_LEN_FIELD;
324        let cd_end = cd_start + self.common_descriptors.len();
325        buf[cd_start..cd_end].copy_from_slice(self.common_descriptors.raw());
326
327        let mut pos = cd_end;
328        for rp in &self.resolution_providers {
329            let rp_body_len = resolution_provider_serialized_len(rp);
330            let rp_info_length = rp_body_len as u16;
331            buf[pos] = RESERVED_NIBBLE | ((rp_info_length >> 8) as u8 & 0x0F);
332            buf[pos + 1] = (rp_info_length & 0xFF) as u8;
333            pos += RP_INFO_LEN_FIELD;
334
335            if rp.name.len() > u8::MAX as usize {
336                return Err(Error::ValueOutOfRange {
337                    field: "resolution_provider_name_length",
338                    reason: "exceeds 255 bytes",
339                });
340            }
341            buf[pos] = rp.name.len() as u8;
342            pos += RP_NAME_LEN_FIELD;
343            buf[pos..pos + rp.name.len()].copy_from_slice(rp.name.raw());
344            pos += rp.name.len();
345
346            let rdl = rp.descriptors.len() as u16;
347            buf[pos] = RESERVED_NIBBLE | ((rdl >> 8) as u8 & 0x0F);
348            buf[pos + 1] = (rdl & 0xFF) as u8;
349            pos += RP_DESC_LEN_FIELD;
350            buf[pos..pos + rp.descriptors.len()].copy_from_slice(rp.descriptors.raw());
351            pos += rp.descriptors.len();
352
353            for ca in &rp.crid_authorities {
354                if ca.name.len() > u8::MAX as usize {
355                    return Err(Error::ValueOutOfRange {
356                        field: "crid_authority_name_length",
357                        reason: "exceeds 255 bytes",
358                    });
359                }
360                buf[pos] = ca.name.len() as u8;
361                pos += CA_NAME_LEN_FIELD;
362                buf[pos..pos + ca.name.len()].copy_from_slice(ca.name.raw());
363                pos += ca.name.len();
364
365                let adl = ca.descriptors.len() as u16;
366                buf[pos] =
367                    0xC0 | ((ca.crid_authority_policy & 0x03) << 4) | ((adl >> 8) as u8 & 0x0F);
368                buf[pos + 1] = (adl & 0xFF) as u8;
369                pos += CA_HEADER_LEN;
370                buf[pos..pos + ca.descriptors.len()].copy_from_slice(ca.descriptors.raw());
371                pos += ca.descriptors.len();
372            }
373        }
374
375        let crc_pos = len - CRC_LEN;
376        let crc = dvb_common::crc32_mpeg2::compute(&buf[..crc_pos]);
377        buf[crc_pos..len].copy_from_slice(&crc.to_be_bytes());
378        Ok(len)
379    }
380}
381impl<'a> crate::traits::TableDef<'a> for RntSection<'a> {
382    const TABLE_ID_RANGES: &'static [(u8, u8)] = &[(TABLE_ID, TABLE_ID)];
383    const NAME: &'static str = "RESOLUTION_PROVIDER_NOTIFICATION";
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    #[test]
391    fn parse_happy_path() {
392        let common_desc = [0x83u8, 0x02, 0xAB, 0xCD];
393        let rp = ResolutionProvider {
394            name: DvbText::new(b"bb"),
395            descriptors: DescriptorLoop::new(&[]),
396            crid_authorities: vec![CridAuthority {
397                name: DvbText::new(b"au"),
398                crid_authority_policy: 1,
399                descriptors: DescriptorLoop::new(&[]),
400            }],
401        };
402        let rnt = RntSection {
403            context_id: 0x0042,
404            version_number: 3,
405            current_next_indicator: true,
406            section_number: 0,
407            last_section_number: 0,
408            context_id_type: 0x01,
409            common_descriptors: DescriptorLoop::new(&common_desc),
410            resolution_providers: vec![rp],
411        };
412        let mut buf = vec![0u8; rnt.serialized_len()];
413        rnt.serialize_into(&mut buf).unwrap();
414        let parsed = RntSection::parse(&buf).unwrap();
415        assert_eq!(parsed.context_id, 0x0042);
416        assert_eq!(parsed.version_number, 3);
417        assert!(parsed.current_next_indicator);
418        assert_eq!(parsed.context_id_type, 0x01);
419        assert_eq!(parsed.resolution_providers.len(), 1);
420        assert_eq!(parsed.resolution_providers[0].name.raw(), b"bb");
421        assert_eq!(parsed.resolution_providers[0].crid_authorities.len(), 1);
422        assert_eq!(
423            parsed.resolution_providers[0].crid_authorities[0].crid_authority_policy,
424            1
425        );
426        assert_eq!(
427            parsed.resolution_providers[0].crid_authorities[0]
428                .name
429                .raw(),
430            b"au"
431        );
432    }
433
434    #[test]
435    fn parse_no_descriptors_no_providers() {
436        let rnt = RntSection {
437            context_id: 0x0000,
438            version_number: 0,
439            current_next_indicator: false,
440            section_number: 0,
441            last_section_number: 0,
442            context_id_type: 0x00,
443            common_descriptors: DescriptorLoop::new(&[]),
444            resolution_providers: Vec::new(),
445        };
446        let mut buf = vec![0u8; rnt.serialized_len()];
447        rnt.serialize_into(&mut buf).unwrap();
448        let parsed = RntSection::parse(&buf).unwrap();
449        assert_eq!(parsed.common_descriptors.len(), 0);
450        assert!(parsed.resolution_providers.is_empty());
451    }
452
453    #[test]
454    fn byte_exact_round_trip() {
455        let rp = ResolutionProvider {
456            name: DvbText::new(b"provider"),
457            descriptors: DescriptorLoop::new(&[0x40, 0x03, b'R', b'N', b'T']),
458            crid_authorities: vec![
459                CridAuthority {
460                    name: DvbText::new(b"auth1"),
461                    crid_authority_policy: 0,
462                    descriptors: DescriptorLoop::new(&[]),
463                },
464                CridAuthority {
465                    name: DvbText::new(b"auth2"),
466                    crid_authority_policy: 2,
467                    descriptors: DescriptorLoop::new(&[0x42, 0x00]),
468                },
469            ],
470        };
471        let rnt = RntSection {
472            context_id: 0xABCD,
473            version_number: 15,
474            current_next_indicator: true,
475            section_number: 1,
476            last_section_number: 2,
477            context_id_type: 0x02,
478            common_descriptors: DescriptorLoop::new(&[0x40, 0x03, b'R', b'N', b'T']),
479            resolution_providers: vec![rp],
480        };
481        let mut buf = vec![0u8; rnt.serialized_len()];
482        rnt.serialize_into(&mut buf).unwrap();
483        let re = RntSection::parse(&buf).unwrap();
484        let mut buf2 = vec![0u8; re.serialized_len()];
485        re.serialize_into(&mut buf2).unwrap();
486        assert_eq!(buf, buf2, "byte-exact re-serialize");
487        let re = RntSection::parse(&buf).unwrap();
488        assert_eq!(re.resolution_providers.len(), 1);
489        assert_eq!(re.resolution_providers[0].name.raw(), b"provider");
490        assert_eq!(re.resolution_providers[0].crid_authorities.len(), 2);
491        assert_eq!(
492            re.resolution_providers[0].crid_authorities[1].crid_authority_policy,
493            2
494        );
495    }
496
497    #[test]
498    fn parse_rejects_wrong_table_id() {
499        let rnt = RntSection {
500            context_id: 0x0001,
501            version_number: 0,
502            current_next_indicator: true,
503            section_number: 0,
504            last_section_number: 0,
505            context_id_type: 0x00,
506            common_descriptors: DescriptorLoop::new(&[]),
507            resolution_providers: Vec::new(),
508        };
509        let mut buf = vec![0u8; rnt.serialized_len()];
510        rnt.serialize_into(&mut buf).unwrap();
511        buf[0] = 0x70;
512        assert!(matches!(
513            RntSection::parse(&buf).unwrap_err(),
514            Error::UnexpectedTableId { table_id: 0x70, .. }
515        ));
516    }
517
518    #[test]
519    fn parse_rejects_short_buffer() {
520        assert!(matches!(
521            RntSection::parse(&[0x79, 0x00]).unwrap_err(),
522            Error::BufferTooShort { .. }
523        ));
524    }
525
526    #[test]
527    fn serialize_rejects_too_small_buffer() {
528        let rnt = RntSection {
529            context_id: 0x0001,
530            version_number: 0,
531            current_next_indicator: true,
532            section_number: 0,
533            last_section_number: 0,
534            context_id_type: 0x00,
535            common_descriptors: DescriptorLoop::new(&[]),
536            resolution_providers: Vec::new(),
537        };
538        let mut buf = vec![0u8; 2];
539        assert!(matches!(
540            rnt.serialize_into(&mut buf).unwrap_err(),
541            Error::OutputBufferTooSmall { .. }
542        ));
543    }
544
545    #[test]
546    fn parse_rejects_zero_section_length() {
547        let mut buf = vec![0u8; 64];
548        buf[0] = TABLE_ID;
549        buf[1] = 0xF0;
550        buf[2] = 0x00;
551        for b in &mut buf[3..] {
552            *b = 0xFF;
553        }
554        assert!(matches!(
555            RntSection::parse(&buf).unwrap_err(),
556            Error::SectionLengthOverflow { .. }
557        ));
558    }
559
560    #[test]
561    fn parse_handwritten_rnt_no_providers() {
562        let mut bytes: Vec<u8> = vec![
563            0x79, 0xF0, 0x0C, 0x00, 0x42, 0xC7, 0x00, 0x00, 0x01, 0xF0, 0x00,
564        ];
565        let crc = dvb_common::crc32_mpeg2::compute(&bytes);
566        bytes.extend_from_slice(&crc.to_be_bytes());
567        let rnt = RntSection::parse(&bytes).unwrap();
568        assert_eq!(rnt.context_id, 0x0042);
569        assert_eq!(rnt.version_number, 3);
570        assert!(rnt.current_next_indicator);
571        assert!(rnt.resolution_providers.is_empty());
572    }
573}