1use crate::error::{Error, Result};
23use crate::objects;
24use alloc::vec::Vec;
25use broadcast_common::{Parse, Serialize};
26
27pub mod tag {
29 use crate::tag::ApduTag;
30 pub const STATUS_QUERY_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
32 pub const TRAP_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
34 pub const GET_NEXT_ITEM_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
36 pub const GET_NEXT_ITEM_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
38 pub const STATUS_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
40}
41
42const STATUS_ITEM_LEN: usize = 4;
44
45fn read_u32(b: &[u8]) -> u32 {
46 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize))]
52#[non_exhaustive]
53pub enum StatusItem {
54 SelectionInformation,
56 PortProfile,
58 ViewedService,
60 ActivationStatus,
62 Reserved(u32),
64}
65
66impl StatusItem {
67 #[must_use]
69 pub fn from_u32(v: u32) -> Self {
70 match v {
71 1 => Self::SelectionInformation,
72 2 => Self::PortProfile,
73 3 => Self::ViewedService,
74 4 => Self::ActivationStatus,
75 other => Self::Reserved(other),
76 }
77 }
78 #[must_use]
80 pub const fn to_u32(self) -> u32 {
81 match self {
82 Self::SelectionInformation => 1,
83 Self::PortProfile => 2,
84 Self::ViewedService => 3,
85 Self::ActivationStatus => 4,
86 Self::Reserved(v) => v,
87 }
88 }
89 #[must_use]
91 pub fn name(&self) -> &'static str {
92 match self {
93 Self::SelectionInformation => "Selection Information",
94 Self::PortProfile => "Port Profile",
95 Self::ViewedService => "Viewed Service",
96 Self::ActivationStatus => "Activation Status",
97 Self::Reserved(_) => "reserved",
98 }
99 }
100}
101broadcast_common::impl_spec_display!(StatusItem, Reserved);
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
107#[cfg_attr(feature = "serde", derive(serde::Serialize))]
108pub struct StatusQueryReq {
109 pub status_item: StatusItem,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[cfg_attr(feature = "serde", derive(serde::Serialize))]
116pub struct TrapReq {
117 pub status_item: StatusItem,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123#[cfg_attr(feature = "serde", derive(serde::Serialize))]
124pub struct GetNextItemReq {
125 pub start_status_item: u32,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq, Eq)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize))]
133pub struct GetNextItemAck {
134 pub next_status_item: u32,
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
143#[cfg_attr(feature = "serde", derive(serde::Serialize))]
144pub struct StatusAck<'a> {
145 pub status_item: StatusItem,
147 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
149 pub status_bytes: &'a [u8],
150}
151
152macro_rules! status_item_object {
153 ($ty:ident, $tag:expr, $what:literal, $field:ident, $decode:expr, $encode:expr) => {
154 impl<'a> Parse<'a> for $ty {
155 type Error = Error;
156 fn parse(bytes: &'a [u8]) -> Result<Self> {
157 let body = objects::parse_apdu_header(bytes, $tag, $what)?;
158 if body.len() < STATUS_ITEM_LEN {
159 return Err(Error::BufferTooShort {
160 need: STATUS_ITEM_LEN,
161 have: body.len(),
162 what: $what,
163 });
164 }
165 let raw = read_u32(body);
166 Ok(Self {
167 $field: $decode(raw),
168 })
169 }
170 }
171 impl Serialize for $ty {
172 type Error = Error;
173 fn serialized_len(&self) -> usize {
174 objects::apdu_len(STATUS_ITEM_LEN)
175 }
176 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
177 let pos = objects::write_apdu_header($tag, STATUS_ITEM_LEN, buf)?;
178 let raw: u32 = $encode(self.$field);
179 buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&raw.to_be_bytes());
180 Ok(pos + STATUS_ITEM_LEN)
181 }
182 }
183 };
184}
185
186status_item_object!(
187 StatusQueryReq,
188 tag::STATUS_QUERY_REQ,
189 "StatusQueryReq",
190 status_item,
191 StatusItem::from_u32,
192 StatusItem::to_u32
193);
194status_item_object!(
195 TrapReq,
196 tag::TRAP_REQ,
197 "TrapReq",
198 status_item,
199 StatusItem::from_u32,
200 StatusItem::to_u32
201);
202status_item_object!(
203 GetNextItemReq,
204 tag::GET_NEXT_ITEM_REQ,
205 "GetNextItemReq",
206 start_status_item,
207 core::convert::identity,
208 core::convert::identity
209);
210status_item_object!(
211 GetNextItemAck,
212 tag::GET_NEXT_ITEM_ACK,
213 "GetNextItemAck",
214 next_status_item,
215 core::convert::identity,
216 core::convert::identity
217);
218
219impl<'a> Parse<'a> for StatusAck<'a> {
220 type Error = Error;
221 fn parse(bytes: &'a [u8]) -> Result<Self> {
222 let body = objects::parse_apdu_header(bytes, tag::STATUS_ACK, "StatusAck")?;
223 if body.len() < STATUS_ITEM_LEN {
224 return Err(Error::BufferTooShort {
225 need: STATUS_ITEM_LEN,
226 have: body.len(),
227 what: "StatusAck",
228 });
229 }
230 Ok(Self {
231 status_item: StatusItem::from_u32(read_u32(body)),
232 status_bytes: &body[STATUS_ITEM_LEN..],
233 })
234 }
235}
236impl Serialize for StatusAck<'_> {
237 type Error = Error;
238 fn serialized_len(&self) -> usize {
239 objects::apdu_len(STATUS_ITEM_LEN + self.status_bytes.len())
240 }
241 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
242 let body_len = STATUS_ITEM_LEN + self.status_bytes.len();
243 let mut pos = objects::write_apdu_header(tag::STATUS_ACK, body_len, buf)?;
244 buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&self.status_item.to_u32().to_be_bytes());
245 pos += STATUS_ITEM_LEN;
246 buf[pos..pos + self.status_bytes.len()].copy_from_slice(self.status_bytes);
247 Ok(pos + self.status_bytes.len())
248 }
249}
250
251pub const SELECTION_TIME_LEN: usize = 5;
261
262#[derive(Debug, Clone, PartialEq, Eq, Default)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265pub struct OutputSignal<'a> {
266 pub out_port_id: u8,
268 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
271 pub out_signal_desc: &'a [u8],
272}
273
274#[derive(Debug, Clone, PartialEq, Eq, Default)]
277#[cfg_attr(feature = "serde", derive(serde::Serialize))]
278pub struct InPortDescription<'a> {
279 pub in_port_id: u8,
281 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
284 pub in_signal_desc: &'a [u8],
285 pub outputs: Vec<OutputSignal<'a>>,
287}
288
289#[derive(Debug, Clone, PartialEq, Eq, Default)]
292#[cfg_attr(feature = "serde", derive(serde::Serialize))]
293pub struct SelectionInformation<'a> {
294 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
296 pub time: &'a [u8],
297 pub ports: Vec<InPortDescription<'a>>,
299}
300
301impl<'a> Parse<'a> for SelectionInformation<'a> {
302 type Error = Error;
303 fn parse(body: &'a [u8]) -> Result<Self> {
304 if body.len() < SELECTION_TIME_LEN {
305 return Err(Error::BufferTooShort {
306 need: SELECTION_TIME_LEN,
307 have: body.len(),
308 what: "SelectionInformation.time",
309 });
310 }
311 let time = &body[..SELECTION_TIME_LEN];
312 let mut rest = &body[SELECTION_TIME_LEN..];
313 let mut ports = Vec::new();
314 while !rest.is_empty() {
315 if rest.len() < 2 {
317 return Err(Error::BufferTooShort {
318 need: 2,
319 have: rest.len(),
320 what: "SelectionInformation.in_port",
321 });
322 }
323 let in_port_id = rest[0];
324 let in_len = rest[1] as usize;
325 let after_in = 2 + in_len;
326 if rest.len() < after_in + 2 {
327 return Err(Error::BufferTooShort {
328 need: after_in + 2,
329 have: rest.len(),
330 what: "SelectionInformation.in_signal_desc",
331 });
332 }
333 let in_signal_desc = &rest[2..after_in];
334 let length_outputs =
336 (((rest[after_in] & 0x0F) as usize) << 8) | rest[after_in + 1] as usize;
337 let outputs_start = after_in + 2;
338 let outputs_end = outputs_start + length_outputs;
339 if rest.len() < outputs_end {
340 return Err(Error::BufferTooShort {
341 need: outputs_end,
342 have: rest.len(),
343 what: "SelectionInformation.outputs",
344 });
345 }
346 let mut out_rest = &rest[outputs_start..outputs_end];
347 let mut outputs = Vec::new();
348 while !out_rest.is_empty() {
349 if out_rest.len() < 2 {
351 return Err(Error::BufferTooShort {
352 need: 2,
353 have: out_rest.len(),
354 what: "SelectionInformation.out_port",
355 });
356 }
357 let out_port_id = out_rest[0];
358 let out_len = out_rest[1] as usize;
359 let out_end = 2 + out_len;
360 if out_rest.len() < out_end {
361 return Err(Error::BufferTooShort {
362 need: out_end,
363 have: out_rest.len(),
364 what: "SelectionInformation.out_signal_desc",
365 });
366 }
367 outputs.push(OutputSignal {
368 out_port_id,
369 out_signal_desc: &out_rest[2..out_end],
370 });
371 out_rest = &out_rest[out_end..];
372 }
373 ports.push(InPortDescription {
374 in_port_id,
375 in_signal_desc,
376 outputs,
377 });
378 rest = &rest[outputs_end..];
379 }
380 Ok(Self { time, ports })
381 }
382}
383impl Serialize for SelectionInformation<'_> {
384 type Error = Error;
385 fn serialized_len(&self) -> usize {
386 let mut n = SELECTION_TIME_LEN;
387 for p in &self.ports {
388 n += 2 + p.in_signal_desc.len() + 2;
389 for o in &p.outputs {
390 n += 2 + o.out_signal_desc.len();
391 }
392 }
393 n
394 }
395 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
396 let total = self.serialized_len();
397 if buf.len() < total {
398 return Err(Error::OutputBufferTooSmall {
399 need: total,
400 have: buf.len(),
401 });
402 }
403 if self.time.len() != SELECTION_TIME_LEN {
404 return Err(Error::InvalidObject {
405 what: "SelectionInformation",
406 reason: "time must be exactly 5 bytes",
407 });
408 }
409 let mut pos = 0;
410 buf[pos..pos + SELECTION_TIME_LEN].copy_from_slice(self.time);
411 pos += SELECTION_TIME_LEN;
412 for p in &self.ports {
413 if p.in_signal_desc.len() > u8::MAX as usize {
414 return Err(Error::InvalidObject {
415 what: "SelectionInformation",
416 reason: "in_signal_desc longer than 255 bytes",
417 });
418 }
419 buf[pos] = p.in_port_id;
420 buf[pos + 1] = p.in_signal_desc.len() as u8;
421 pos += 2;
422 buf[pos..pos + p.in_signal_desc.len()].copy_from_slice(p.in_signal_desc);
423 pos += p.in_signal_desc.len();
424 let mut length_outputs = 0usize;
426 for o in &p.outputs {
427 length_outputs += 2 + o.out_signal_desc.len();
428 }
429 if length_outputs > 0x0FFF {
430 return Err(Error::InvalidObject {
431 what: "SelectionInformation",
432 reason: "length_outputs exceeds 12-bit maximum",
433 });
434 }
435 buf[pos] = ((length_outputs >> 8) & 0x0F) as u8;
437 buf[pos + 1] = (length_outputs & 0xFF) as u8;
438 pos += 2;
439 for o in &p.outputs {
440 if o.out_signal_desc.len() > u8::MAX as usize {
441 return Err(Error::InvalidObject {
442 what: "SelectionInformation",
443 reason: "out_signal_desc longer than 255 bytes",
444 });
445 }
446 buf[pos] = o.out_port_id;
447 buf[pos + 1] = o.out_signal_desc.len() as u8;
448 pos += 2;
449 buf[pos..pos + o.out_signal_desc.len()].copy_from_slice(o.out_signal_desc);
450 pos += o.out_signal_desc.len();
451 }
452 }
453 Ok(pos)
454 }
455}
456
457#[derive(Debug, Clone, PartialEq, Eq, Default)]
459#[cfg_attr(feature = "serde", derive(serde::Serialize))]
460pub struct PortDescription<'a> {
461 pub in_port_id: u8,
463 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
465 pub in_port_desc: &'a [u8],
466 pub out_port_id: u8,
468 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
470 pub out_port_desc: &'a [u8],
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, Default)]
476#[cfg_attr(feature = "serde", derive(serde::Serialize))]
477pub struct PortProfile<'a> {
478 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
481 pub receiver_identification: &'a [u8],
482 pub ports: Vec<PortDescription<'a>>,
484}
485
486impl<'a> Parse<'a> for PortProfile<'a> {
487 type Error = Error;
488 fn parse(body: &'a [u8]) -> Result<Self> {
489 if body.is_empty() {
490 return Err(Error::BufferTooShort {
491 need: 1,
492 have: 0,
493 what: "PortProfile.receiver_identification_length",
494 });
495 }
496 let id_len = body[0] as usize;
497 if body.len() < 1 + id_len {
498 return Err(Error::BufferTooShort {
499 need: 1 + id_len,
500 have: body.len(),
501 what: "PortProfile.receiver_identification",
502 });
503 }
504 let receiver_identification = &body[1..1 + id_len];
505 let mut rest = &body[1 + id_len..];
506 let mut ports = Vec::new();
507 while !rest.is_empty() {
508 if rest.len() < 2 {
510 return Err(Error::BufferTooShort {
511 need: 2,
512 have: rest.len(),
513 what: "PortProfile.in_port",
514 });
515 }
516 let in_port_id = rest[0];
517 let in_len = rest[1] as usize;
518 let after_in = 2 + in_len;
519 if rest.len() < after_in + 2 {
521 return Err(Error::BufferTooShort {
522 need: after_in + 2,
523 have: rest.len(),
524 what: "PortProfile.out_port",
525 });
526 }
527 let in_port_desc = &rest[2..after_in];
528 let out_port_id = rest[after_in];
529 let out_len = rest[after_in + 1] as usize;
530 let after_out = after_in + 2 + out_len;
531 if rest.len() < after_out {
532 return Err(Error::BufferTooShort {
533 need: after_out,
534 have: rest.len(),
535 what: "PortProfile.out_port_desc",
536 });
537 }
538 ports.push(PortDescription {
539 in_port_id,
540 in_port_desc,
541 out_port_id,
542 out_port_desc: &rest[after_in + 2..after_out],
543 });
544 rest = &rest[after_out..];
545 }
546 Ok(Self {
547 receiver_identification,
548 ports,
549 })
550 }
551}
552impl Serialize for PortProfile<'_> {
553 type Error = Error;
554 fn serialized_len(&self) -> usize {
555 let mut n = 1 + self.receiver_identification.len();
556 for p in &self.ports {
557 n += 2 + p.in_port_desc.len() + 2 + p.out_port_desc.len();
558 }
559 n
560 }
561 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
562 let total = self.serialized_len();
563 if buf.len() < total {
564 return Err(Error::OutputBufferTooSmall {
565 need: total,
566 have: buf.len(),
567 });
568 }
569 if self.receiver_identification.len() > u8::MAX as usize {
570 return Err(Error::InvalidObject {
571 what: "PortProfile",
572 reason: "receiver_identification longer than 255 bytes",
573 });
574 }
575 let mut pos = 0;
576 buf[pos] = self.receiver_identification.len() as u8;
577 pos += 1;
578 buf[pos..pos + self.receiver_identification.len()]
579 .copy_from_slice(self.receiver_identification);
580 pos += self.receiver_identification.len();
581 for p in &self.ports {
582 if p.in_port_desc.len() > u8::MAX as usize || p.out_port_desc.len() > u8::MAX as usize {
583 return Err(Error::InvalidObject {
584 what: "PortProfile",
585 reason: "port description longer than 255 bytes",
586 });
587 }
588 buf[pos] = p.in_port_id;
589 buf[pos + 1] = p.in_port_desc.len() as u8;
590 pos += 2;
591 buf[pos..pos + p.in_port_desc.len()].copy_from_slice(p.in_port_desc);
592 pos += p.in_port_desc.len();
593 buf[pos] = p.out_port_id;
594 buf[pos + 1] = p.out_port_desc.len() as u8;
595 pos += 2;
596 buf[pos..pos + p.out_port_desc.len()].copy_from_slice(p.out_port_desc);
597 pos += p.out_port_desc.len();
598 }
599 Ok(pos)
600 }
601}
602
603#[derive(Debug, Clone, PartialEq, Eq, Default)]
605#[cfg_attr(feature = "serde", derive(serde::Serialize))]
606pub struct ViewedService {
607 pub service_id: u16,
609 pub component_tags: Vec<u8>,
611}
612
613impl<'a> Parse<'a> for ViewedService {
614 type Error = Error;
615 fn parse(body: &'a [u8]) -> Result<Self> {
616 if body.len() < 3 {
618 return Err(Error::BufferTooShort {
619 need: 3,
620 have: body.len(),
621 what: "ViewedService",
622 });
623 }
624 let service_id = u16::from_be_bytes([body[0], body[1]]);
625 let n = body[2] as usize;
626 if body.len() < 3 + n {
627 return Err(Error::BufferTooShort {
628 need: 3 + n,
629 have: body.len(),
630 what: "ViewedService.component_tags",
631 });
632 }
633 Ok(Self {
634 service_id,
635 component_tags: body[3..3 + n].to_vec(),
636 })
637 }
638}
639impl Serialize for ViewedService {
640 type Error = Error;
641 fn serialized_len(&self) -> usize {
642 3 + self.component_tags.len()
643 }
644 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
645 let total = self.serialized_len();
646 if buf.len() < total {
647 return Err(Error::OutputBufferTooSmall {
648 need: total,
649 have: buf.len(),
650 });
651 }
652 if self.component_tags.len() > u8::MAX as usize {
653 return Err(Error::InvalidObject {
654 what: "ViewedService",
655 reason: "more than 255 component tags",
656 });
657 }
658 buf[0..2].copy_from_slice(&self.service_id.to_be_bytes());
659 buf[2] = self.component_tags.len() as u8;
660 buf[3..3 + self.component_tags.len()].copy_from_slice(&self.component_tags);
661 Ok(total)
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Eq)]
667#[cfg_attr(feature = "serde", derive(serde::Serialize))]
668#[non_exhaustive]
669pub enum ActivationState {
670 StandbyActive,
672 On,
674 Reserved(u8),
676}
677
678impl ActivationState {
679 #[must_use]
681 pub fn from_u8(v: u8) -> Self {
682 match v {
683 1 => Self::StandbyActive,
684 2 => Self::On,
685 other => Self::Reserved(other),
686 }
687 }
688 #[must_use]
690 pub const fn to_u8(self) -> u8 {
691 match self {
692 Self::StandbyActive => 1,
693 Self::On => 2,
694 Self::Reserved(v) => v & 0x07,
695 }
696 }
697 #[must_use]
699 pub fn name(&self) -> &'static str {
700 match self {
701 Self::StandbyActive => "Standby-active",
702 Self::On => "On",
703 Self::Reserved(_) => "reserved",
704 }
705 }
706}
707broadcast_common::impl_spec_display!(ActivationState, Reserved);
708
709#[derive(Debug, Clone, Copy, PartialEq, Eq)]
711#[cfg_attr(feature = "serde", derive(serde::Serialize))]
712pub struct ActivationStatus {
713 pub event_activated: bool,
715 pub activation_state: ActivationState,
719 pub reserved: u8,
722}
723
724impl ActivationStatus {
725 const EVENT_ACTIVATED_BIT: u8 = 0x08;
726 const ACTIVATION_STATE_MASK: u8 = 0x07;
727 const RESERVED_SHIFT: u32 = 4;
729 const RESERVED_MASK: u8 = 0x0F;
730}
731
732impl<'a> Parse<'a> for ActivationStatus {
733 type Error = Error;
734 fn parse(body: &'a [u8]) -> Result<Self> {
735 if body.is_empty() {
736 return Err(Error::BufferTooShort {
737 need: 1,
738 have: 0,
739 what: "ActivationStatus",
740 });
741 }
742 Ok(Self {
743 event_activated: body[0] & Self::EVENT_ACTIVATED_BIT != 0,
744 activation_state: ActivationState::from_u8(body[0] & Self::ACTIVATION_STATE_MASK),
745 reserved: body[0] >> Self::RESERVED_SHIFT,
746 })
747 }
748}
749impl Serialize for ActivationStatus {
750 type Error = Error;
751 fn serialized_len(&self) -> usize {
752 1
753 }
754 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
755 if buf.is_empty() {
756 return Err(Error::OutputBufferTooSmall { need: 1, have: 0 });
757 }
758 let mut b = 0u8;
759 if self.event_activated {
760 b |= Self::EVENT_ACTIVATED_BIT;
761 }
762 b |= self.activation_state.to_u8() & Self::ACTIVATION_STATE_MASK;
763 b |= (self.reserved & Self::RESERVED_MASK) << Self::RESERVED_SHIFT;
764 buf[0] = b;
765 Ok(1)
766 }
767}
768
769crate::dispatch::declare_resource_apdus! {
770 #[derive(Debug, Clone, PartialEq, Eq)]
772 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
773 #[non_exhaustive]
774 pub enum StatusQueryApdu<'a> ("status_query") {
775 StatusQueryReq(StatusQueryReq) = tag::STATUS_QUERY_REQ,
777 TrapReq(TrapReq) = tag::TRAP_REQ,
779 GetNextItemReq(GetNextItemReq) = tag::GET_NEXT_ITEM_REQ,
781 GetNextItemAck(GetNextItemAck) = tag::GET_NEXT_ITEM_ACK,
783 StatusAck(StatusAck<'a>) = tag::STATUS_ACK,
785 }
786}
787
788#[cfg(test)]
789mod tests {
790 use super::*;
791
792 #[test]
793 fn status_query_req_round_trips_and_bites() {
794 let req = StatusQueryReq {
795 status_item: StatusItem::PortProfile,
796 };
797 let bytes = req.to_bytes();
798 assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02]);
800 assert_eq!(StatusQueryReq::parse(&bytes).unwrap(), req);
801 let other = StatusQueryReq {
802 status_item: StatusItem::ViewedService,
803 };
804 assert_ne!(bytes, other.to_bytes());
805 }
806
807 #[test]
808 fn trap_and_getnext_round_trip() {
809 let trap = TrapReq {
810 status_item: StatusItem::ActivationStatus,
811 };
812 assert_eq!(trap.to_bytes(), [0x9F, 0x80, 0x01, 0x04, 0, 0, 0, 4]);
813 assert_eq!(TrapReq::parse(&trap.to_bytes()).unwrap(), trap);
814
815 let gnr = GetNextItemReq {
816 start_status_item: 0x0000_0007,
817 };
818 assert_eq!(gnr.to_bytes(), [0x9F, 0x80, 0x02, 0x04, 0, 0, 0, 7]);
819 assert_eq!(GetNextItemReq::parse(&gnr.to_bytes()).unwrap(), gnr);
820
821 let gna = GetNextItemAck {
822 next_status_item: 0x0000_0002,
823 };
824 assert_eq!(gna.to_bytes(), [0x9F, 0x80, 0x03, 0x04, 0, 0, 0, 2]);
825 assert_eq!(GetNextItemAck::parse(&gna.to_bytes()).unwrap(), gna);
826 }
827
828 #[test]
829 fn status_item_reserved_round_trips() {
830 let req = StatusQueryReq {
831 status_item: StatusItem::from_u32(0),
832 };
833 assert_eq!(req.status_item, StatusItem::Reserved(0));
834 assert_eq!(req.status_item.name(), "reserved");
835 assert_eq!(StatusQueryReq::parse(&req.to_bytes()).unwrap(), req);
836 }
837
838 #[test]
839 fn status_ack_with_status_bytes_round_trips_and_bites() {
840 let ack = StatusAck {
841 status_item: StatusItem::ViewedService,
842 status_bytes: &[0x01, 0x23, 0x00],
843 };
844 let bytes = ack.to_bytes();
845 assert_eq!(
847 bytes,
848 [0x9F, 0x80, 0x04, 0x07, 0, 0, 0, 3, 0x01, 0x23, 0x00]
849 );
850 assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
851 let mut other = ack.clone();
852 other.status_bytes = &[0x01, 0x23, 0x01];
853 assert_ne!(bytes, other.to_bytes());
854 }
855
856 #[test]
857 fn status_ack_empty_status_bytes_means_unsupported() {
858 let ack = StatusAck {
859 status_item: StatusItem::PortProfile,
860 status_bytes: &[],
861 };
862 let bytes = ack.to_bytes();
863 assert_eq!(bytes, [0x9F, 0x80, 0x04, 0x04, 0, 0, 0, 2]);
864 assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
865 }
866
867 #[test]
868 fn selection_information_multi_port_round_trips_and_bites() {
869 let si = SelectionInformation {
871 time: &[0x20, 0x06, 0x18, 0x12, 0x00],
872 ports: alloc::vec![
873 InPortDescription {
874 in_port_id: 0x01,
875 in_signal_desc: &[0xAA, 0xBB],
876 outputs: alloc::vec![
877 OutputSignal {
878 out_port_id: 0x00,
879 out_signal_desc: &[0x02],
880 },
881 OutputSignal {
882 out_port_id: 0x01,
883 out_signal_desc: &[0x01],
884 },
885 ],
886 },
887 InPortDescription {
888 in_port_id: 0x18,
889 in_signal_desc: &[],
890 outputs: alloc::vec![],
891 },
892 ],
893 };
894 let bytes = si.to_bytes();
895 assert_eq!(SelectionInformation::parse(&bytes).unwrap(), si);
896 let mut other = si.clone();
898 other.ports[0].outputs[1].out_port_id = 0x02;
899 assert_ne!(bytes, other.to_bytes());
900 assert_eq!(
904 bytes,
905 [
906 0x20, 0x06, 0x18, 0x12, 0x00, 0x01, 0x02, 0xAA, 0xBB, 0x00, 0x06, 0x00, 0x01, 0x02, 0x01, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00, ]
914 );
915 }
916
917 #[test]
918 fn port_profile_multi_port_round_trips_and_bites() {
919 let pp = PortProfile {
920 receiver_identification: b"ACME-TV1",
921 ports: alloc::vec![
922 PortDescription {
923 in_port_id: 0x00,
924 in_port_desc: b"RF0",
925 out_port_id: 0x00,
926 out_port_desc: b"D0",
927 },
928 PortDescription {
929 in_port_id: 0x18,
930 in_port_desc: b"CI",
931 out_port_id: 0x10,
932 out_port_desc: b"SCART",
933 },
934 ],
935 };
936 let bytes = pp.to_bytes();
937 assert_eq!(PortProfile::parse(&bytes).unwrap(), pp);
938 let mut other = pp.clone();
939 other.ports[1].out_port_id = 0x11;
940 assert_ne!(bytes, other.to_bytes());
941 }
942
943 #[test]
944 fn viewed_service_multi_component_round_trips_and_bites() {
945 let vs = ViewedService {
946 service_id: 0x1234,
947 component_tags: alloc::vec![0x10, 0x20, 0x30],
948 };
949 let bytes = vs.to_bytes();
950 assert_eq!(bytes, [0x12, 0x34, 0x03, 0x10, 0x20, 0x30]);
951 assert_eq!(ViewedService::parse(&bytes).unwrap(), vs);
952 let mut other = vs.clone();
953 other.component_tags[2] = 0x31;
954 assert_ne!(bytes, other.to_bytes());
955 }
956
957 #[test]
958 fn activation_status_packs_bits() {
959 let a = ActivationStatus {
960 event_activated: true,
961 activation_state: ActivationState::On,
962 reserved: 0,
963 };
964 let bytes = a.to_bytes();
965 assert_eq!(bytes, [0x0A]);
967 assert_eq!(ActivationStatus::parse(&bytes).unwrap(), a);
968 let b = ActivationStatus {
970 event_activated: false,
971 activation_state: ActivationState::StandbyActive,
972 reserved: 0,
973 };
974 assert_eq!(b.to_bytes(), [0x01]);
975 assert_eq!(ActivationStatus::parse(&[0x01]).unwrap(), b);
976 for byte in 0x00u8..=0xFF {
979 let parsed = ActivationStatus::parse(&[byte]).unwrap();
980 assert_eq!(parsed.to_bytes(), [byte], "byte {byte:#04X}");
981 }
982 }
983
984 #[test]
985 fn dispatch_routes_each_tag() {
986 let q = StatusQueryReq {
987 status_item: StatusItem::SelectionInformation,
988 }
989 .to_bytes();
990 assert!(matches!(
991 StatusQueryApdu::parse(&q).unwrap(),
992 StatusQueryApdu::StatusQueryReq(_)
993 ));
994 let ack = StatusAck {
995 status_item: StatusItem::ActivationStatus,
996 status_bytes: &[0x0A],
997 }
998 .to_bytes();
999 let parsed = StatusQueryApdu::parse(&ack).unwrap();
1000 assert!(matches!(parsed, StatusQueryApdu::StatusAck(_)));
1001 assert_eq!(parsed.to_bytes(), ack);
1002 }
1003}