1use super::*;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize))]
10#[non_exhaustive]
11pub enum IconTransportMode {
12 InlineData,
14 Url,
17 Reserved(u8),
19}
20
21impl IconTransportMode {
22 #[must_use]
23 pub fn from_u8(v: u8) -> Self {
26 match v & 0x03 {
27 0 => Self::InlineData,
28 1 => Self::Url,
29 v => Self::Reserved(v),
30 }
31 }
32
33 #[must_use]
34 pub fn to_u8(self) -> u8 {
36 match self {
37 Self::InlineData => 0,
38 Self::Url => 1,
39 Self::Reserved(v) => v,
40 }
41 }
42
43 #[must_use]
44 pub fn name(self) -> &'static str {
46 match self {
47 Self::InlineData => "inline data",
48 Self::Url => "URL",
49 Self::Reserved(_) => "reserved",
50 }
51 }
52}
53dvb_common::impl_spec_display!(IconTransportMode, Reserved);
54
55impl<'a> ExtensionBodyDef<'a> for ImageIcon<'a> {
56 const TAG_EXTENSION: u8 = 0x00;
57 const NAME: &'static str = "IMAGE_ICON";
58}
59
60#[derive(Debug, Clone, PartialEq, Eq)]
70#[cfg_attr(feature = "serde", derive(serde::Serialize))]
71#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
72pub struct ImageIcon<'a> {
73 pub descriptor_number: u8,
75 pub last_descriptor_number: u8,
77 pub icon_id: u8,
79 pub body: ImageIconBody<'a>,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq)]
85#[cfg_attr(feature = "serde", derive(serde::Serialize))]
86#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
87#[non_exhaustive]
88pub enum ImageIconBody<'a> {
89 First(ImageIconFirst<'a>),
91 Continuation {
93 icon_data: &'a [u8],
95 },
96}
97
98#[derive(Debug, Clone, PartialEq, Eq)]
100#[cfg_attr(feature = "serde", derive(serde::Serialize))]
101#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
102pub struct ImageIconFirst<'a> {
103 pub icon_transport_mode: IconTransportMode,
105 pub position: Option<IconPosition>,
107 pub icon_type: crate::text::DvbText<'a>,
109 pub payload: IconLocation<'a>,
112}
113
114#[derive(Debug, Clone, PartialEq, Eq)]
117#[cfg_attr(feature = "serde", derive(serde::Serialize))]
118#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
119#[non_exhaustive]
120pub enum IconLocation<'a> {
121 Data(&'a [u8]),
123 Url(crate::text::DvbText<'a>),
125 None,
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131#[cfg_attr(feature = "serde", derive(serde::Serialize))]
132pub struct IconPosition {
133 pub coordinate_system: u8,
135 pub icon_horizontal_origin: u16,
137 pub icon_vertical_origin: u16,
139}
140
141impl<'a> Parse<'a> for ImageIcon<'a> {
142 type Error = crate::error::Error;
143 fn parse(sel: &'a [u8]) -> Result<Self> {
144 if sel.len() < 2 {
147 return Err(Error::BufferTooShort {
148 need: 2,
149 have: sel.len(),
150 what: "image_icon body",
151 });
152 }
153 let descriptor_number = sel[0] >> 4;
154 let last_descriptor_number = sel[0] & 0x0F;
155 let icon_id = sel[1] & 0x07;
156
157 let body = if descriptor_number == 0x00 {
158 if sel.len() < 3 {
160 return Err(Error::BufferTooShort {
161 need: 3,
162 have: sel.len(),
163 what: "image_icon body",
164 });
165 }
166 let packed = sel[2];
167 let icon_transport_mode = IconTransportMode::from_u8(packed >> 6);
168 let position_flag = (packed >> 5) & 0x01;
169 let mut pos = 3usize;
170
171 let position = if position_flag == 1 {
172 let coordinate_system = (packed >> 2) & 0x07;
173 if sel.len() < pos + 3 {
174 return Err(Error::BufferTooShort {
175 need: pos + 3,
176 have: sel.len(),
177 what: "image_icon body",
178 });
179 }
180 let b0 = sel[pos];
181 let b1 = sel[pos + 1];
182 let b2 = sel[pos + 2];
183 let icon_horizontal_origin = (u16::from(b0) << 4) | (u16::from(b1) >> 4);
184 let icon_vertical_origin = ((u16::from(b1) & 0x0F) << 8) | u16::from(b2);
185 pos += 3;
186 Some(IconPosition {
187 coordinate_system,
188 icon_horizontal_origin,
189 icon_vertical_origin,
190 })
191 } else {
192 None
193 };
194
195 if sel.len() < pos + 1 {
197 return Err(Error::BufferTooShort {
198 need: pos + 1,
199 have: sel.len(),
200 what: "image_icon body",
201 });
202 }
203 let icon_type_length = sel[pos] as usize;
204 pos += 1;
205 if sel.len() < pos + icon_type_length {
206 return Err(Error::BufferTooShort {
207 need: pos + icon_type_length,
208 have: sel.len(),
209 what: "image_icon body",
210 });
211 }
212 let icon_type = crate::text::DvbText::new(&sel[pos..pos + icon_type_length]);
213 pos += icon_type_length;
214
215 let payload = match icon_transport_mode {
217 IconTransportMode::InlineData => {
218 if sel.len() < pos + 1 {
219 return Err(Error::BufferTooShort {
220 need: pos + 1,
221 have: sel.len(),
222 what: "image_icon body",
223 });
224 }
225 let payload_len = sel[pos] as usize;
226 pos += 1;
227 if sel.len() < pos + payload_len {
228 return Err(Error::BufferTooShort {
229 need: pos + payload_len,
230 have: sel.len(),
231 what: "image_icon body",
232 });
233 }
234 let p = &sel[pos..pos + payload_len];
235 pos += payload_len;
236 IconLocation::Data(p)
237 }
238 IconTransportMode::Url => {
239 if sel.len() < pos + 1 {
240 return Err(Error::BufferTooShort {
241 need: pos + 1,
242 have: sel.len(),
243 what: "image_icon body",
244 });
245 }
246 let payload_len = sel[pos] as usize;
247 pos += 1;
248 if sel.len() < pos + payload_len {
249 return Err(Error::BufferTooShort {
250 need: pos + payload_len,
251 have: sel.len(),
252 what: "image_icon body",
253 });
254 }
255 let t = crate::text::DvbText::new(&sel[pos..pos + payload_len]);
256 pos += payload_len;
257 IconLocation::Url(t)
258 }
259 _ => IconLocation::None,
260 };
261
262 if pos != sel.len() {
263 return Err(invalid("image_icon: trailing bytes"));
264 }
265
266 ImageIconBody::First(ImageIconFirst {
267 icon_transport_mode,
268 position,
269 icon_type,
270 payload,
271 })
272 } else {
273 if sel.len() < 3 {
275 return Err(Error::BufferTooShort {
276 need: 3,
277 have: sel.len(),
278 what: "image_icon body",
279 });
280 }
281 let icon_data_length = sel[2] as usize;
282 if sel.len() < 3 + icon_data_length {
283 return Err(Error::BufferTooShort {
284 need: 3 + icon_data_length,
285 have: sel.len(),
286 what: "image_icon body",
287 });
288 }
289 let icon_data = &sel[3..3 + icon_data_length];
290 if 3 + icon_data_length != sel.len() {
291 return Err(invalid("image_icon: trailing bytes"));
292 }
293 ImageIconBody::Continuation { icon_data }
294 };
295
296 Ok(ImageIcon {
297 descriptor_number,
298 last_descriptor_number,
299 icon_id,
300 body,
301 })
302 }
303}
304
305impl Serialize for ImageIcon<'_> {
306 type Error = crate::error::Error;
307 fn serialized_len(&self) -> usize {
308 2 + match &self.body {
309 ImageIconBody::First(f) => {
310 1 + if f.position.is_some() { 3 } else { 0 }
312 + 1 + f.icon_type.len()
314 + match &f.payload {
315 IconLocation::Data(d) => 1 + d.len(),
316 IconLocation::Url(u) => 1 + u.len(),
317 IconLocation::None => 0,
318 }
319 }
320 ImageIconBody::Continuation { icon_data } => 1 + icon_data.len(),
321 }
322 }
323 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
324 let len = self.serialized_len();
325 if buf.len() < len {
326 return Err(Error::OutputBufferTooSmall {
327 need: len,
328 have: buf.len(),
329 });
330 }
331 buf[0] = (self.descriptor_number << 4) | (self.last_descriptor_number & 0x0F);
333 buf[1] = 0xF8 | (self.icon_id & 0x07);
335 let mut p = 2;
336 match &self.body {
337 ImageIconBody::First(f) => {
338 let position_flag = u8::from(f.position.is_some());
340 let itm = f.icon_transport_mode.to_u8() & 0x03;
341 if let Some(pos) = &f.position {
342 buf[p] = (itm << 6)
344 | (position_flag << 5)
345 | ((pos.coordinate_system & 0x07) << 2)
346 | 0x03;
347 p += 1;
348 let h = pos.icon_horizontal_origin & 0x0FFF;
350 let v = pos.icon_vertical_origin & 0x0FFF;
351 buf[p] = (h >> 4) as u8;
352 buf[p + 1] = (((h & 0x0F) << 4) | ((v >> 8) & 0x0F)) as u8;
353 buf[p + 2] = v as u8;
354 p += 3;
355 } else {
356 buf[p] = (itm << 6) | (position_flag << 5) | 0x1F;
358 p += 1;
359 }
360 buf[p] = f.icon_type.len() as u8;
362 p += 1;
363 buf[p..p + f.icon_type.len()].copy_from_slice(f.icon_type.raw());
364 p += f.icon_type.len();
365 match &f.payload {
367 IconLocation::Data(d) => {
368 buf[p] = d.len() as u8;
369 p += 1;
370 buf[p..p + d.len()].copy_from_slice(d);
371 }
372 IconLocation::Url(u) => {
373 buf[p] = u.len() as u8;
374 p += 1;
375 buf[p..p + u.len()].copy_from_slice(u.raw());
376 }
377 IconLocation::None => {}
378 }
379 }
380 ImageIconBody::Continuation { icon_data } => {
381 buf[p] = icon_data.len() as u8;
382 p += 1;
383 buf[p..p + icon_data.len()].copy_from_slice(icon_data);
384 }
385 }
386 Ok(len)
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::*;
393 use crate::descriptors::extension::test_support::*;
394 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor, ExtensionTag};
395
396 #[test]
397 fn parse_image_icon_first_position_mode0() {
398 let sel = [
406 0x03, 0x05, 0x24, 0x06, 0x40, 0xC8, 0x02, 0xDE, 0xAD, 0x03, 0x01, 0x02, 0x03,
407 ];
408 let bytes = wrap(0x00, &sel);
409 let d = ExtensionDescriptor::parse(&bytes).unwrap();
410 assert_eq!(d.kind(), Some(ExtensionTag::ImageIcon));
411 match &d.body {
412 ExtensionBody::ImageIcon(b) => {
413 assert_eq!(b.descriptor_number, 0);
414 assert_eq!(b.last_descriptor_number, 3);
415 assert_eq!(b.icon_id, 5);
416 match &b.body {
417 ImageIconBody::First(f) => {
418 assert_eq!(f.icon_transport_mode, IconTransportMode::InlineData);
419 assert!(f.position.is_some());
420 let pos = f.position.as_ref().unwrap();
421 assert_eq!(pos.coordinate_system, 1);
422 assert_eq!(pos.icon_horizontal_origin, 100);
423 assert_eq!(pos.icon_vertical_origin, 200);
424 assert_eq!(f.icon_type.raw(), &[0xDE, 0xAD]);
425 match &f.payload {
426 IconLocation::Data(d) => assert_eq!(*d, &[0x01, 0x02, 0x03]),
427 other => panic!("expected Data, got {other:?}"),
428 }
429 }
430 other => panic!("expected First, got {other:?}"),
431 }
432 }
433 other => panic!("expected ImageIcon, got {other:?}"),
434 }
435 round_trip(&d);
436 }
437
438 #[test]
439 fn parse_image_icon_first_no_position_mode1() {
440 let sel = [0x01, 0x07, 0x40, 0x01, 0xAB, 0x04, b'h', b't', b't', b'p'];
445 let bytes = wrap(0x00, &sel);
446 let d = ExtensionDescriptor::parse(&bytes).unwrap();
447 match &d.body {
448 ExtensionBody::ImageIcon(b) => match &b.body {
449 ImageIconBody::First(f) => {
450 assert_eq!(f.icon_transport_mode, IconTransportMode::Url);
451 assert!(f.position.is_none());
452 assert_eq!(f.icon_type.raw(), &[0xAB]);
453 match &f.payload {
454 IconLocation::Url(u) => assert_eq!(u.decode(), "http"),
455 other => panic!("expected Url, got {other:?}"),
456 }
457 }
458 other => panic!("expected First, got {other:?}"),
459 },
460 other => panic!("expected ImageIcon, got {other:?}"),
461 }
462 round_trip(&d);
463 }
464
465 #[test]
466 fn parse_image_icon_first_mode2_empty_payload() {
467 let sel = [0x00, 0x00, 0x80, 0x00];
471 let bytes = wrap(0x00, &sel);
472 let d = ExtensionDescriptor::parse(&bytes).unwrap();
473 match &d.body {
474 ExtensionBody::ImageIcon(b) => match &b.body {
475 ImageIconBody::First(f) => {
476 assert_eq!(f.icon_transport_mode, IconTransportMode::Reserved(2));
477 assert!(f.position.is_none());
478 assert!(f.icon_type.is_empty());
479 assert!(matches!(f.payload, IconLocation::None));
480 }
481 other => panic!("expected First, got {other:?}"),
482 },
483 other => panic!("expected ImageIcon, got {other:?}"),
484 }
485 round_trip(&d);
486 }
487
488 #[test]
489 fn parse_image_icon_continuation() {
490 let sel = [0x23, 0x01, 0x04, 0xAA, 0xBB, 0xCC, 0xDD];
494 let bytes = wrap(0x00, &sel);
495 let d = ExtensionDescriptor::parse(&bytes).unwrap();
496 assert_eq!(d.kind(), Some(ExtensionTag::ImageIcon));
497 match &d.body {
498 ExtensionBody::ImageIcon(b) => {
499 assert_eq!(b.descriptor_number, 2);
500 assert_eq!(b.last_descriptor_number, 3);
501 assert_eq!(b.icon_id, 1);
502 match &b.body {
503 ImageIconBody::Continuation { icon_data } => {
504 assert_eq!(icon_data, &[0xAA, 0xBB, 0xCC, 0xDD]);
505 }
506 other => panic!("expected Continuation, got {other:?}"),
507 }
508 }
509 other => panic!("expected ImageIcon, got {other:?}"),
510 }
511 round_trip(&d);
512 }
513
514 #[test]
515 fn parse_image_icon_rejects_trailing_bytes() {
516 let sel = [0x00, 0x00, 0x80, 0x00, 0xFF];
519 let bytes = wrap(0x00, &sel);
520 assert!(matches!(
521 ExtensionDescriptor::parse(&bytes).unwrap_err(),
522 crate::error::Error::InvalidDescriptor {
523 tag: super::TAG,
524 ..
525 }
526 ));
527 }
528
529 #[test]
530 fn parse_image_icon_rejects_truncated_continuation() {
531 let sel = [0x23, 0x01, 0x05, 0xAA, 0xBB, 0xCC];
533 let bytes = wrap(0x00, &sel);
534 assert!(matches!(
535 ExtensionDescriptor::parse(&bytes).unwrap_err(),
536 crate::error::Error::BufferTooShort { .. }
537 ));
538 }
539
540 #[cfg(feature = "serde")]
541 #[test]
542 fn serde_serialize_image_icon() {
543 let d = ExtensionDescriptor {
544 tag_extension: 0x00,
545 body: ExtensionBody::ImageIcon(ImageIcon {
546 descriptor_number: 2,
547 last_descriptor_number: 3,
548 icon_id: 1,
549 body: ImageIconBody::Continuation {
550 icon_data: &[0xAA, 0xBB],
551 },
552 }),
553 };
554 let json = serde_json::to_string(&d).unwrap();
555 assert!(json.contains("\"tag_extension\":0"));
556 assert!(json.contains("\"imageIcon\""));
557 }
558
559 #[test]
560 fn icon_transport_mode_full_range_round_trip() {
561 for v in 0u8..=0x03 {
562 let itm = IconTransportMode::from_u8(v);
563 assert_eq!(
564 itm.to_u8(),
565 v,
566 "IconTransportMode round-trip failed for {v}"
567 );
568 }
569 }
570
571 #[test]
572 fn icon_transport_mode_known_values() {
573 assert_eq!(IconTransportMode::from_u8(0), IconTransportMode::InlineData);
574 assert_eq!(IconTransportMode::from_u8(1), IconTransportMode::Url);
575 assert_eq!(
576 IconTransportMode::from_u8(2),
577 IconTransportMode::Reserved(2)
578 );
579 assert_eq!(IconTransportMode::InlineData.name(), "inline data");
580 assert_eq!(IconTransportMode::Url.name(), "URL");
581 assert_eq!(IconTransportMode::Reserved(3).name(), "reserved");
582 }
583}