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