1use super::*;
3
4impl<'a> ExtensionBodyDef<'a> for ImageIcon<'a> {
5 const TAG_EXTENSION: u8 = 0x00;
6 const NAME: &'static str = "IMAGE_ICON";
7}
8
9#[derive(Debug, Clone, PartialEq, Eq)]
19#[cfg_attr(feature = "serde", derive(serde::Serialize))]
20#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
21pub struct ImageIcon<'a> {
22 pub descriptor_number: u8,
24 pub last_descriptor_number: u8,
26 pub icon_id: u8,
28 pub body: ImageIconBody<'a>,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
36pub enum ImageIconBody<'a> {
37 First(ImageIconFirst<'a>),
39 Continuation {
41 icon_data: &'a [u8],
43 },
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48#[cfg_attr(feature = "serde", derive(serde::Serialize))]
49#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
50pub struct ImageIconFirst<'a> {
51 pub icon_transport_mode: u8,
53 pub position: Option<IconPosition>,
55 pub icon_type: crate::text::DvbText<'a>,
57 pub payload: IconLocation<'a>,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
65#[cfg_attr(feature = "serde", derive(serde::Serialize))]
66#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
67#[non_exhaustive]
68pub enum IconLocation<'a> {
69 Data(&'a [u8]),
71 Url(crate::text::DvbText<'a>),
73 None,
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize))]
80pub struct IconPosition {
81 pub coordinate_system: u8,
83 pub icon_horizontal_origin: u16,
85 pub icon_vertical_origin: u16,
87}
88
89impl<'a> Parse<'a> for ImageIcon<'a> {
90 type Error = crate::error::Error;
91 fn parse(sel: &'a [u8]) -> Result<Self> {
92 if sel.len() < 2 {
95 return Err(Error::BufferTooShort {
96 need: 2,
97 have: sel.len(),
98 what: "image_icon body",
99 });
100 }
101 let descriptor_number = sel[0] >> 4;
102 let last_descriptor_number = sel[0] & 0x0F;
103 let icon_id = sel[1] & 0x07;
104
105 let body = if descriptor_number == 0x00 {
106 if sel.len() < 3 {
108 return Err(Error::BufferTooShort {
109 need: 3,
110 have: sel.len(),
111 what: "image_icon body",
112 });
113 }
114 let packed = sel[2];
115 let icon_transport_mode = packed >> 6;
116 let position_flag = (packed >> 5) & 0x01;
117 let mut pos = 3usize;
118
119 let position = if position_flag == 1 {
120 let coordinate_system = (packed >> 2) & 0x07;
121 if sel.len() < pos + 3 {
122 return Err(Error::BufferTooShort {
123 need: pos + 3,
124 have: sel.len(),
125 what: "image_icon body",
126 });
127 }
128 let b0 = sel[pos];
129 let b1 = sel[pos + 1];
130 let b2 = sel[pos + 2];
131 let icon_horizontal_origin = (u16::from(b0) << 4) | (u16::from(b1) >> 4);
132 let icon_vertical_origin = ((u16::from(b1) & 0x0F) << 8) | u16::from(b2);
133 pos += 3;
134 Some(IconPosition {
135 coordinate_system,
136 icon_horizontal_origin,
137 icon_vertical_origin,
138 })
139 } else {
140 None
141 };
142
143 if sel.len() < pos + 1 {
145 return Err(Error::BufferTooShort {
146 need: pos + 1,
147 have: sel.len(),
148 what: "image_icon body",
149 });
150 }
151 let icon_type_length = sel[pos] as usize;
152 pos += 1;
153 if sel.len() < pos + icon_type_length {
154 return Err(Error::BufferTooShort {
155 need: pos + icon_type_length,
156 have: sel.len(),
157 what: "image_icon body",
158 });
159 }
160 let icon_type = crate::text::DvbText::new(&sel[pos..pos + icon_type_length]);
161 pos += icon_type_length;
162
163 let payload = match icon_transport_mode {
165 0 => {
166 if sel.len() < pos + 1 {
167 return Err(Error::BufferTooShort {
168 need: pos + 1,
169 have: sel.len(),
170 what: "image_icon body",
171 });
172 }
173 let payload_len = sel[pos] as usize;
174 pos += 1;
175 if sel.len() < pos + payload_len {
176 return Err(Error::BufferTooShort {
177 need: pos + payload_len,
178 have: sel.len(),
179 what: "image_icon body",
180 });
181 }
182 let p = &sel[pos..pos + payload_len];
183 pos += payload_len;
184 IconLocation::Data(p)
185 }
186 1 => {
187 if sel.len() < pos + 1 {
188 return Err(Error::BufferTooShort {
189 need: pos + 1,
190 have: sel.len(),
191 what: "image_icon body",
192 });
193 }
194 let payload_len = sel[pos] as usize;
195 pos += 1;
196 if sel.len() < pos + payload_len {
197 return Err(Error::BufferTooShort {
198 need: pos + payload_len,
199 have: sel.len(),
200 what: "image_icon body",
201 });
202 }
203 let t = crate::text::DvbText::new(&sel[pos..pos + payload_len]);
204 pos += payload_len;
205 IconLocation::Url(t)
206 }
207 _ => IconLocation::None,
208 };
209
210 if pos != sel.len() {
211 return Err(invalid("image_icon: trailing bytes"));
212 }
213
214 ImageIconBody::First(ImageIconFirst {
215 icon_transport_mode,
216 position,
217 icon_type,
218 payload,
219 })
220 } else {
221 if sel.len() < 3 {
223 return Err(Error::BufferTooShort {
224 need: 3,
225 have: sel.len(),
226 what: "image_icon body",
227 });
228 }
229 let icon_data_length = sel[2] as usize;
230 if sel.len() < 3 + icon_data_length {
231 return Err(Error::BufferTooShort {
232 need: 3 + icon_data_length,
233 have: sel.len(),
234 what: "image_icon body",
235 });
236 }
237 let icon_data = &sel[3..3 + icon_data_length];
238 if 3 + icon_data_length != sel.len() {
239 return Err(invalid("image_icon: trailing bytes"));
240 }
241 ImageIconBody::Continuation { icon_data }
242 };
243
244 Ok(ImageIcon {
245 descriptor_number,
246 last_descriptor_number,
247 icon_id,
248 body,
249 })
250 }
251}
252
253impl Serialize for ImageIcon<'_> {
254 type Error = crate::error::Error;
255 fn serialized_len(&self) -> usize {
256 2 + match &self.body {
257 ImageIconBody::First(f) => {
258 1 + if f.position.is_some() { 3 } else { 0 }
260 + 1 + f.icon_type.len()
262 + match &f.payload {
263 IconLocation::Data(d) => 1 + d.len(),
264 IconLocation::Url(u) => 1 + u.len(),
265 IconLocation::None => 0,
266 }
267 }
268 ImageIconBody::Continuation { icon_data } => 1 + icon_data.len(),
269 }
270 }
271 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
272 let len = self.serialized_len();
273 if buf.len() < len {
274 return Err(Error::OutputBufferTooSmall {
275 need: len,
276 have: buf.len(),
277 });
278 }
279 buf[0] = (self.descriptor_number << 4) | (self.last_descriptor_number & 0x0F);
281 buf[1] = 0xF8 | (self.icon_id & 0x07);
283 let mut p = 2;
284 match &self.body {
285 ImageIconBody::First(f) => {
286 let position_flag = u8::from(f.position.is_some());
288 if let Some(pos) = &f.position {
289 buf[p] = (f.icon_transport_mode << 6)
291 | (position_flag << 5)
292 | ((pos.coordinate_system & 0x07) << 2)
293 | 0x03;
294 p += 1;
295 let h = pos.icon_horizontal_origin & 0x0FFF;
297 let v = pos.icon_vertical_origin & 0x0FFF;
298 buf[p] = (h >> 4) as u8;
299 buf[p + 1] = (((h & 0x0F) << 4) | ((v >> 8) & 0x0F)) as u8;
300 buf[p + 2] = v as u8;
301 p += 3;
302 } else {
303 buf[p] = (f.icon_transport_mode << 6) | (position_flag << 5) | 0x1F;
305 p += 1;
306 }
307 buf[p] = f.icon_type.len() as u8;
309 p += 1;
310 buf[p..p + f.icon_type.len()].copy_from_slice(f.icon_type.raw());
311 p += f.icon_type.len();
312 match &f.payload {
314 IconLocation::Data(d) => {
315 buf[p] = d.len() as u8;
316 p += 1;
317 buf[p..p + d.len()].copy_from_slice(d);
318 }
319 IconLocation::Url(u) => {
320 buf[p] = u.len() as u8;
321 p += 1;
322 buf[p..p + u.len()].copy_from_slice(u.raw());
323 }
324 IconLocation::None => {}
325 }
326 }
327 ImageIconBody::Continuation { icon_data } => {
328 buf[p] = icon_data.len() as u8;
329 p += 1;
330 buf[p..p + icon_data.len()].copy_from_slice(icon_data);
331 }
332 }
333 Ok(len)
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340 use crate::descriptors::extension::test_support::*;
341 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
342
343 #[test]
344 fn parse_image_icon_first_position_mode0() {
345 let sel = [
353 0x03, 0x05, 0x24, 0x06, 0x40, 0xC8, 0x02, 0xDE, 0xAD, 0x03, 0x01, 0x02, 0x03,
354 ];
355 let bytes = wrap(0x00, &sel);
356 let d = ExtensionDescriptor::parse(&bytes).unwrap();
357 assert_eq!(d.kind(), Some(ExtensionTag::ImageIcon));
358 match &d.body {
359 ExtensionBody::ImageIcon(b) => {
360 assert_eq!(b.descriptor_number, 0);
361 assert_eq!(b.last_descriptor_number, 3);
362 assert_eq!(b.icon_id, 5);
363 match &b.body {
364 ImageIconBody::First(f) => {
365 assert_eq!(f.icon_transport_mode, 0);
366 assert!(f.position.is_some());
367 let pos = f.position.as_ref().unwrap();
368 assert_eq!(pos.coordinate_system, 1);
369 assert_eq!(pos.icon_horizontal_origin, 100);
370 assert_eq!(pos.icon_vertical_origin, 200);
371 assert_eq!(f.icon_type.raw(), &[0xDE, 0xAD]);
372 match &f.payload {
373 IconLocation::Data(d) => assert_eq!(*d, &[0x01, 0x02, 0x03]),
374 other => panic!("expected Data, got {other:?}"),
375 }
376 }
377 other => panic!("expected First, got {other:?}"),
378 }
379 }
380 other => panic!("expected ImageIcon, got {other:?}"),
381 }
382 round_trip(&d);
383 }
384
385 #[test]
386 fn parse_image_icon_first_no_position_mode1() {
387 let sel = [0x01, 0x07, 0x40, 0x01, 0xAB, 0x04, b'h', b't', b't', b'p'];
392 let bytes = wrap(0x00, &sel);
393 let d = ExtensionDescriptor::parse(&bytes).unwrap();
394 match &d.body {
395 ExtensionBody::ImageIcon(b) => match &b.body {
396 ImageIconBody::First(f) => {
397 assert_eq!(f.icon_transport_mode, 1);
398 assert!(f.position.is_none());
399 assert_eq!(f.icon_type.raw(), &[0xAB]);
400 match &f.payload {
401 IconLocation::Url(u) => assert_eq!(u.decode(), "http"),
402 other => panic!("expected Url, got {other:?}"),
403 }
404 }
405 other => panic!("expected First, got {other:?}"),
406 },
407 other => panic!("expected ImageIcon, got {other:?}"),
408 }
409 round_trip(&d);
410 }
411
412 #[test]
413 fn parse_image_icon_first_mode2_empty_payload() {
414 let sel = [0x00, 0x00, 0x80, 0x00];
418 let bytes = wrap(0x00, &sel);
419 let d = ExtensionDescriptor::parse(&bytes).unwrap();
420 match &d.body {
421 ExtensionBody::ImageIcon(b) => match &b.body {
422 ImageIconBody::First(f) => {
423 assert_eq!(f.icon_transport_mode, 2);
424 assert!(f.position.is_none());
425 assert!(f.icon_type.is_empty());
426 assert!(matches!(f.payload, IconLocation::None));
427 }
428 other => panic!("expected First, got {other:?}"),
429 },
430 other => panic!("expected ImageIcon, got {other:?}"),
431 }
432 round_trip(&d);
433 }
434
435 #[test]
436 fn parse_image_icon_continuation() {
437 let sel = [0x23, 0x01, 0x04, 0xAA, 0xBB, 0xCC, 0xDD];
441 let bytes = wrap(0x00, &sel);
442 let d = ExtensionDescriptor::parse(&bytes).unwrap();
443 assert_eq!(d.kind(), Some(ExtensionTag::ImageIcon));
444 match &d.body {
445 ExtensionBody::ImageIcon(b) => {
446 assert_eq!(b.descriptor_number, 2);
447 assert_eq!(b.last_descriptor_number, 3);
448 assert_eq!(b.icon_id, 1);
449 match &b.body {
450 ImageIconBody::Continuation { icon_data } => {
451 assert_eq!(icon_data, &[0xAA, 0xBB, 0xCC, 0xDD]);
452 }
453 other => panic!("expected Continuation, got {other:?}"),
454 }
455 }
456 other => panic!("expected ImageIcon, got {other:?}"),
457 }
458 round_trip(&d);
459 }
460
461 #[test]
462 fn parse_image_icon_rejects_trailing_bytes() {
463 let sel = [0x00, 0x00, 0x80, 0x00, 0xFF];
466 let bytes = wrap(0x00, &sel);
467 assert!(matches!(
468 ExtensionDescriptor::parse(&bytes).unwrap_err(),
469 crate::error::Error::InvalidDescriptor {
470 tag: super::TAG,
471 ..
472 }
473 ));
474 }
475
476 #[test]
477 fn parse_image_icon_rejects_truncated_continuation() {
478 let sel = [0x23, 0x01, 0x05, 0xAA, 0xBB, 0xCC];
480 let bytes = wrap(0x00, &sel);
481 assert!(matches!(
482 ExtensionDescriptor::parse(&bytes).unwrap_err(),
483 crate::error::Error::BufferTooShort { .. }
484 ));
485 }
486
487 #[cfg(feature = "serde")]
488 #[test]
489 fn serde_serialize_image_icon() {
490 let d = ExtensionDescriptor {
491 tag_extension: 0x00,
492 body: ExtensionBody::ImageIcon(ImageIcon {
493 descriptor_number: 2,
494 last_descriptor_number: 3,
495 icon_id: 1,
496 body: ImageIconBody::Continuation {
497 icon_data: &[0xAA, 0xBB],
498 },
499 }),
500 };
501 let json = serde_json::to_string(&d).unwrap();
502 assert!(json.contains("\"tag_extension\":0"));
503 assert!(json.contains("\"imageIcon\""));
504 }
505}