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