Skip to main content

dvb_si/descriptors/
multilingual_component.rs

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