1use super::descriptor_body;
8use crate::error::{Error, Result};
9use crate::text::{DvbText, LangCode};
10use alloc::vec::Vec;
11use dvb_common::{Parse, Serialize};
12
13pub const TAG: u8 = 0x5E;
15const HEADER_LEN: usize = 2;
16const COMPONENT_TAG_LEN: usize = 1;
17const LANG_LEN: usize = 3;
18const TEXT_LEN_FIELD: usize = 1;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize))]
23#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
24pub struct ComponentTextEntry<'a> {
25 pub language_code: LangCode,
27 pub text: DvbText<'a>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize))]
34#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
35pub struct MultilingualComponentDescriptor<'a> {
36 pub component_tag: u8,
38 pub entries: Vec<ComponentTextEntry<'a>>,
40}
41
42impl<'a> Parse<'a> for MultilingualComponentDescriptor<'a> {
43 type Error = crate::error::Error;
44 fn parse(bytes: &'a [u8]) -> Result<Self> {
45 let body = descriptor_body(
46 bytes,
47 TAG,
48 "MultilingualComponentDescriptor",
49 "unexpected tag for multilingual_component_descriptor",
50 )?;
51 if body.len() < COMPONENT_TAG_LEN {
52 return Err(Error::InvalidDescriptor {
53 tag: TAG,
54 reason: "multilingual_component_descriptor body missing component_tag",
55 });
56 }
57 let component_tag = body[0];
58 let mut entries = Vec::new();
59 let mut pos = COMPONENT_TAG_LEN;
60 while pos < body.len() {
61 if pos + LANG_LEN + TEXT_LEN_FIELD > body.len() {
62 return Err(Error::InvalidDescriptor {
63 tag: TAG,
64 reason: "entry header runs past descriptor end",
65 });
66 }
67 let language_code = LangCode([body[pos], body[pos + 1], body[pos + 2]]);
68 let text_len = body[pos + LANG_LEN] as usize;
69 let text_start = pos + LANG_LEN + TEXT_LEN_FIELD;
70 let text_end = text_start + text_len;
71 if text_end > body.len() {
72 return Err(Error::InvalidDescriptor {
73 tag: TAG,
74 reason: "text_length runs past descriptor end",
75 });
76 }
77 entries.push(ComponentTextEntry {
78 language_code,
79 text: DvbText::new(&body[text_start..text_end]),
80 });
81 pos = text_end;
82 }
83 Ok(Self {
84 component_tag,
85 entries,
86 })
87 }
88}
89
90impl Serialize for MultilingualComponentDescriptor<'_> {
91 type Error = crate::error::Error;
92 fn serialized_len(&self) -> usize {
93 HEADER_LEN
94 + COMPONENT_TAG_LEN
95 + self
96 .entries
97 .iter()
98 .map(|e| LANG_LEN + TEXT_LEN_FIELD + e.text.len())
99 .sum::<usize>()
100 }
101
102 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
103 for e in &self.entries {
104 if e.text.len() > u8::MAX as usize {
105 return Err(Error::InvalidDescriptor {
106 tag: TAG,
107 reason: "text exceeds 255 bytes (text_length is 8-bit)",
108 });
109 }
110 }
111 let len = self.serialized_len();
112 let body = len - HEADER_LEN;
113 if body > u8::MAX as usize {
114 return Err(Error::InvalidDescriptor {
115 tag: TAG,
116 reason: "multilingual_component_descriptor body exceeds 255 bytes",
117 });
118 }
119 if buf.len() < len {
120 return Err(Error::OutputBufferTooSmall {
121 need: len,
122 have: buf.len(),
123 });
124 }
125 buf[0] = TAG;
126 buf[1] = body as u8;
127 buf[HEADER_LEN] = self.component_tag;
128 let mut pos = HEADER_LEN + COMPONENT_TAG_LEN;
129 for e in &self.entries {
130 buf[pos..pos + LANG_LEN].copy_from_slice(&e.language_code.0);
131 buf[pos + LANG_LEN] = e.text.len() as u8;
132 let text_start = pos + LANG_LEN + TEXT_LEN_FIELD;
133 buf[text_start..text_start + e.text.len()].copy_from_slice(e.text.raw());
134 pos = text_start + e.text.len();
135 }
136 Ok(len)
137 }
138}
139impl<'a> crate::traits::DescriptorDef<'a> for MultilingualComponentDescriptor<'a> {
140 const TAG: u8 = TAG;
141 const NAME: &'static str = "MULTILINGUAL_COMPONENT";
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 fn build(component_tag: u8, entries: &[([u8; 3], &[u8])]) -> Vec<u8> {
149 let body: usize = COMPONENT_TAG_LEN
150 + entries
151 .iter()
152 .map(|(_, t)| LANG_LEN + 1 + t.len())
153 .sum::<usize>();
154 let mut v = Vec::with_capacity(HEADER_LEN + body);
155 v.push(TAG);
156 v.push(body as u8);
157 v.push(component_tag);
158 for (lang, text) in entries {
159 v.extend_from_slice(lang);
160 v.push(text.len() as u8);
161 v.extend_from_slice(text);
162 }
163 v
164 }
165
166 #[test]
167 fn parse_extracts_component_tag_and_entries() {
168 let bytes = build(0x12, &[(*b"eng", b"Video")]);
169 let d = MultilingualComponentDescriptor::parse(&bytes).unwrap();
170 assert_eq!(d.component_tag, 0x12);
171 assert_eq!(d.entries.len(), 1);
172 assert_eq!(d.entries[0].language_code, LangCode(*b"eng"));
173 assert_eq!(d.entries[0].text.raw(), b"Video");
174 }
175
176 #[test]
177 fn parse_multiple_entries() {
178 let bytes = build(0x03, &[(*b"eng", b"Audio"), (*b"fra", b"Son")]);
179 let d = MultilingualComponentDescriptor::parse(&bytes).unwrap();
180 assert_eq!(d.component_tag, 0x03);
181 assert_eq!(d.entries.len(), 2);
182 assert_eq!(d.entries[1].text.raw(), b"Son");
183 }
184
185 #[test]
186 fn parse_component_tag_only_valid() {
187 let bytes = [TAG, 1, 0x09];
189 let d = MultilingualComponentDescriptor::parse(&bytes).unwrap();
190 assert_eq!(d.component_tag, 0x09);
191 assert_eq!(d.entries.len(), 0);
192 }
193
194 #[test]
195 fn parse_rejects_wrong_tag() {
196 let err = MultilingualComponentDescriptor::parse(&[0x5D, 1, 0x00]).unwrap_err();
197 assert!(matches!(err, Error::InvalidDescriptor { tag: 0x5D, .. }));
198 }
199
200 #[test]
201 fn parse_rejects_short_buffer() {
202 let err = MultilingualComponentDescriptor::parse(&[TAG]).unwrap_err();
203 assert!(matches!(err, Error::BufferTooShort { .. }));
204 }
205
206 #[test]
207 fn parse_rejects_missing_component_tag() {
208 let err = MultilingualComponentDescriptor::parse(&[TAG, 0]).unwrap_err();
210 assert!(matches!(err, Error::InvalidDescriptor { .. }));
211 }
212
213 #[test]
214 fn parse_rejects_text_length_overrun() {
215 let bytes = [TAG, 5, 0x01, b'e', b'n', b'g', 100];
217 let err = MultilingualComponentDescriptor::parse(&bytes).unwrap_err();
218 assert!(matches!(err, Error::InvalidDescriptor { .. }));
219 }
220
221 #[test]
222 fn serialize_round_trip() {
223 let bytes = build(0x07, &[(*b"eng", b"Subtitle"), (*b"deu", b"Untertitel")]);
224 let parsed = MultilingualComponentDescriptor::parse(&bytes).unwrap();
225 let mut buf = vec![0u8; parsed.serialized_len()];
226 parsed.serialize_into(&mut buf).unwrap();
227 assert_eq!(buf, bytes);
228 let re = MultilingualComponentDescriptor::parse(&buf).unwrap();
229 assert_eq!(parsed, re);
230 }
231
232 #[test]
233 fn serialize_rejects_too_small_buffer() {
234 let d = MultilingualComponentDescriptor {
235 component_tag: 0x01,
236 entries: vec![],
237 };
238 let mut tiny = [0u8; 2];
239 let err = d.serialize_into(&mut tiny).unwrap_err();
240 assert!(matches!(err, Error::OutputBufferTooSmall { .. }));
241 }
242
243 #[test]
244 fn serialize_rejects_over_range_text() {
245 let text = vec![0u8; 256];
246 let d = MultilingualComponentDescriptor {
247 component_tag: 0x01,
248 entries: vec![ComponentTextEntry {
249 language_code: LangCode(*b"eng"),
250 text: DvbText::new(&text),
251 }],
252 };
253 let mut buf = vec![0u8; d.serialized_len()];
254 let err = d.serialize_into(&mut buf).unwrap_err();
255 assert!(matches!(err, Error::InvalidDescriptor { tag: TAG, .. }));
256 }
257
258 #[cfg(feature = "serde")]
259 #[test]
260 fn serde_serialize_is_stable() {
261 let d = MultilingualComponentDescriptor {
262 component_tag: 0x12,
263 entries: vec![ComponentTextEntry {
264 language_code: LangCode(*b"eng"),
265 text: DvbText::new(b"Video"),
266 }],
267 };
268 let json = serde_json::to_string(&d).unwrap();
269 assert!(json.contains("\"component_tag\""));
270 assert!(json.contains("\"language_code\""));
271 assert!(json.contains("\"eng\""));
272 assert!(json.contains("\"Video\""));
273 }
274}