1use crate::error::{Error, Result};
23use crate::objects;
24use alloc::vec::Vec;
25use broadcast_common::len::{need_in, need_out};
26use broadcast_common::{Parse, Serialize};
27
28pub mod tag {
30 use crate::tag::ApduTag;
31 pub const STATUS_QUERY_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
33 pub const TRAP_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
35 pub const GET_NEXT_ITEM_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
37 pub const GET_NEXT_ITEM_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
39 pub const STATUS_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
41}
42
43const STATUS_ITEM_LEN: usize = 4;
45
46fn read_u32(b: &[u8]) -> u32 {
47 u32::from_be_bytes([b[0], b[1], b[2], b[3]])
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52#[cfg_attr(feature = "serde", derive(serde::Serialize))]
53#[non_exhaustive]
54pub enum StatusItem {
55 SelectionInformation,
57 PortProfile,
59 ViewedService,
61 ActivationStatus,
63 Reserved(u32),
65}
66
67impl StatusItem {
68 #[must_use]
70 pub fn from_u32(v: u32) -> Self {
71 match v {
72 1 => Self::SelectionInformation,
73 2 => Self::PortProfile,
74 3 => Self::ViewedService,
75 4 => Self::ActivationStatus,
76 other => Self::Reserved(other),
77 }
78 }
79 #[must_use]
81 pub const fn to_u32(self) -> u32 {
82 match self {
83 Self::SelectionInformation => 1,
84 Self::PortProfile => 2,
85 Self::ViewedService => 3,
86 Self::ActivationStatus => 4,
87 Self::Reserved(v) => v,
88 }
89 }
90 #[must_use]
92 pub fn name(&self) -> &'static str {
93 match self {
94 Self::SelectionInformation => "Selection Information",
95 Self::PortProfile => "Port Profile",
96 Self::ViewedService => "Viewed Service",
97 Self::ActivationStatus => "Activation Status",
98 Self::Reserved(_) => "reserved",
99 }
100 }
101}
102broadcast_common::impl_spec_display!(StatusItem, Reserved);
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
108#[cfg_attr(feature = "serde", derive(serde::Serialize))]
109pub struct StatusQueryReq {
110 #[cfg_attr(feature = "serde", serde(skip))]
114 pub length_form: crate::length::LengthForm,
115
116 pub status_item: StatusItem,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
122#[cfg_attr(feature = "serde", derive(serde::Serialize))]
123pub struct TrapReq {
124 #[cfg_attr(feature = "serde", serde(skip))]
128 pub length_form: crate::length::LengthForm,
129
130 pub status_item: StatusItem,
132}
133
134#[derive(Debug, Clone, Copy, PartialEq, Eq)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize))]
137pub struct GetNextItemReq {
138 #[cfg_attr(feature = "serde", serde(skip))]
142 pub length_form: crate::length::LengthForm,
143
144 pub start_status_item: u32,
147}
148
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
151#[cfg_attr(feature = "serde", derive(serde::Serialize))]
152pub struct GetNextItemAck {
153 #[cfg_attr(feature = "serde", serde(skip))]
157 pub length_form: crate::length::LengthForm,
158
159 pub next_status_item: u32,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq)]
168#[cfg_attr(feature = "serde", derive(serde::Serialize))]
169pub struct StatusAck<'a> {
170 #[cfg_attr(feature = "serde", serde(skip))]
174 pub length_form: crate::length::LengthForm,
175
176 pub status_item: StatusItem,
178 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
180 pub status_bytes: &'a [u8],
181}
182
183macro_rules! status_item_object {
184 ($ty:ident, $tag:expr, $what:literal, $field:ident, $decode:expr, $encode:expr) => {
185 impl<'a> Parse<'a> for $ty {
186 type Error = Error;
187 fn parse(bytes: &'a [u8]) -> Result<Self> {
188 let (length_form, body) = objects::parse_apdu_header(bytes, $tag, $what)?;
189 need_in(body, STATUS_ITEM_LEN, $what)?;
190 objects::reject_trailing_body(body, STATUS_ITEM_LEN, $what)?;
191 let raw = read_u32(body);
192 Ok(Self {
193 length_form,
194 $field: $decode(raw),
195 })
196 }
197 }
198 impl Serialize for $ty {
199 type Error = Error;
200 fn serialized_len(&self) -> usize {
201 objects::apdu_len(STATUS_ITEM_LEN, self.length_form)
202 }
203 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
204 let pos = objects::write_apdu_header($tag, STATUS_ITEM_LEN, self.length_form, buf)?;
205 let raw: u32 = $encode(self.$field);
206 buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&raw.to_be_bytes());
207 Ok(pos + STATUS_ITEM_LEN)
208 }
209 }
210 };
211}
212
213status_item_object!(
214 StatusQueryReq,
215 tag::STATUS_QUERY_REQ,
216 "StatusQueryReq",
217 status_item,
218 StatusItem::from_u32,
219 StatusItem::to_u32
220);
221status_item_object!(
222 TrapReq,
223 tag::TRAP_REQ,
224 "TrapReq",
225 status_item,
226 StatusItem::from_u32,
227 StatusItem::to_u32
228);
229status_item_object!(
230 GetNextItemReq,
231 tag::GET_NEXT_ITEM_REQ,
232 "GetNextItemReq",
233 start_status_item,
234 core::convert::identity,
235 core::convert::identity
236);
237status_item_object!(
238 GetNextItemAck,
239 tag::GET_NEXT_ITEM_ACK,
240 "GetNextItemAck",
241 next_status_item,
242 core::convert::identity,
243 core::convert::identity
244);
245
246impl<'a> Parse<'a> for StatusAck<'a> {
247 type Error = Error;
248 fn parse(bytes: &'a [u8]) -> Result<Self> {
249 let (length_form, body) = objects::parse_apdu_header(bytes, tag::STATUS_ACK, "StatusAck")?;
250 need_in(body, STATUS_ITEM_LEN, "StatusAck")?;
251 Ok(Self {
252 length_form,
253 status_item: StatusItem::from_u32(read_u32(body)),
254 status_bytes: &body[STATUS_ITEM_LEN..],
255 })
256 }
257}
258impl Serialize for StatusAck<'_> {
259 type Error = Error;
260 fn serialized_len(&self) -> usize {
261 objects::apdu_len(STATUS_ITEM_LEN + self.status_bytes.len(), self.length_form)
262 }
263 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
264 let body_len = STATUS_ITEM_LEN + self.status_bytes.len();
265 let mut pos = objects::write_apdu_header(tag::STATUS_ACK, body_len, self.length_form, buf)?;
266 buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&self.status_item.to_u32().to_be_bytes());
267 pos += STATUS_ITEM_LEN;
268 buf[pos..pos + self.status_bytes.len()].copy_from_slice(self.status_bytes);
269 Ok(pos + self.status_bytes.len())
270 }
271}
272
273pub const SELECTION_TIME_LEN: usize = 5;
283
284#[derive(Debug, Clone, PartialEq, Eq, Default)]
286#[cfg_attr(feature = "serde", derive(serde::Serialize))]
287pub struct OutputSignal<'a> {
288 pub out_port_id: u8,
290 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
293 pub out_signal_desc: &'a [u8],
294}
295
296#[derive(Debug, Clone, PartialEq, Eq, Default)]
299#[cfg_attr(feature = "serde", derive(serde::Serialize))]
300pub struct InPortDescription<'a> {
301 pub in_port_id: u8,
303 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
306 pub in_signal_desc: &'a [u8],
307 pub outputs: Vec<OutputSignal<'a>>,
309}
310
311#[derive(Debug, Clone, PartialEq, Eq, Default)]
314#[cfg_attr(feature = "serde", derive(serde::Serialize))]
315pub struct SelectionInformation<'a> {
316 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
318 pub time: &'a [u8],
319 pub ports: Vec<InPortDescription<'a>>,
321}
322
323impl<'a> Parse<'a> for SelectionInformation<'a> {
324 type Error = Error;
325 fn parse(body: &'a [u8]) -> Result<Self> {
326 need_in(body, SELECTION_TIME_LEN, "SelectionInformation.time")?;
327 let time = &body[..SELECTION_TIME_LEN];
328 let mut rest = &body[SELECTION_TIME_LEN..];
329 let mut ports = Vec::new();
330 while !rest.is_empty() {
331 need_in(rest, 2, "SelectionInformation.in_port")?;
333 let in_port_id = rest[0];
334 let in_len = rest[1] as usize;
335 let after_in = 2 + in_len;
336 need_in(rest, after_in + 2, "SelectionInformation.in_signal_desc")?;
337 let in_signal_desc = &rest[2..after_in];
338 let length_outputs =
340 (((rest[after_in] & 0x0F) as usize) << 8) | rest[after_in + 1] as usize;
341 let outputs_start = after_in + 2;
342 let outputs_end = outputs_start + length_outputs;
343 need_in(rest, outputs_end, "SelectionInformation.outputs")?;
344 let mut out_rest = &rest[outputs_start..outputs_end];
345 let mut outputs = Vec::new();
346 while !out_rest.is_empty() {
347 need_in(out_rest, 2, "SelectionInformation.out_port")?;
349 let out_port_id = out_rest[0];
350 let out_len = out_rest[1] as usize;
351 let out_end = 2 + out_len;
352 need_in(out_rest, out_end, "SelectionInformation.out_signal_desc")?;
353 outputs.push(OutputSignal {
354 out_port_id,
355 out_signal_desc: &out_rest[2..out_end],
356 });
357 out_rest = &out_rest[out_end..];
358 }
359 ports.push(InPortDescription {
360 in_port_id,
361 in_signal_desc,
362 outputs,
363 });
364 rest = &rest[outputs_end..];
365 }
366 Ok(Self { time, ports })
367 }
368}
369impl Serialize for SelectionInformation<'_> {
370 type Error = Error;
371 fn serialized_len(&self) -> usize {
372 let mut n = SELECTION_TIME_LEN;
373 for p in &self.ports {
374 n += 2 + p.in_signal_desc.len() + 2;
375 for o in &p.outputs {
376 n += 2 + o.out_signal_desc.len();
377 }
378 }
379 n
380 }
381 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
382 let total = self.serialized_len();
383 need_out(buf, total)?;
384 if self.time.len() != SELECTION_TIME_LEN {
385 return Err(Error::InvalidObject {
386 what: "SelectionInformation",
387 reason: "time must be exactly 5 bytes",
388 });
389 }
390 let mut pos = 0;
391 buf[pos..pos + SELECTION_TIME_LEN].copy_from_slice(self.time);
392 pos += SELECTION_TIME_LEN;
393 for p in &self.ports {
394 if p.in_signal_desc.len() > u8::MAX as usize {
395 return Err(Error::InvalidObject {
396 what: "SelectionInformation",
397 reason: "in_signal_desc longer than 255 bytes",
398 });
399 }
400 buf[pos] = p.in_port_id;
401 buf[pos + 1] = p.in_signal_desc.len() as u8;
402 pos += 2;
403 buf[pos..pos + p.in_signal_desc.len()].copy_from_slice(p.in_signal_desc);
404 pos += p.in_signal_desc.len();
405 let mut length_outputs = 0usize;
407 for o in &p.outputs {
408 length_outputs += 2 + o.out_signal_desc.len();
409 }
410 if length_outputs > 0x0FFF {
411 return Err(Error::InvalidObject {
412 what: "SelectionInformation",
413 reason: "length_outputs exceeds 12-bit maximum",
414 });
415 }
416 buf[pos] = ((length_outputs >> 8) & 0x0F) as u8;
418 buf[pos + 1] = (length_outputs & 0xFF) as u8;
419 pos += 2;
420 for o in &p.outputs {
421 if o.out_signal_desc.len() > u8::MAX as usize {
422 return Err(Error::InvalidObject {
423 what: "SelectionInformation",
424 reason: "out_signal_desc longer than 255 bytes",
425 });
426 }
427 buf[pos] = o.out_port_id;
428 buf[pos + 1] = o.out_signal_desc.len() as u8;
429 pos += 2;
430 buf[pos..pos + o.out_signal_desc.len()].copy_from_slice(o.out_signal_desc);
431 pos += o.out_signal_desc.len();
432 }
433 }
434 Ok(pos)
435 }
436}
437
438#[derive(Debug, Clone, PartialEq, Eq, Default)]
440#[cfg_attr(feature = "serde", derive(serde::Serialize))]
441pub struct PortDescription<'a> {
442 pub in_port_id: u8,
444 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
446 pub in_port_desc: &'a [u8],
447 pub out_port_id: u8,
449 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
451 pub out_port_desc: &'a [u8],
452}
453
454#[derive(Debug, Clone, PartialEq, Eq, Default)]
457#[cfg_attr(feature = "serde", derive(serde::Serialize))]
458pub struct PortProfile<'a> {
459 #[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
462 pub receiver_identification: &'a [u8],
463 pub ports: Vec<PortDescription<'a>>,
465}
466
467impl<'a> Parse<'a> for PortProfile<'a> {
468 type Error = Error;
469 fn parse(body: &'a [u8]) -> Result<Self> {
470 need_in(body, 1, "PortProfile.receiver_identification_length")?;
471 let id_len = body[0] as usize;
472 need_in(body, 1 + id_len, "PortProfile.receiver_identification")?;
473 let receiver_identification = &body[1..1 + id_len];
474 let mut rest = &body[1 + id_len..];
475 let mut ports = Vec::new();
476 while !rest.is_empty() {
477 need_in(rest, 2, "PortProfile.in_port")?;
479 let in_port_id = rest[0];
480 let in_len = rest[1] as usize;
481 let after_in = 2 + in_len;
482 need_in(rest, after_in + 2, "PortProfile.out_port")?;
484 let in_port_desc = &rest[2..after_in];
485 let out_port_id = rest[after_in];
486 let out_len = rest[after_in + 1] as usize;
487 let after_out = after_in + 2 + out_len;
488 need_in(rest, after_out, "PortProfile.out_port_desc")?;
489 ports.push(PortDescription {
490 in_port_id,
491 in_port_desc,
492 out_port_id,
493 out_port_desc: &rest[after_in + 2..after_out],
494 });
495 rest = &rest[after_out..];
496 }
497 Ok(Self {
498 receiver_identification,
499 ports,
500 })
501 }
502}
503impl Serialize for PortProfile<'_> {
504 type Error = Error;
505 fn serialized_len(&self) -> usize {
506 let mut n = 1 + self.receiver_identification.len();
507 for p in &self.ports {
508 n += 2 + p.in_port_desc.len() + 2 + p.out_port_desc.len();
509 }
510 n
511 }
512 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
513 let total = self.serialized_len();
514 need_out(buf, total)?;
515 if self.receiver_identification.len() > u8::MAX as usize {
516 return Err(Error::InvalidObject {
517 what: "PortProfile",
518 reason: "receiver_identification longer than 255 bytes",
519 });
520 }
521 let mut pos = 0;
522 buf[pos] = self.receiver_identification.len() as u8;
523 pos += 1;
524 buf[pos..pos + self.receiver_identification.len()]
525 .copy_from_slice(self.receiver_identification);
526 pos += self.receiver_identification.len();
527 for p in &self.ports {
528 if p.in_port_desc.len() > u8::MAX as usize || p.out_port_desc.len() > u8::MAX as usize {
529 return Err(Error::InvalidObject {
530 what: "PortProfile",
531 reason: "port description longer than 255 bytes",
532 });
533 }
534 buf[pos] = p.in_port_id;
535 buf[pos + 1] = p.in_port_desc.len() as u8;
536 pos += 2;
537 buf[pos..pos + p.in_port_desc.len()].copy_from_slice(p.in_port_desc);
538 pos += p.in_port_desc.len();
539 buf[pos] = p.out_port_id;
540 buf[pos + 1] = p.out_port_desc.len() as u8;
541 pos += 2;
542 buf[pos..pos + p.out_port_desc.len()].copy_from_slice(p.out_port_desc);
543 pos += p.out_port_desc.len();
544 }
545 Ok(pos)
546 }
547}
548
549#[derive(Debug, Clone, PartialEq, Eq, Default)]
551#[cfg_attr(feature = "serde", derive(serde::Serialize))]
552pub struct ViewedService {
553 pub service_id: u16,
555 pub component_tags: Vec<u8>,
557}
558
559impl<'a> Parse<'a> for ViewedService {
560 type Error = Error;
561 fn parse(body: &'a [u8]) -> Result<Self> {
562 need_in(body, 3, "ViewedService")?;
564 let service_id = u16::from_be_bytes([body[0], body[1]]);
565 let n = body[2] as usize;
566 need_in(body, 3 + n, "ViewedService.component_tags")?;
567 Ok(Self {
568 service_id,
569 component_tags: body[3..3 + n].to_vec(),
570 })
571 }
572}
573impl Serialize for ViewedService {
574 type Error = Error;
575 fn serialized_len(&self) -> usize {
576 3 + self.component_tags.len()
577 }
578 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
579 let total = self.serialized_len();
580 need_out(buf, total)?;
581 if self.component_tags.len() > u8::MAX as usize {
582 return Err(Error::InvalidObject {
583 what: "ViewedService",
584 reason: "more than 255 component tags",
585 });
586 }
587 buf[0..2].copy_from_slice(&self.service_id.to_be_bytes());
588 buf[2] = self.component_tags.len() as u8;
589 buf[3..3 + self.component_tags.len()].copy_from_slice(&self.component_tags);
590 Ok(total)
591 }
592}
593
594#[derive(Debug, Clone, Copy, PartialEq, Eq)]
596#[cfg_attr(feature = "serde", derive(serde::Serialize))]
597#[non_exhaustive]
598pub enum ActivationState {
599 StandbyActive,
601 On,
603 Reserved(u8),
605}
606
607impl ActivationState {
608 #[must_use]
610 pub fn from_u8(v: u8) -> Self {
611 match v {
612 1 => Self::StandbyActive,
613 2 => Self::On,
614 other => Self::Reserved(other),
615 }
616 }
617 #[must_use]
619 pub const fn to_u8(self) -> u8 {
620 match self {
621 Self::StandbyActive => 1,
622 Self::On => 2,
623 Self::Reserved(v) => v & 0x07,
624 }
625 }
626 #[must_use]
628 pub fn name(&self) -> &'static str {
629 match self {
630 Self::StandbyActive => "Standby-active",
631 Self::On => "On",
632 Self::Reserved(_) => "reserved",
633 }
634 }
635}
636broadcast_common::impl_spec_display!(ActivationState, Reserved);
637
638#[derive(Debug, Clone, Copy, PartialEq, Eq)]
640#[cfg_attr(feature = "serde", derive(serde::Serialize))]
641pub struct ActivationStatus {
642 pub event_activated: bool,
644 pub activation_state: ActivationState,
648 pub reserved: u8,
651}
652
653impl ActivationStatus {
654 const EVENT_ACTIVATED_BIT: u8 = 0x08;
655 const ACTIVATION_STATE_MASK: u8 = 0x07;
656 const RESERVED_SHIFT: u32 = 4;
658 const RESERVED_MASK: u8 = 0x0F;
659}
660
661impl<'a> Parse<'a> for ActivationStatus {
662 type Error = Error;
663 fn parse(body: &'a [u8]) -> Result<Self> {
664 need_in(body, 1, "ActivationStatus")?;
665 Ok(Self {
666 event_activated: body[0] & Self::EVENT_ACTIVATED_BIT != 0,
667 activation_state: ActivationState::from_u8(body[0] & Self::ACTIVATION_STATE_MASK),
668 reserved: body[0] >> Self::RESERVED_SHIFT,
669 })
670 }
671}
672impl Serialize for ActivationStatus {
673 type Error = Error;
674 fn serialized_len(&self) -> usize {
675 1
676 }
677 fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
678 if buf.is_empty() {
679 return Err(Error::OutputBufferTooSmall(
680 broadcast_common::error::OutputBufferTooSmall { need: 1, have: 0 },
681 ));
682 }
683 let mut b = 0u8;
684 if self.event_activated {
685 b |= Self::EVENT_ACTIVATED_BIT;
686 }
687 b |= self.activation_state.to_u8() & Self::ACTIVATION_STATE_MASK;
688 b |= (self.reserved & Self::RESERVED_MASK) << Self::RESERVED_SHIFT;
689 buf[0] = b;
690 Ok(1)
691 }
692}
693
694crate::dispatch::declare_resource_apdus! {
695 #[derive(Debug, Clone, PartialEq, Eq)]
697 #[cfg_attr(feature = "serde", derive(serde::Serialize))]
698 #[non_exhaustive]
699 pub enum StatusQueryApdu<'a> ("status_query") {
700 StatusQueryReq(StatusQueryReq) = tag::STATUS_QUERY_REQ,
702 TrapReq(TrapReq) = tag::TRAP_REQ,
704 GetNextItemReq(GetNextItemReq) = tag::GET_NEXT_ITEM_REQ,
706 GetNextItemAck(GetNextItemAck) = tag::GET_NEXT_ITEM_ACK,
708 StatusAck(StatusAck<'a>) = tag::STATUS_ACK,
710 }
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716
717 #[test]
718 fn status_query_req_round_trips_and_bites() {
719 let req = StatusQueryReq {
720 length_form: crate::length::LengthForm::Minimal,
721 status_item: StatusItem::PortProfile,
722 };
723 let bytes = req.try_to_bytes().unwrap();
724 assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02]);
726 assert_eq!(StatusQueryReq::parse(&bytes).unwrap(), req);
727 let other = StatusQueryReq {
728 length_form: crate::length::LengthForm::Minimal,
729 status_item: StatusItem::ViewedService,
730 };
731 assert_ne!(bytes, other.try_to_bytes().unwrap());
732 }
733
734 #[test]
735 fn trap_and_getnext_round_trip() {
736 let trap = TrapReq {
737 length_form: crate::length::LengthForm::Minimal,
738 status_item: StatusItem::ActivationStatus,
739 };
740 assert_eq!(
741 trap.try_to_bytes().unwrap(),
742 [0x9F, 0x80, 0x01, 0x04, 0, 0, 0, 4]
743 );
744 assert_eq!(TrapReq::parse(&trap.try_to_bytes().unwrap()).unwrap(), trap);
745
746 let gnr = GetNextItemReq {
747 length_form: crate::length::LengthForm::Minimal,
748 start_status_item: 0x0000_0007,
749 };
750 assert_eq!(
751 gnr.try_to_bytes().unwrap(),
752 [0x9F, 0x80, 0x02, 0x04, 0, 0, 0, 7]
753 );
754 assert_eq!(
755 GetNextItemReq::parse(&gnr.try_to_bytes().unwrap()).unwrap(),
756 gnr
757 );
758
759 let gna = GetNextItemAck {
760 length_form: crate::length::LengthForm::Minimal,
761 next_status_item: 0x0000_0002,
762 };
763 assert_eq!(
764 gna.try_to_bytes().unwrap(),
765 [0x9F, 0x80, 0x03, 0x04, 0, 0, 0, 2]
766 );
767 assert_eq!(
768 GetNextItemAck::parse(&gna.try_to_bytes().unwrap()).unwrap(),
769 gna
770 );
771 }
772
773 #[test]
774 fn status_item_reserved_round_trips() {
775 let req = StatusQueryReq {
776 length_form: crate::length::LengthForm::Minimal,
777 status_item: StatusItem::from_u32(0),
778 };
779 assert_eq!(req.status_item, StatusItem::Reserved(0));
780 assert_eq!(req.status_item.name(), "reserved");
781 assert_eq!(
782 StatusQueryReq::parse(&req.try_to_bytes().unwrap()).unwrap(),
783 req
784 );
785 }
786
787 #[test]
788 fn status_ack_with_status_bytes_round_trips_and_bites() {
789 let ack = StatusAck {
790 length_form: crate::length::LengthForm::Minimal,
791 status_item: StatusItem::ViewedService,
792 status_bytes: &[0x01, 0x23, 0x00],
793 };
794 let bytes = ack.try_to_bytes().unwrap();
795 assert_eq!(
797 bytes,
798 [0x9F, 0x80, 0x04, 0x07, 0, 0, 0, 3, 0x01, 0x23, 0x00]
799 );
800 assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
801 let mut other = ack.clone();
802 other.status_bytes = &[0x01, 0x23, 0x01];
803 assert_ne!(bytes, other.try_to_bytes().unwrap());
804 }
805
806 #[test]
807 fn status_ack_empty_status_bytes_means_unsupported() {
808 let ack = StatusAck {
809 length_form: crate::length::LengthForm::Minimal,
810 status_item: StatusItem::PortProfile,
811 status_bytes: &[],
812 };
813 let bytes = ack.try_to_bytes().unwrap();
814 assert_eq!(bytes, [0x9F, 0x80, 0x04, 0x04, 0, 0, 0, 2]);
815 assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
816 }
817
818 #[test]
819 fn selection_information_multi_port_round_trips_and_bites() {
820 let si = SelectionInformation {
822 time: &[0x20, 0x06, 0x18, 0x12, 0x00],
823 ports: alloc::vec![
824 InPortDescription {
825 in_port_id: 0x01,
826 in_signal_desc: &[0xAA, 0xBB],
827 outputs: alloc::vec![
828 OutputSignal {
829 out_port_id: 0x00,
830 out_signal_desc: &[0x02],
831 },
832 OutputSignal {
833 out_port_id: 0x01,
834 out_signal_desc: &[0x01],
835 },
836 ],
837 },
838 InPortDescription {
839 in_port_id: 0x18,
840 in_signal_desc: &[],
841 outputs: alloc::vec![],
842 },
843 ],
844 };
845 let bytes = si.try_to_bytes().unwrap();
846 assert_eq!(SelectionInformation::parse(&bytes).unwrap(), si);
847 let mut other = si.clone();
849 other.ports[0].outputs[1].out_port_id = 0x02;
850 assert_ne!(bytes, other.try_to_bytes().unwrap());
851 assert_eq!(
855 bytes,
856 [
857 0x20, 0x06, 0x18, 0x12, 0x00, 0x01, 0x02, 0xAA, 0xBB, 0x00, 0x06, 0x00, 0x01, 0x02, 0x01, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00, ]
865 );
866 }
867
868 #[test]
869 fn port_profile_multi_port_round_trips_and_bites() {
870 let pp = PortProfile {
871 receiver_identification: b"ACME-TV1",
872 ports: alloc::vec![
873 PortDescription {
874 in_port_id: 0x00,
875 in_port_desc: b"RF0",
876 out_port_id: 0x00,
877 out_port_desc: b"D0",
878 },
879 PortDescription {
880 in_port_id: 0x18,
881 in_port_desc: b"CI",
882 out_port_id: 0x10,
883 out_port_desc: b"SCART",
884 },
885 ],
886 };
887 let bytes = pp.try_to_bytes().unwrap();
888 assert_eq!(PortProfile::parse(&bytes).unwrap(), pp);
889 let mut other = pp.clone();
890 other.ports[1].out_port_id = 0x11;
891 assert_ne!(bytes, other.try_to_bytes().unwrap());
892 }
893
894 #[test]
895 fn viewed_service_multi_component_round_trips_and_bites() {
896 let vs = ViewedService {
897 service_id: 0x1234,
898 component_tags: alloc::vec![0x10, 0x20, 0x30],
899 };
900 let bytes = vs.try_to_bytes().unwrap();
901 assert_eq!(bytes, [0x12, 0x34, 0x03, 0x10, 0x20, 0x30]);
902 assert_eq!(ViewedService::parse(&bytes).unwrap(), vs);
903 let mut other = vs.clone();
904 other.component_tags[2] = 0x31;
905 assert_ne!(bytes, other.try_to_bytes().unwrap());
906 }
907
908 #[test]
909 fn activation_status_packs_bits() {
910 let a = ActivationStatus {
911 event_activated: true,
912 activation_state: ActivationState::On,
913 reserved: 0,
914 };
915 let bytes = a.try_to_bytes().unwrap();
916 assert_eq!(bytes, [0x0A]);
918 assert_eq!(ActivationStatus::parse(&bytes).unwrap(), a);
919 let b = ActivationStatus {
921 event_activated: false,
922 activation_state: ActivationState::StandbyActive,
923 reserved: 0,
924 };
925 assert_eq!(b.try_to_bytes().unwrap(), [0x01]);
926 assert_eq!(ActivationStatus::parse(&[0x01]).unwrap(), b);
927 for byte in 0x00u8..=0xFF {
930 let parsed = ActivationStatus::parse(&[byte]).unwrap();
931 assert_eq!(parsed.try_to_bytes().unwrap(), [byte], "byte {byte:#04X}");
932 }
933 }
934
935 #[test]
936 fn dispatch_routes_each_tag() {
937 let q = StatusQueryReq {
938 length_form: crate::length::LengthForm::Minimal,
939 status_item: StatusItem::SelectionInformation,
940 }
941 .try_to_bytes()
942 .unwrap();
943 assert!(matches!(
944 StatusQueryApdu::parse(&q).unwrap(),
945 StatusQueryApdu::StatusQueryReq(_)
946 ));
947 let ack = StatusAck {
948 length_form: crate::length::LengthForm::Minimal,
949 status_item: StatusItem::ActivationStatus,
950 status_bytes: &[0x0A],
951 }
952 .try_to_bytes()
953 .unwrap();
954 let parsed = StatusQueryApdu::parse(&ack).unwrap();
955 assert!(matches!(parsed, StatusQueryApdu::StatusAck(_)));
956 assert_eq!(parsed.try_to_bytes().unwrap(), ack);
957 }
958}