1use super::*;
4use alloc::vec::Vec;
5
6impl<'a> ExtensionBodyDef<'a> for CpcmDeliverySignalling<'a> {
7 const TAG_EXTENSION: u8 = 0x01;
8 const NAME: &'static str = "CPCM_DELIVERY_SIGNALLING";
9}
10
11#[derive(Debug, Clone, PartialEq, Eq)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize))]
17#[cfg_attr(feature = "yoke", derive(yoke::Yokeable))]
18pub struct CpcmDeliverySignalling<'a> {
19 pub cpcm_version: u8,
21 #[cfg_attr(feature = "serde", serde(borrow))]
23 pub selector_bytes: &'a [u8],
24}
25
26impl<'a> CpcmDeliverySignalling<'a> {
27 #[must_use]
33 pub fn usi(&self) -> Option<Result<CpcmUsi>> {
34 if self.cpcm_version == 1 {
35 Some(CpcmUsi::parse(self.selector_bytes))
36 } else {
37 None
38 }
39 }
40}
41
42impl<'a> Parse<'a> for CpcmDeliverySignalling<'a> {
43 type Error = crate::error::Error;
44 fn parse(sel: &'a [u8]) -> Result<Self> {
45 let (cpcm_version, selector_bytes) = sel.split_first().ok_or(Error::BufferTooShort {
46 need: 1,
47 have: 0,
48 what: "cpcm_delivery_signalling body",
49 })?;
50 Ok(CpcmDeliverySignalling {
51 cpcm_version: *cpcm_version,
52 selector_bytes,
53 })
54 }
55}
56
57impl Serialize for CpcmDeliverySignalling<'_> {
58 type Error = crate::error::Error;
59 fn serialized_len(&self) -> usize {
60 1 + self.selector_bytes.len()
61 }
62 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
63 let len = self.serialized_len();
64 if buf.len() < len {
65 return Err(Error::OutputBufferTooSmall {
66 need: len,
67 have: buf.len(),
68 });
69 }
70 buf[0] = self.cpcm_version;
71 buf[1..len].copy_from_slice(self.selector_bytes);
72 Ok(len)
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82#[cfg_attr(feature = "serde", derive(serde::Serialize))]
83#[non_exhaustive]
84#[repr(u8)]
85pub enum CopyControl {
86 CopyControlNotAsserted = 0,
88 CopyOnce = 1,
90 CopyNoMore = 2,
92 CopyNeverZeroRetentionNotAsserted = 3,
94 CopyNeverZeroRetentionAsserted = 4,
96 Reserved(u8) = 5,
98}
99
100impl CopyControl {
101 #[must_use]
103 pub fn from_u8(v: u8) -> Self {
104 match v & 0x07 {
105 0 => CopyControl::CopyControlNotAsserted,
106 1 => CopyControl::CopyOnce,
107 2 => CopyControl::CopyNoMore,
108 3 => CopyControl::CopyNeverZeroRetentionNotAsserted,
109 4 => CopyControl::CopyNeverZeroRetentionAsserted,
110 r => CopyControl::Reserved(r),
111 }
112 }
113
114 #[must_use]
116 pub const fn to_u8(self) -> u8 {
117 match self {
118 CopyControl::CopyControlNotAsserted => 0,
119 CopyControl::CopyOnce => 1,
120 CopyControl::CopyNoMore => 2,
121 CopyControl::CopyNeverZeroRetentionNotAsserted => 3,
122 CopyControl::CopyNeverZeroRetentionAsserted => 4,
123 CopyControl::Reserved(r) => r,
124 }
125 }
126
127 #[must_use]
129 pub fn name(self) -> &'static str {
130 match self {
131 CopyControl::CopyControlNotAsserted => "Copy Control Not Asserted",
132 CopyControl::CopyOnce => "Copy Once",
133 CopyControl::CopyNoMore => "Copy No More",
134 CopyControl::CopyNeverZeroRetentionNotAsserted => {
135 "Copy Never - Zero Retention Not Asserted"
136 }
137 CopyControl::CopyNeverZeroRetentionAsserted => "Copy Never - Zero Retention Asserted",
138 CopyControl::Reserved(_) => "reserved",
139 }
140 }
141}
142dvb_common::impl_spec_display!(CopyControl, Reserved);
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq)]
148#[cfg_attr(feature = "serde", derive(serde::Serialize))]
149#[non_exhaustive]
150#[repr(u8)]
151pub enum MoveCopyPropagation {
152 Mlad = 0,
154 Mgad = 1,
156 Mad = 2,
158 Mcpcm = 3,
160}
161
162impl MoveCopyPropagation {
163 #[must_use]
165 pub fn from_u8(v: u8) -> Self {
166 match v & 0x03 {
167 0 => MoveCopyPropagation::Mlad,
168 1 => MoveCopyPropagation::Mgad,
169 2 => MoveCopyPropagation::Mad,
170 _ => MoveCopyPropagation::Mcpcm,
171 }
172 }
173
174 #[must_use]
176 pub const fn to_u8(self) -> u8 {
177 self as u8
178 }
179
180 #[must_use]
182 pub fn name(self) -> &'static str {
183 match self {
184 MoveCopyPropagation::Mlad => "MLAD",
185 MoveCopyPropagation::Mgad => "MGAD",
186 MoveCopyPropagation::Mad => "MAD",
187 MoveCopyPropagation::Mcpcm => "MCPCM",
188 }
189 }
190}
191dvb_common::impl_spec_display!(MoveCopyPropagation);
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
197#[cfg_attr(feature = "serde", derive(serde::Serialize))]
198#[non_exhaustive]
199#[repr(u8)]
200pub enum ViewPropagation {
201 Vlad = 0,
203 Vgad = 1,
205 Vad = 2,
207 Vcpcm = 3,
209}
210
211impl ViewPropagation {
212 #[must_use]
214 pub fn from_u8(v: u8) -> Self {
215 match v & 0x03 {
216 0 => ViewPropagation::Vlad,
217 1 => ViewPropagation::Vgad,
218 2 => ViewPropagation::Vad,
219 _ => ViewPropagation::Vcpcm,
220 }
221 }
222
223 #[must_use]
225 pub const fn to_u8(self) -> u8 {
226 self as u8
227 }
228
229 #[must_use]
231 pub fn name(self) -> &'static str {
232 match self {
233 ViewPropagation::Vlad => "VLAD",
234 ViewPropagation::Vgad => "VGAD",
235 ViewPropagation::Vad => "VAD",
236 ViewPropagation::Vcpcm => "VCPCM",
237 }
238 }
239}
240dvb_common::impl_spec_display!(ViewPropagation);
241
242#[derive(Debug, Clone, PartialEq, Eq)]
247#[cfg_attr(feature = "serde", derive(serde::Serialize))]
248pub struct CpsVectorEntry {
249 pub c_and_r_regime_mask: u8,
251 pub cps_vector: Vec<u8>,
253}
254
255const USI_FLAGS_LEN: usize = 3;
258const CPCM_DATE_TIME_LEN: usize = 5;
260const CPCM_PLAYBACK_PERIOD_LEN: usize = 2;
262const SIMULTANEOUS_VIEW_COUNT_LEN: usize = 1;
264const CPS_ENTRY_HDR_LEN: usize = 3;
267
268#[derive(Debug, Clone, PartialEq, Eq)]
291#[cfg_attr(feature = "serde", derive(serde::Serialize))]
292pub struct CpcmUsi {
293 pub copy_control: CopyControl,
296 pub do_not_cpcm_scramble: bool,
298 pub viewable: bool,
300 pub view_window_activated: bool,
302 pub view_period_activated: bool,
304 pub simultaneous_view_count_activated: bool,
306 pub move_local: bool,
309 pub view_local: bool,
311 pub move_and_copy_propagation_information: MoveCopyPropagation,
313 pub view_propagation_information: ViewPropagation,
315 pub remote_access_date_moving_window_flag: bool,
317 pub remote_access_date_immediate_flag: bool,
319 pub remote_access_record_flag: bool,
322 pub export_controlled_cps: bool,
324 pub export_beyond_trust: bool,
326 pub disable_analogue_sd_export: bool,
328 pub disable_analogue_sd_consumption: bool,
330 pub disable_analogue_hd_export: bool,
332 pub disable_analogue_hd_consumption: bool,
334 pub image_constraint: bool,
336 pub view_window_start: Option<[u8; CPCM_DATE_TIME_LEN]>,
339 pub view_window_end: Option<[u8; CPCM_DATE_TIME_LEN]>,
341 pub view_period_from_first_playback: Option<[u8; CPCM_PLAYBACK_PERIOD_LEN]>,
344 pub simultaneous_view_count: Option<u8>,
347 pub remote_access_date: Option<[u8; CPCM_DATE_TIME_LEN]>,
350 pub cps_vectors: Vec<CpsVectorEntry>,
352}
353
354impl<'a> Parse<'a> for CpcmUsi {
355 type Error = crate::error::Error;
356
357 fn parse(bytes: &'a [u8]) -> Result<Self> {
358 if bytes.is_empty() {
360 return Err(Error::BufferTooShort {
361 need: 1,
362 have: 0,
363 what: "CpcmUsi length",
364 });
365 }
366 let length = bytes[0] as usize;
367 if bytes.len() < 1 + USI_FLAGS_LEN {
370 return Err(Error::BufferTooShort {
371 need: 1 + USI_FLAGS_LEN,
372 have: bytes.len(),
373 what: "CpcmUsi fixed flags",
374 });
375 }
376 if bytes.len() < 1 + length {
379 return Err(Error::BufferTooShort {
380 need: 1 + length,
381 have: bytes.len(),
382 what: "CpcmUsi body",
383 });
384 }
385 let body = &bytes[1..1 + length];
387 if body.len() < USI_FLAGS_LEN {
388 return Err(Error::BufferTooShort {
389 need: USI_FLAGS_LEN,
390 have: body.len(),
391 what: "CpcmUsi flag bytes",
392 });
393 }
394
395 let b1 = body[0];
397 let copy_control = CopyControl::from_u8(b1 >> 5);
398 let do_not_cpcm_scramble = (b1 >> 4) & 1 != 0;
399 let viewable = (b1 >> 3) & 1 != 0;
400 let view_window_activated = (b1 >> 2) & 1 != 0;
401 let view_period_activated = (b1 >> 1) & 1 != 0;
402 let simultaneous_view_count_activated = b1 & 1 != 0;
403
404 let b2 = body[1];
406 let move_local = (b2 >> 7) & 1 != 0;
407 let view_local = (b2 >> 6) & 1 != 0;
408 let move_and_copy_propagation_information = MoveCopyPropagation::from_u8(b2 >> 4);
409 let view_propagation_information = ViewPropagation::from_u8(b2 >> 2);
410 let remote_access_date_moving_window_flag = (b2 >> 1) & 1 != 0;
411 let remote_access_date_immediate_flag = b2 & 1 != 0;
412
413 let b3 = body[2];
415 let remote_access_record_flag = (b3 >> 7) & 1 != 0;
416 let export_controlled_cps = (b3 >> 6) & 1 != 0;
417 let export_beyond_trust = (b3 >> 5) & 1 != 0;
418 let disable_analogue_sd_export = (b3 >> 4) & 1 != 0;
419 let disable_analogue_sd_consumption = (b3 >> 3) & 1 != 0;
420 let disable_analogue_hd_export = (b3 >> 2) & 1 != 0;
421 let disable_analogue_hd_consumption = (b3 >> 1) & 1 != 0;
422 let image_constraint = b3 & 1 != 0;
423
424 let mut pos = USI_FLAGS_LEN; let view_window_start;
428 let view_window_end;
429 if view_window_activated {
430 if body.len() < pos + CPCM_DATE_TIME_LEN * 2 {
431 return Err(Error::BufferTooShort {
432 need: pos + CPCM_DATE_TIME_LEN * 2,
433 have: body.len(),
434 what: "CpcmUsi view_window_start/end",
435 });
436 }
437 let mut start = [0u8; CPCM_DATE_TIME_LEN];
438 start.copy_from_slice(&body[pos..pos + CPCM_DATE_TIME_LEN]);
439 pos += CPCM_DATE_TIME_LEN;
440 let mut end = [0u8; CPCM_DATE_TIME_LEN];
441 end.copy_from_slice(&body[pos..pos + CPCM_DATE_TIME_LEN]);
442 pos += CPCM_DATE_TIME_LEN;
443 view_window_start = Some(start);
444 view_window_end = Some(end);
445 } else {
446 view_window_start = None;
447 view_window_end = None;
448 }
449
450 let view_period_from_first_playback = if view_period_activated {
451 if body.len() < pos + CPCM_PLAYBACK_PERIOD_LEN {
452 return Err(Error::BufferTooShort {
453 need: pos + CPCM_PLAYBACK_PERIOD_LEN,
454 have: body.len(),
455 what: "CpcmUsi view_period_from_first_playback",
456 });
457 }
458 let mut vp = [0u8; CPCM_PLAYBACK_PERIOD_LEN];
459 vp.copy_from_slice(&body[pos..pos + CPCM_PLAYBACK_PERIOD_LEN]);
460 pos += CPCM_PLAYBACK_PERIOD_LEN;
461 Some(vp)
462 } else {
463 None
464 };
465
466 let simultaneous_view_count;
467 if simultaneous_view_count_activated {
468 if body.len() < pos + SIMULTANEOUS_VIEW_COUNT_LEN {
469 return Err(Error::BufferTooShort {
470 need: pos + SIMULTANEOUS_VIEW_COUNT_LEN,
471 have: body.len(),
472 what: "CpcmUsi simultaneous_view_count",
473 });
474 }
475 simultaneous_view_count = Some(body[pos]);
476 pos += SIMULTANEOUS_VIEW_COUNT_LEN;
477 } else {
478 simultaneous_view_count = None;
479 }
480
481 let remote_access_date =
482 if remote_access_date_immediate_flag || remote_access_date_moving_window_flag {
483 if body.len() < pos + CPCM_DATE_TIME_LEN {
484 return Err(Error::BufferTooShort {
485 need: pos + CPCM_DATE_TIME_LEN,
486 have: body.len(),
487 what: "CpcmUsi remote_access_date",
488 });
489 }
490 let mut rad = [0u8; CPCM_DATE_TIME_LEN];
491 rad.copy_from_slice(&body[pos..pos + CPCM_DATE_TIME_LEN]);
492 pos += CPCM_DATE_TIME_LEN;
493 Some(rad)
494 } else {
495 None
496 };
497
498 let mut cps_vectors = Vec::new();
499 if export_controlled_cps {
500 if body.len() < pos + 1 {
501 return Err(Error::BufferTooShort {
502 need: pos + 1,
503 have: body.len(),
504 what: "CpcmUsi cps_vector_count",
505 });
506 }
507 let cps_vector_count = body[pos] as usize;
508 pos += 1;
509 for _ in 0..cps_vector_count {
510 if body.len() < pos + CPS_ENTRY_HDR_LEN {
511 return Err(Error::BufferTooShort {
512 need: pos + CPS_ENTRY_HDR_LEN,
513 have: body.len(),
514 what: "CpcmUsi CPS entry header",
515 });
516 }
517 let c_and_r_regime_mask = body[pos];
518 let cps_vector_length = u16::from_be_bytes([body[pos + 1], body[pos + 2]]) as usize;
519 pos += CPS_ENTRY_HDR_LEN;
520 if body.len() < pos + cps_vector_length {
521 return Err(Error::BufferTooShort {
522 need: pos + cps_vector_length,
523 have: body.len(),
524 what: "CpcmUsi cps_vector_byte",
525 });
526 }
527 let cps_vector = body[pos..pos + cps_vector_length].to_vec();
528 pos += cps_vector_length;
529 cps_vectors.push(CpsVectorEntry {
530 c_and_r_regime_mask,
531 cps_vector,
532 });
533 }
534 }
535
536 Ok(CpcmUsi {
537 copy_control,
538 do_not_cpcm_scramble,
539 viewable,
540 view_window_activated,
541 view_period_activated,
542 simultaneous_view_count_activated,
543 move_local,
544 view_local,
545 move_and_copy_propagation_information,
546 view_propagation_information,
547 remote_access_date_moving_window_flag,
548 remote_access_date_immediate_flag,
549 remote_access_record_flag,
550 export_controlled_cps,
551 export_beyond_trust,
552 disable_analogue_sd_export,
553 disable_analogue_sd_consumption,
554 disable_analogue_hd_export,
555 disable_analogue_hd_consumption,
556 image_constraint,
557 view_window_start,
558 view_window_end,
559 view_period_from_first_playback,
560 simultaneous_view_count,
561 remote_access_date,
562 cps_vectors,
563 })
564 }
565}
566
567impl CpcmUsi {
568 fn body_len(&self) -> usize {
570 let mut n = USI_FLAGS_LEN;
571 if self.view_window_activated {
572 n += CPCM_DATE_TIME_LEN * 2;
573 }
574 if self.view_period_activated {
575 n += CPCM_PLAYBACK_PERIOD_LEN;
576 }
577 if self.simultaneous_view_count_activated {
578 n += SIMULTANEOUS_VIEW_COUNT_LEN;
579 }
580 if self.remote_access_date_immediate_flag || self.remote_access_date_moving_window_flag {
581 n += CPCM_DATE_TIME_LEN;
582 }
583 if self.export_controlled_cps {
584 n += 1; for entry in &self.cps_vectors {
586 n += CPS_ENTRY_HDR_LEN + entry.cps_vector.len();
587 }
588 }
589 n
590 }
591}
592
593impl Serialize for CpcmUsi {
594 type Error = crate::error::Error;
595
596 fn serialized_len(&self) -> usize {
597 1 + self.body_len() }
599
600 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
601 let total = self.serialized_len();
602 if buf.len() < total {
603 return Err(Error::OutputBufferTooSmall {
604 need: total,
605 have: buf.len(),
606 });
607 }
608 let body_len = self.body_len();
609 buf[0] = body_len as u8;
610
611 buf[1] = (self.copy_control.to_u8() << 5)
613 | ((u8::from(self.do_not_cpcm_scramble)) << 4)
614 | ((u8::from(self.viewable)) << 3)
615 | ((u8::from(self.view_window_activated)) << 2)
616 | ((u8::from(self.view_period_activated)) << 1)
617 | u8::from(self.simultaneous_view_count_activated);
618
619 buf[2] = ((u8::from(self.move_local)) << 7)
621 | ((u8::from(self.view_local)) << 6)
622 | ((self.move_and_copy_propagation_information.to_u8() & 0x03) << 4)
623 | ((self.view_propagation_information.to_u8() & 0x03) << 2)
624 | ((u8::from(self.remote_access_date_moving_window_flag)) << 1)
625 | u8::from(self.remote_access_date_immediate_flag);
626
627 buf[3] = ((u8::from(self.remote_access_record_flag)) << 7)
629 | ((u8::from(self.export_controlled_cps)) << 6)
630 | ((u8::from(self.export_beyond_trust)) << 5)
631 | ((u8::from(self.disable_analogue_sd_export)) << 4)
632 | ((u8::from(self.disable_analogue_sd_consumption)) << 3)
633 | ((u8::from(self.disable_analogue_hd_export)) << 2)
634 | ((u8::from(self.disable_analogue_hd_consumption)) << 1)
635 | u8::from(self.image_constraint);
636
637 let mut pos = 1 + USI_FLAGS_LEN; if self.view_window_activated {
641 if let (Some(start), Some(end)) = (self.view_window_start, self.view_window_end) {
642 buf[pos..pos + CPCM_DATE_TIME_LEN].copy_from_slice(&start);
643 pos += CPCM_DATE_TIME_LEN;
644 buf[pos..pos + CPCM_DATE_TIME_LEN].copy_from_slice(&end);
645 pos += CPCM_DATE_TIME_LEN;
646 }
647 }
648 if self.view_period_activated {
649 if let Some(vp) = self.view_period_from_first_playback {
650 buf[pos..pos + CPCM_PLAYBACK_PERIOD_LEN].copy_from_slice(&vp);
651 pos += CPCM_PLAYBACK_PERIOD_LEN;
652 }
653 }
654 if self.simultaneous_view_count_activated {
655 if let Some(svc) = self.simultaneous_view_count {
656 buf[pos] = svc;
657 pos += SIMULTANEOUS_VIEW_COUNT_LEN;
658 }
659 }
660 if self.remote_access_date_immediate_flag || self.remote_access_date_moving_window_flag {
661 if let Some(rad) = self.remote_access_date {
662 buf[pos..pos + CPCM_DATE_TIME_LEN].copy_from_slice(&rad);
663 pos += CPCM_DATE_TIME_LEN;
664 }
665 }
666 if self.export_controlled_cps {
667 buf[pos] = self.cps_vectors.len() as u8;
668 pos += 1;
669 for entry in &self.cps_vectors {
670 buf[pos] = entry.c_and_r_regime_mask;
671 let vlen = entry.cps_vector.len() as u16;
672 buf[pos + 1..pos + 3].copy_from_slice(&vlen.to_be_bytes());
673 pos += CPS_ENTRY_HDR_LEN;
674 buf[pos..pos + entry.cps_vector.len()].copy_from_slice(&entry.cps_vector);
675 pos += entry.cps_vector.len();
676 }
677 }
678 Ok(pos)
679 }
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685 use crate::descriptors::extension::test_support::*;
686 use crate::descriptors::extension::{ExtensionBody, ExtensionDescriptor};
687 use dvb_common::Serialize;
688
689 fn minimal_usi() -> CpcmUsi {
691 CpcmUsi {
692 copy_control: CopyControl::CopyOnce,
693 do_not_cpcm_scramble: false,
694 viewable: true,
695 view_window_activated: false,
696 view_period_activated: false,
697 simultaneous_view_count_activated: false,
698 move_local: false,
699 view_local: false,
700 move_and_copy_propagation_information: MoveCopyPropagation::Mlad,
701 view_propagation_information: ViewPropagation::Vlad,
702 remote_access_date_moving_window_flag: false,
703 remote_access_date_immediate_flag: false,
704 remote_access_record_flag: false,
705 export_controlled_cps: false,
706 export_beyond_trust: false,
707 disable_analogue_sd_export: false,
708 disable_analogue_sd_consumption: false,
709 disable_analogue_hd_export: false,
710 disable_analogue_hd_consumption: false,
711 image_constraint: false,
712 view_window_start: None,
713 view_window_end: None,
714 view_period_from_first_playback: None,
715 simultaneous_view_count: None,
716 remote_access_date: None,
717 cps_vectors: Vec::new(),
718 }
719 }
720
721 fn round_trip_usi(usi: &CpcmUsi) {
722 let mut buf = vec![0u8; usi.serialized_len()];
723 usi.serialize_into(&mut buf).unwrap();
724 let parsed = CpcmUsi::parse(&buf).unwrap();
725 assert_eq!(usi, &parsed, "USI round-trip mismatch");
726 }
727
728 #[test]
739 fn minimal_usi_byte_anchor() {
740 let usi = minimal_usi();
741 let expected = [
742 0x03, 0x28, 0x00, 0x00,
745 ];
746 let mut buf = vec![0u8; usi.serialized_len()];
747 usi.serialize_into(&mut buf).unwrap();
748 assert_eq!(
749 buf.as_slice(),
750 &expected,
751 "byte-anchor mismatch for minimal USI"
752 );
753 let back = CpcmUsi::parse(&expected).unwrap();
754 assert_eq!(usi, back);
755 }
756
757 #[test]
759 fn usi_view_window_round_trip() {
760 let mut usi = minimal_usi();
761 usi.view_window_activated = true;
762 usi.view_window_start = Some([0x01, 0x02, 0x03, 0x04, 0x05]);
763 usi.view_window_end = Some([0x06, 0x07, 0x08, 0x09, 0x0A]);
764 round_trip_usi(&usi);
765 let mut buf = vec![0u8; usi.serialized_len()];
767 usi.serialize_into(&mut buf).unwrap();
768 assert_eq!(buf[0], 13, "length byte for view_window USI");
769 assert_ne!(buf[1] & 0x04, 0, "view_window_activated bit must be set");
771 assert_eq!(&buf[4..9], &[0x01, 0x02, 0x03, 0x04, 0x05]);
773 assert_eq!(&buf[9..14], &[0x06, 0x07, 0x08, 0x09, 0x0A]);
774 }
775
776 #[test]
778 fn usi_view_period_round_trip() {
779 let mut usi = minimal_usi();
780 usi.view_period_activated = true;
781 usi.view_period_from_first_playback = Some([0xAB, 0xCD]);
782 round_trip_usi(&usi);
783 let mut buf = vec![0u8; usi.serialized_len()];
784 usi.serialize_into(&mut buf).unwrap();
785 assert_eq!(buf[0], 5, "length byte for view_period USI (3+2)");
786 assert_eq!(&buf[4..6], &[0xAB, 0xCD]);
787 }
788
789 #[test]
791 fn usi_simultaneous_view_count_round_trip() {
792 let mut usi = minimal_usi();
793 usi.simultaneous_view_count_activated = true;
794 usi.simultaneous_view_count = Some(4);
795 round_trip_usi(&usi);
796 let mut buf = vec![0u8; usi.serialized_len()];
797 usi.serialize_into(&mut buf).unwrap();
798 assert_eq!(buf[0], 4, "length byte for svc USI (3+1)");
799 assert_eq!(buf[4], 4u8);
800 }
801
802 #[test]
804 fn usi_remote_access_date_round_trip() {
805 let mut usi = minimal_usi();
806 usi.remote_access_date_immediate_flag = true;
807 usi.remote_access_date = Some([0x11, 0x22, 0x33, 0x44, 0x55]);
808 round_trip_usi(&usi);
809 let mut buf = vec![0u8; usi.serialized_len()];
810 usi.serialize_into(&mut buf).unwrap();
811 assert_eq!(buf[0], 8, "length byte for rad USI (3+5)");
812 assert_eq!(&buf[4..9], &[0x11, 0x22, 0x33, 0x44, 0x55]);
813 }
814
815 #[test]
817 fn usi_export_controlled_cps_round_trip() {
818 let mut usi = minimal_usi();
821 usi.export_controlled_cps = true;
822 usi.cps_vectors = vec![
823 CpsVectorEntry {
824 c_and_r_regime_mask: 0xA5,
825 cps_vector: vec![0x10, 0x20],
826 },
827 CpsVectorEntry {
828 c_and_r_regime_mask: 0x3C,
829 cps_vector: vec![0xDE, 0xAD, 0xBE],
830 },
831 ];
832 round_trip_usi(&usi);
833 let mut buf = vec![0u8; usi.serialized_len()];
834 usi.serialize_into(&mut buf).unwrap();
835 assert_eq!(buf[0], 15, "length byte for cps USI");
836 assert_eq!(buf[4], 2);
838 assert_eq!(buf[5], 0xA5);
840 assert_eq!(&buf[6..8], &[0x00, 0x02]);
841 assert_eq!(&buf[8..10], &[0x10, 0x20]);
842 assert_eq!(buf[10], 0x3C);
844 assert_eq!(&buf[11..13], &[0x00, 0x03]);
845 assert_eq!(&buf[13..16], &[0xDE, 0xAD, 0xBE]);
846 }
847
848 #[test]
850 fn usi_mutation_changes_expected_bytes() {
851 let usi1 = minimal_usi();
852 let mut usi2 = minimal_usi();
853 usi2.copy_control = CopyControl::CopyNeverZeroRetentionAsserted; usi2.image_constraint = true;
855
856 let mut buf1 = vec![0u8; usi1.serialized_len()];
857 let mut buf2 = vec![0u8; usi2.serialized_len()];
858 usi1.serialize_into(&mut buf1).unwrap();
859 usi2.serialize_into(&mut buf2).unwrap();
860
861 assert_eq!(buf1[1], 0x28);
865 assert_eq!(buf2[1], 0x88);
866 assert_eq!(buf1[3], 0x00);
868 assert_eq!(buf2[3], 0x01);
869 }
870
871 #[test]
873 fn copy_control_table9_mapping() {
874 assert_eq!(CopyControl::from_u8(0), CopyControl::CopyControlNotAsserted);
875 assert_eq!(CopyControl::from_u8(1), CopyControl::CopyOnce);
876 assert_eq!(CopyControl::from_u8(2), CopyControl::CopyNoMore);
877 assert_eq!(
878 CopyControl::from_u8(3),
879 CopyControl::CopyNeverZeroRetentionNotAsserted
880 );
881 assert_eq!(
882 CopyControl::from_u8(4),
883 CopyControl::CopyNeverZeroRetentionAsserted
884 );
885 assert_eq!(CopyControl::from_u8(5), CopyControl::Reserved(5));
887 assert_eq!(CopyControl::from_u8(6), CopyControl::Reserved(6));
888 assert_eq!(CopyControl::from_u8(7), CopyControl::Reserved(7));
889 for v in 0u8..=7 {
891 assert_eq!(CopyControl::from_u8(v).to_u8(), v);
892 }
893 assert_eq!(CopyControl::Reserved(5).name(), "reserved");
895 }
896
897 #[test]
899 fn usi_returns_none_for_non_v1() {
900 let desc = CpcmDeliverySignalling {
901 cpcm_version: 2,
902 selector_bytes: &[0x03, 0x28, 0x00, 0x00],
903 };
904 assert!(desc.usi().is_none());
905
906 let desc0 = CpcmDeliverySignalling {
907 cpcm_version: 0,
908 selector_bytes: &[],
909 };
910 assert!(desc0.usi().is_none());
911 }
912
913 #[test]
915 fn usi_returns_some_for_v1() {
916 let selector = [0x03u8, 0x28, 0x00, 0x00];
917 let desc = CpcmDeliverySignalling {
918 cpcm_version: 1,
919 selector_bytes: &selector,
920 };
921 let usi = desc
922 .usi()
923 .expect("should be Some")
924 .expect("should parse OK");
925 assert_eq!(usi.copy_control, CopyControl::CopyOnce);
926 assert!(usi.viewable);
927 }
928
929 #[test]
931 fn parse_cpcm_delivery_signalling_structured() {
932 let sel = [0x01, 0x39, 0x24, 0x45, 0x03];
934 let bytes = wrap(0x01, &sel);
935 let d = ExtensionDescriptor::parse(&bytes).unwrap();
936 match &d.body {
937 ExtensionBody::CpcmDeliverySignalling(b) => {
938 assert_eq!(b.cpcm_version, 1);
939 assert_eq!(b.selector_bytes, &[0x39, 0x24, 0x45, 0x03]);
940 }
941 other => panic!("expected CpcmDeliverySignalling, got {other:?}"),
942 }
943 round_trip(&d);
944 }
945
946 #[test]
947 fn parse_cpcm_delivery_signalling_version_only() {
948 let sel = [0x01]; let bytes = wrap(0x01, &sel);
950 let d = ExtensionDescriptor::parse(&bytes).unwrap();
951 match &d.body {
952 ExtensionBody::CpcmDeliverySignalling(b) => {
953 assert_eq!(b.cpcm_version, 1);
954 assert!(b.selector_bytes.is_empty());
955 }
956 other => panic!("expected CpcmDeliverySignalling, got {other:?}"),
957 }
958 round_trip(&d);
959 }
960}