1use crate::error::{Error, Result};
8use crate::text::{DvbText, LangCode};
9use crate::traits::Descriptor;
10use dvb_common::{Parse, Serialize};
11
12pub 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#[derive(Debug, Clone, PartialEq, Eq)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct ComponentTextEntry<'a> {
23 pub language_code: LangCode,
25 pub text: DvbText<'a>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize))] pub struct MultilingualComponentDescriptor<'a> {
33 pub component_tag: u8,
35 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 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 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 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 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}