Skip to main content

dvb_si/descriptors/
multilingual_bouquet_name.rs

1//! Multilingual Bouquet Name Descriptor — ETSI EN 300 468 §6.2.22 (tag 0x5C).
2//!
3//! Table 76 (PDF p. 93). Carried in the BAT. A loop of (ISO 639-2 language
4//! code, bouquet name) pairs, each name length-prefixed by an 8-bit field.
5
6use crate::error::{Error, Result};
7use crate::text::{DvbText, LangCode};
8use crate::traits::Descriptor;
9use dvb_common::{Parse, Serialize};
10
11/// Descriptor tag for multilingual_bouquet_name_descriptor.
12pub const TAG: u8 = 0x5C;
13const HEADER_LEN: usize = 2;
14const LANG_LEN: usize = 3;
15const NAME_LEN_FIELD: usize = 1;
16
17/// One localised bouquet name.
18#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20pub struct BouquetNameEntry<'a> {
21    /// ISO 639-2 language code.
22    pub language_code: LangCode,
23    /// DVB Annex-A encoded bouquet name.
24    pub bouquet_name: DvbText<'a>,
25}
26
27/// Multilingual Bouquet Name Descriptor (tag 0x5C).
28#[derive(Debug, Clone, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize))]
30pub struct MultilingualBouquetNameDescriptor<'a> {
31    /// Localised names in wire order.
32    pub entries: Vec<BouquetNameEntry<'a>>,
33}
34
35impl<'a> Parse<'a> for MultilingualBouquetNameDescriptor<'a> {
36    type Error = crate::error::Error;
37    fn parse(bytes: &'a [u8]) -> Result<Self> {
38        if bytes.len() < HEADER_LEN {
39            return Err(Error::BufferTooShort {
40                need: HEADER_LEN,
41                have: bytes.len(),
42                what: "MultilingualBouquetNameDescriptor header",
43            });
44        }
45        if bytes[0] != TAG {
46            return Err(Error::InvalidDescriptor {
47                tag: bytes[0],
48                reason: "unexpected tag for multilingual_bouquet_name_descriptor",
49            });
50        }
51        let length = bytes[1] as usize;
52        let end = HEADER_LEN + length;
53        if bytes.len() < end {
54            return Err(Error::BufferTooShort {
55                need: end,
56                have: bytes.len(),
57                what: "MultilingualBouquetNameDescriptor body",
58            });
59        }
60        let mut entries = Vec::new();
61        let mut pos = HEADER_LEN;
62        while pos < end {
63            if pos + LANG_LEN + NAME_LEN_FIELD > end {
64                return Err(Error::InvalidDescriptor {
65                    tag: TAG,
66                    reason: "entry header runs past descriptor end",
67                });
68            }
69            let language_code = LangCode([bytes[pos], bytes[pos + 1], bytes[pos + 2]]);
70            let name_len = bytes[pos + LANG_LEN] as usize;
71            let name_start = pos + LANG_LEN + NAME_LEN_FIELD;
72            let name_end = name_start + name_len;
73            if name_end > end {
74                return Err(Error::InvalidDescriptor {
75                    tag: TAG,
76                    reason: "name_length runs past descriptor end",
77                });
78            }
79            entries.push(BouquetNameEntry {
80                language_code,
81                bouquet_name: DvbText::new(&bytes[name_start..name_end]),
82            });
83            pos = name_end;
84        }
85        Ok(Self { entries })
86    }
87}
88
89impl Serialize for MultilingualBouquetNameDescriptor<'_> {
90    type Error = crate::error::Error;
91    fn serialized_len(&self) -> usize {
92        HEADER_LEN
93            + self
94                .entries
95                .iter()
96                .map(|e| LANG_LEN + NAME_LEN_FIELD + e.bouquet_name.len())
97                .sum::<usize>()
98    }
99
100    fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
101        for e in &self.entries {
102            if e.bouquet_name.len() > u8::MAX as usize {
103                return Err(Error::InvalidDescriptor {
104                    tag: TAG,
105                    reason: "bouquet_name exceeds 255 bytes (name_length is 8-bit)",
106                });
107            }
108        }
109        let len = self.serialized_len();
110        let body = len - HEADER_LEN;
111        if body > u8::MAX as usize {
112            return Err(Error::InvalidDescriptor {
113                tag: TAG,
114                reason: "multilingual_bouquet_name_descriptor body exceeds 255 bytes",
115            });
116        }
117        if buf.len() < len {
118            return Err(Error::OutputBufferTooSmall {
119                need: len,
120                have: buf.len(),
121            });
122        }
123        buf[0] = TAG;
124        buf[1] = body as u8;
125        let mut pos = HEADER_LEN;
126        for e in &self.entries {
127            buf[pos..pos + LANG_LEN].copy_from_slice(&e.language_code.0);
128            buf[pos + LANG_LEN] = e.bouquet_name.len() as u8;
129            let name_start = pos + LANG_LEN + NAME_LEN_FIELD;
130            buf[name_start..name_start + e.bouquet_name.len()]
131                .copy_from_slice(e.bouquet_name.raw());
132            pos = name_start + e.bouquet_name.len();
133        }
134        Ok(len)
135    }
136}
137
138impl<'a> Descriptor<'a> for MultilingualBouquetNameDescriptor<'a> {
139    const TAG: u8 = TAG;
140    fn descriptor_length(&self) -> u8 {
141        (self.serialized_len() - HEADER_LEN) as u8
142    }
143}
144
145impl<'a> crate::traits::DescriptorDef<'a> for MultilingualBouquetNameDescriptor<'a> {
146    const TAG: u8 = TAG;
147    const NAME: &'static str = "MULTILINGUAL_BOUQUET_NAME";
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153
154    fn build(entries: &[([u8; 3], &[u8])]) -> Vec<u8> {
155        let body: usize = entries.iter().map(|(_, n)| LANG_LEN + 1 + n.len()).sum();
156        let mut v = Vec::with_capacity(HEADER_LEN + body);
157        v.push(TAG);
158        v.push(body as u8);
159        for (lang, name) in entries {
160            v.extend_from_slice(lang);
161            v.push(name.len() as u8);
162            v.extend_from_slice(name);
163        }
164        v
165    }
166
167    #[test]
168    fn parse_single_entry() {
169        let bytes = build(&[(*b"eng", b"Sky")]);
170        let d = MultilingualBouquetNameDescriptor::parse(&bytes).unwrap();
171        assert_eq!(d.entries.len(), 1);
172        assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
173        assert_eq!(d.entries[0].bouquet_name.raw(), b"Sky");
174    }
175
176    #[test]
177    fn parse_multiple_entries() {
178        let bytes = build(&[(*b"eng", b"Pack"), (*b"fra", b"Bouquet")]);
179        let d = MultilingualBouquetNameDescriptor::parse(&bytes).unwrap();
180        assert_eq!(d.entries.len(), 2);
181        assert_eq!(d.entries[1].bouquet_name.raw(), b"Bouquet");
182    }
183
184    #[test]
185    fn parse_rejects_wrong_tag() {
186        let err = MultilingualBouquetNameDescriptor::parse(&[0x5B, 0]).unwrap_err();
187        assert!(matches!(err, Error::InvalidDescriptor { tag: 0x5B, .. }));
188    }
189
190    #[test]
191    fn parse_rejects_short_buffer() {
192        let err = MultilingualBouquetNameDescriptor::parse(&[TAG]).unwrap_err();
193        assert!(matches!(err, Error::BufferTooShort { .. }));
194    }
195
196    #[test]
197    fn parse_rejects_name_length_overrun() {
198        let bytes = [TAG, 5, b'e', b'n', b'g', 100, 0];
199        let err = MultilingualBouquetNameDescriptor::parse(&bytes).unwrap_err();
200        assert!(matches!(err, Error::InvalidDescriptor { .. }));
201    }
202
203    #[test]
204    fn parse_rejects_truncated_entry_header() {
205        let bytes = [TAG, 2, b'e', b'n'];
206        let err = MultilingualBouquetNameDescriptor::parse(&bytes).unwrap_err();
207        assert!(matches!(err, Error::InvalidDescriptor { .. }));
208    }
209
210    #[test]
211    fn empty_descriptor_valid() {
212        let d = MultilingualBouquetNameDescriptor::parse(&[TAG, 0]).unwrap();
213        assert_eq!(d.entries.len(), 0);
214    }
215
216    #[test]
217    fn serialize_round_trip() {
218        let bytes = build(&[(*b"eng", b"Bouquet"), (*b"deu", b"Paket")]);
219        let parsed = MultilingualBouquetNameDescriptor::parse(&bytes).unwrap();
220        let mut buf = vec![0u8; parsed.serialized_len()];
221        parsed.serialize_into(&mut buf).unwrap();
222        assert_eq!(buf, bytes);
223        let re = MultilingualBouquetNameDescriptor::parse(&buf).unwrap();
224        assert_eq!(parsed, re);
225    }
226
227    #[test]
228    fn serialize_rejects_too_small_buffer() {
229        let d = MultilingualBouquetNameDescriptor {
230            entries: vec![BouquetNameEntry {
231                language_code: LangCode(*b"eng"),
232                bouquet_name: DvbText::new(b"X"),
233            }],
234        };
235        let mut tiny = [0u8; 3];
236        let err = d.serialize_into(&mut tiny).unwrap_err();
237        assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
238    }
239
240    #[test]
241    fn serialize_rejects_over_range_name() {
242        let name = vec![0u8; 256];
243        let d = MultilingualBouquetNameDescriptor {
244            entries: vec![BouquetNameEntry {
245                language_code: LangCode(*b"eng"),
246                bouquet_name: DvbText::new(&name),
247            }],
248        };
249        let mut buf = vec![0u8; d.serialized_len()];
250        let err = d.serialize_into(&mut buf).unwrap_err();
251        assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
252    }
253
254    #[cfg(feature = "serde")]
255    #[test]
256    fn serde_serialize_is_stable() {
257        // Borrowed `&[u8]` cannot be deserialized from a JSON array by
258        // serde_json; matching the borrowed-bytes descriptors in this crate we
259        // exercise the serialize path and assert it is deterministic.
260        let d = MultilingualBouquetNameDescriptor {
261            entries: vec![BouquetNameEntry {
262                language_code: LangCode(*b"eng"),
263                bouquet_name: DvbText::new(b"Sky"),
264            }],
265        };
266        let json = serde_json::to_string(&d).unwrap();
267        assert_eq!(json, serde_json::to_string(&d.clone()).unwrap());
268    }
269}