Skip to main content

dvb_si/descriptors/
nvod_reference.rs

1//! NVOD Reference Descriptor — ETSI EN 300 468 §6.2.28 (tag 0x4B).
2//!
3//! Table 80 (PDF p. 96). Carried in the SDT for an NVOD reference service.
4//! Body is a loop of (transport_stream_id, original_network_id, service_id)
5//! triples, each identifying one of the time-shifted NVOD services.
6
7use crate::error::{Error, Result};
8use crate::traits::Descriptor;
9use dvb_common::{Parse, Serialize};
10
11/// Descriptor tag for NVOD_reference_descriptor.
12pub const TAG: u8 = 0x4B;
13const HEADER_LEN: usize = 2;
14const ENTRY_LEN: usize = 6;
15/// Maximum body length expressible in the 8-bit `descriptor_length` field.
16const MAX_BODY_LEN: usize = u8::MAX as usize;
17
18/// One NVOD reference triple.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub struct NvodReferenceEntry {
22    /// transport_stream_id carrying the referenced service.
23    pub transport_stream_id: u16,
24    /// original_network_id of the referenced service.
25    pub original_network_id: u16,
26    /// service_id of the referenced NVOD time-shifted service.
27    pub service_id: u16,
28}
29
30/// NVOD Reference Descriptor.
31#[derive(Debug, Clone, PartialEq, Eq)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct NvodReferenceDescriptor {
34    /// Reference triples in wire order.
35    pub entries: Vec<NvodReferenceEntry>,
36}
37
38impl<'a> Parse<'a> for NvodReferenceDescriptor {
39    type Error = crate::error::Error;
40    fn parse(bytes: &'a [u8]) -> Result<Self> {
41        if bytes.len() < HEADER_LEN {
42            return Err(Error::BufferTooShort {
43                need: HEADER_LEN,
44                have: bytes.len(),
45                what: "NvodReferenceDescriptor header",
46            });
47        }
48        if bytes[0] != TAG {
49            return Err(Error::InvalidDescriptor {
50                tag: bytes[0],
51                reason: "unexpected tag for NVOD_reference_descriptor",
52            });
53        }
54        let length = bytes[1] as usize;
55        if length % ENTRY_LEN != 0 {
56            return Err(Error::InvalidDescriptor {
57                tag: TAG,
58                reason: "descriptor_length must be a multiple of 6",
59            });
60        }
61        let end = HEADER_LEN + length;
62        if bytes.len() < end {
63            return Err(Error::BufferTooShort {
64                need: end,
65                have: bytes.len(),
66                what: "NvodReferenceDescriptor body",
67            });
68        }
69        let body = &bytes[HEADER_LEN..end];
70        let mut entries = Vec::with_capacity(length / ENTRY_LEN);
71        for chunk in body.chunks_exact(ENTRY_LEN) {
72            entries.push(NvodReferenceEntry {
73                transport_stream_id: u16::from_be_bytes([chunk[0], chunk[1]]),
74                original_network_id: u16::from_be_bytes([chunk[2], chunk[3]]),
75                service_id: u16::from_be_bytes([chunk[4], chunk[5]]),
76            });
77        }
78        Ok(Self { entries })
79    }
80}
81
82impl Serialize for NvodReferenceDescriptor {
83    type Error = crate::error::Error;
84    fn serialized_len(&self) -> usize {
85        HEADER_LEN + ENTRY_LEN * self.entries.len()
86    }
87
88    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
89        let len = self.serialized_len();
90        if buf.len() < len {
91            return Err(Error::OutputBufferTooSmall {
92                need: len,
93                have: buf.len(),
94            });
95        }
96        let body_len = ENTRY_LEN * self.entries.len();
97        // 8-bit descriptor_length field: error rather than silently truncate.
98        if body_len > MAX_BODY_LEN {
99            return Err(Error::SectionLengthOverflow {
100                declared: body_len,
101                available: MAX_BODY_LEN,
102            });
103        }
104        buf[0] = TAG;
105        buf[1] = body_len as u8;
106        let mut pos = HEADER_LEN;
107        for e in &self.entries {
108            buf[pos..pos + 2].copy_from_slice(&e.transport_stream_id.to_be_bytes());
109            buf[pos + 2..pos + 4].copy_from_slice(&e.original_network_id.to_be_bytes());
110            buf[pos + 4..pos + 6].copy_from_slice(&e.service_id.to_be_bytes());
111            pos += ENTRY_LEN;
112        }
113        Ok(len)
114    }
115}
116
117impl<'a> Descriptor<'a> for NvodReferenceDescriptor {
118    const TAG: u8 = TAG;
119    fn descriptor_length(&self) -> u8 {
120        (ENTRY_LEN * self.entries.len()) as u8
121    }
122}
123
124impl<'a> crate::traits::DescriptorDef<'a> for NvodReferenceDescriptor {
125    const TAG: u8 = TAG;
126    const NAME: &'static str = "NVOD_REFERENCE";
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn parse_single_triple() {
135        let bytes = [TAG, 6, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03];
136        let d = NvodReferenceDescriptor::parse(&bytes).unwrap();
137        assert_eq!(d.entries.len(), 1);
138        assert_eq!(d.entries[0].transport_stream_id, 1);
139        assert_eq!(d.entries[0].original_network_id, 2);
140        assert_eq!(d.entries[0].service_id, 3);
141    }
142
143    #[test]
144    fn parse_multiple_triples_preserves_order() {
145        let bytes = [
146            TAG, 12, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06,
147        ];
148        let d = NvodReferenceDescriptor::parse(&bytes).unwrap();
149        assert_eq!(d.entries.len(), 2);
150        assert_eq!(d.entries[1].transport_stream_id, 4);
151        assert_eq!(d.entries[1].original_network_id, 5);
152        assert_eq!(d.entries[1].service_id, 6);
153    }
154
155    #[test]
156    fn parse_rejects_wrong_tag() {
157        assert!(matches!(
158            NvodReferenceDescriptor::parse(&[0x4C, 6, 0, 0, 0, 0, 0, 0]).unwrap_err(),
159            Error::InvalidDescriptor { tag: 0x4C, .. }
160        ));
161    }
162
163    #[test]
164    fn parse_rejects_short_buffer() {
165        let bytes = [TAG, 12, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03];
166        assert!(matches!(
167            NvodReferenceDescriptor::parse(&bytes).unwrap_err(),
168            Error::BufferTooShort { .. }
169        ));
170    }
171
172    #[test]
173    fn parse_rejects_length_not_multiple_of_6() {
174        let bytes = [TAG, 5, 0x00, 0x01, 0x00, 0x02, 0x00];
175        assert!(matches!(
176            NvodReferenceDescriptor::parse(&bytes).unwrap_err(),
177            Error::InvalidDescriptor { tag: TAG, .. }
178        ));
179    }
180
181    #[test]
182    fn empty_descriptor_valid() {
183        let d = NvodReferenceDescriptor::parse(&[TAG, 0]).unwrap();
184        assert!(d.entries.is_empty());
185    }
186
187    #[test]
188    fn serialize_round_trip() {
189        let d = NvodReferenceDescriptor {
190            entries: vec![
191                NvodReferenceEntry {
192                    transport_stream_id: 0x1234,
193                    original_network_id: 0x5678,
194                    service_id: 0x9ABC,
195                },
196                NvodReferenceEntry {
197                    transport_stream_id: 0x0001,
198                    original_network_id: 0x0002,
199                    service_id: 0x0003,
200                },
201            ],
202        };
203        let mut buf = vec![0u8; d.serialized_len()];
204        d.serialize_into(&mut buf).unwrap();
205        assert_eq!(NvodReferenceDescriptor::parse(&buf).unwrap(), d);
206    }
207
208    #[test]
209    fn serialize_rejects_over_range_body() {
210        // 43 triples = 258 body bytes, past the u8 length field.
211        let d = NvodReferenceDescriptor {
212            entries: vec![
213                NvodReferenceEntry {
214                    transport_stream_id: 1,
215                    original_network_id: 2,
216                    service_id: 3,
217                };
218                43
219            ],
220        };
221        let mut buf = vec![0u8; d.serialized_len()];
222        assert!(matches!(
223            d.serialize_into(&mut buf).unwrap_err(),
224            Error::SectionLengthOverflow { .. }
225        ));
226    }
227
228    #[cfg(feature = "serde")]
229    #[test]
230    fn serde_round_trip() {
231        let d = NvodReferenceDescriptor {
232            entries: vec![NvodReferenceEntry {
233                transport_stream_id: 1,
234                original_network_id: 2,
235                service_id: 3,
236            }],
237        };
238        let json = serde_json::to_string(&d).unwrap();
239        let back: NvodReferenceDescriptor = serde_json::from_str(&json).unwrap();
240        assert_eq!(d, back);
241    }
242}