use crate::error::{Error, Result};
use crate::objects;
use alloc::vec::Vec;
use broadcast_common::len::{need_in, need_out};
use broadcast_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const STATUS_QUERY_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x00);
pub const TRAP_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x01);
pub const GET_NEXT_ITEM_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x02);
pub const GET_NEXT_ITEM_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x03);
pub const STATUS_ACK: ApduTag = ApduTag::from_bytes(0x9F, 0x80, 0x04);
}
const STATUS_ITEM_LEN: usize = 4;
fn read_u32(b: &[u8]) -> u32 {
u32::from_be_bytes([b[0], b[1], b[2], b[3]])
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum StatusItem {
SelectionInformation,
PortProfile,
ViewedService,
ActivationStatus,
Reserved(u32),
}
impl StatusItem {
#[must_use]
pub fn from_u32(v: u32) -> Self {
match v {
1 => Self::SelectionInformation,
2 => Self::PortProfile,
3 => Self::ViewedService,
4 => Self::ActivationStatus,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u32(self) -> u32 {
match self {
Self::SelectionInformation => 1,
Self::PortProfile => 2,
Self::ViewedService => 3,
Self::ActivationStatus => 4,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::SelectionInformation => "Selection Information",
Self::PortProfile => "Port Profile",
Self::ViewedService => "Viewed Service",
Self::ActivationStatus => "Activation Status",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(StatusItem, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct StatusQueryReq {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub status_item: StatusItem,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct TrapReq {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub status_item: StatusItem,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct GetNextItemReq {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub start_status_item: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct GetNextItemAck {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub next_status_item: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct StatusAck<'a> {
#[cfg_attr(feature = "serde", serde(skip))]
pub length_form: crate::length::LengthForm,
pub status_item: StatusItem,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub status_bytes: &'a [u8],
}
macro_rules! status_item_object {
($ty:ident, $tag:expr, $what:literal, $field:ident, $decode:expr, $encode:expr) => {
impl<'a> Parse<'a> for $ty {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) = objects::parse_apdu_header(bytes, $tag, $what)?;
need_in(body, STATUS_ITEM_LEN, $what)?;
objects::reject_trailing_body(body, STATUS_ITEM_LEN, $what)?;
let raw = read_u32(body);
Ok(Self {
length_form,
$field: $decode(raw),
})
}
}
impl Serialize for $ty {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(STATUS_ITEM_LEN, self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header($tag, STATUS_ITEM_LEN, self.length_form, buf)?;
let raw: u32 = $encode(self.$field);
buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&raw.to_be_bytes());
Ok(pos + STATUS_ITEM_LEN)
}
}
};
}
status_item_object!(
StatusQueryReq,
tag::STATUS_QUERY_REQ,
"StatusQueryReq",
status_item,
StatusItem::from_u32,
StatusItem::to_u32
);
status_item_object!(
TrapReq,
tag::TRAP_REQ,
"TrapReq",
status_item,
StatusItem::from_u32,
StatusItem::to_u32
);
status_item_object!(
GetNextItemReq,
tag::GET_NEXT_ITEM_REQ,
"GetNextItemReq",
start_status_item,
core::convert::identity,
core::convert::identity
);
status_item_object!(
GetNextItemAck,
tag::GET_NEXT_ITEM_ACK,
"GetNextItemAck",
next_status_item,
core::convert::identity,
core::convert::identity
);
impl<'a> Parse<'a> for StatusAck<'a> {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let (length_form, body) = objects::parse_apdu_header(bytes, tag::STATUS_ACK, "StatusAck")?;
need_in(body, STATUS_ITEM_LEN, "StatusAck")?;
Ok(Self {
length_form,
status_item: StatusItem::from_u32(read_u32(body)),
status_bytes: &body[STATUS_ITEM_LEN..],
})
}
}
impl Serialize for StatusAck<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(STATUS_ITEM_LEN + self.status_bytes.len(), self.length_form)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = STATUS_ITEM_LEN + self.status_bytes.len();
let mut pos = objects::write_apdu_header(tag::STATUS_ACK, body_len, self.length_form, buf)?;
buf[pos..pos + STATUS_ITEM_LEN].copy_from_slice(&self.status_item.to_u32().to_be_bytes());
pos += STATUS_ITEM_LEN;
buf[pos..pos + self.status_bytes.len()].copy_from_slice(self.status_bytes);
Ok(pos + self.status_bytes.len())
}
}
pub const SELECTION_TIME_LEN: usize = 5;
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct OutputSignal<'a> {
pub out_port_id: u8,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub out_signal_desc: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct InPortDescription<'a> {
pub in_port_id: u8,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub in_signal_desc: &'a [u8],
pub outputs: Vec<OutputSignal<'a>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct SelectionInformation<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub time: &'a [u8],
pub ports: Vec<InPortDescription<'a>>,
}
impl<'a> Parse<'a> for SelectionInformation<'a> {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
need_in(body, SELECTION_TIME_LEN, "SelectionInformation.time")?;
let time = &body[..SELECTION_TIME_LEN];
let mut rest = &body[SELECTION_TIME_LEN..];
let mut ports = Vec::new();
while !rest.is_empty() {
need_in(rest, 2, "SelectionInformation.in_port")?;
let in_port_id = rest[0];
let in_len = rest[1] as usize;
let after_in = 2 + in_len;
need_in(rest, after_in + 2, "SelectionInformation.in_signal_desc")?;
let in_signal_desc = &rest[2..after_in];
let length_outputs =
(((rest[after_in] & 0x0F) as usize) << 8) | rest[after_in + 1] as usize;
let outputs_start = after_in + 2;
let outputs_end = outputs_start + length_outputs;
need_in(rest, outputs_end, "SelectionInformation.outputs")?;
let mut out_rest = &rest[outputs_start..outputs_end];
let mut outputs = Vec::new();
while !out_rest.is_empty() {
need_in(out_rest, 2, "SelectionInformation.out_port")?;
let out_port_id = out_rest[0];
let out_len = out_rest[1] as usize;
let out_end = 2 + out_len;
need_in(out_rest, out_end, "SelectionInformation.out_signal_desc")?;
outputs.push(OutputSignal {
out_port_id,
out_signal_desc: &out_rest[2..out_end],
});
out_rest = &out_rest[out_end..];
}
ports.push(InPortDescription {
in_port_id,
in_signal_desc,
outputs,
});
rest = &rest[outputs_end..];
}
Ok(Self { time, ports })
}
}
impl Serialize for SelectionInformation<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut n = SELECTION_TIME_LEN;
for p in &self.ports {
n += 2 + p.in_signal_desc.len() + 2;
for o in &p.outputs {
n += 2 + o.out_signal_desc.len();
}
}
n
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
need_out(buf, total)?;
if self.time.len() != SELECTION_TIME_LEN {
return Err(Error::InvalidObject {
what: "SelectionInformation",
reason: "time must be exactly 5 bytes",
});
}
let mut pos = 0;
buf[pos..pos + SELECTION_TIME_LEN].copy_from_slice(self.time);
pos += SELECTION_TIME_LEN;
for p in &self.ports {
if p.in_signal_desc.len() > u8::MAX as usize {
return Err(Error::InvalidObject {
what: "SelectionInformation",
reason: "in_signal_desc longer than 255 bytes",
});
}
buf[pos] = p.in_port_id;
buf[pos + 1] = p.in_signal_desc.len() as u8;
pos += 2;
buf[pos..pos + p.in_signal_desc.len()].copy_from_slice(p.in_signal_desc);
pos += p.in_signal_desc.len();
let mut length_outputs = 0usize;
for o in &p.outputs {
length_outputs += 2 + o.out_signal_desc.len();
}
if length_outputs > 0x0FFF {
return Err(Error::InvalidObject {
what: "SelectionInformation",
reason: "length_outputs exceeds 12-bit maximum",
});
}
buf[pos] = ((length_outputs >> 8) & 0x0F) as u8;
buf[pos + 1] = (length_outputs & 0xFF) as u8;
pos += 2;
for o in &p.outputs {
if o.out_signal_desc.len() > u8::MAX as usize {
return Err(Error::InvalidObject {
what: "SelectionInformation",
reason: "out_signal_desc longer than 255 bytes",
});
}
buf[pos] = o.out_port_id;
buf[pos + 1] = o.out_signal_desc.len() as u8;
pos += 2;
buf[pos..pos + o.out_signal_desc.len()].copy_from_slice(o.out_signal_desc);
pos += o.out_signal_desc.len();
}
}
Ok(pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PortDescription<'a> {
pub in_port_id: u8,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub in_port_desc: &'a [u8],
pub out_port_id: u8,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub out_port_desc: &'a [u8],
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct PortProfile<'a> {
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub receiver_identification: &'a [u8],
pub ports: Vec<PortDescription<'a>>,
}
impl<'a> Parse<'a> for PortProfile<'a> {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
need_in(body, 1, "PortProfile.receiver_identification_length")?;
let id_len = body[0] as usize;
need_in(body, 1 + id_len, "PortProfile.receiver_identification")?;
let receiver_identification = &body[1..1 + id_len];
let mut rest = &body[1 + id_len..];
let mut ports = Vec::new();
while !rest.is_empty() {
need_in(rest, 2, "PortProfile.in_port")?;
let in_port_id = rest[0];
let in_len = rest[1] as usize;
let after_in = 2 + in_len;
need_in(rest, after_in + 2, "PortProfile.out_port")?;
let in_port_desc = &rest[2..after_in];
let out_port_id = rest[after_in];
let out_len = rest[after_in + 1] as usize;
let after_out = after_in + 2 + out_len;
need_in(rest, after_out, "PortProfile.out_port_desc")?;
ports.push(PortDescription {
in_port_id,
in_port_desc,
out_port_id,
out_port_desc: &rest[after_in + 2..after_out],
});
rest = &rest[after_out..];
}
Ok(Self {
receiver_identification,
ports,
})
}
}
impl Serialize for PortProfile<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
let mut n = 1 + self.receiver_identification.len();
for p in &self.ports {
n += 2 + p.in_port_desc.len() + 2 + p.out_port_desc.len();
}
n
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
need_out(buf, total)?;
if self.receiver_identification.len() > u8::MAX as usize {
return Err(Error::InvalidObject {
what: "PortProfile",
reason: "receiver_identification longer than 255 bytes",
});
}
let mut pos = 0;
buf[pos] = self.receiver_identification.len() as u8;
pos += 1;
buf[pos..pos + self.receiver_identification.len()]
.copy_from_slice(self.receiver_identification);
pos += self.receiver_identification.len();
for p in &self.ports {
if p.in_port_desc.len() > u8::MAX as usize || p.out_port_desc.len() > u8::MAX as usize {
return Err(Error::InvalidObject {
what: "PortProfile",
reason: "port description longer than 255 bytes",
});
}
buf[pos] = p.in_port_id;
buf[pos + 1] = p.in_port_desc.len() as u8;
pos += 2;
buf[pos..pos + p.in_port_desc.len()].copy_from_slice(p.in_port_desc);
pos += p.in_port_desc.len();
buf[pos] = p.out_port_id;
buf[pos + 1] = p.out_port_desc.len() as u8;
pos += 2;
buf[pos..pos + p.out_port_desc.len()].copy_from_slice(p.out_port_desc);
pos += p.out_port_desc.len();
}
Ok(pos)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ViewedService {
pub service_id: u16,
pub component_tags: Vec<u8>,
}
impl<'a> Parse<'a> for ViewedService {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
need_in(body, 3, "ViewedService")?;
let service_id = u16::from_be_bytes([body[0], body[1]]);
let n = body[2] as usize;
need_in(body, 3 + n, "ViewedService.component_tags")?;
Ok(Self {
service_id,
component_tags: body[3..3 + n].to_vec(),
})
}
}
impl Serialize for ViewedService {
type Error = Error;
fn serialized_len(&self) -> usize {
3 + self.component_tags.len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let total = self.serialized_len();
need_out(buf, total)?;
if self.component_tags.len() > u8::MAX as usize {
return Err(Error::InvalidObject {
what: "ViewedService",
reason: "more than 255 component tags",
});
}
buf[0..2].copy_from_slice(&self.service_id.to_be_bytes());
buf[2] = self.component_tags.len() as u8;
buf[3..3 + self.component_tags.len()].copy_from_slice(&self.component_tags);
Ok(total)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ActivationState {
StandbyActive,
On,
Reserved(u8),
}
impl ActivationState {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
1 => Self::StandbyActive,
2 => Self::On,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::StandbyActive => 1,
Self::On => 2,
Self::Reserved(v) => v & 0x07,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::StandbyActive => "Standby-active",
Self::On => "On",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(ActivationState, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct ActivationStatus {
pub event_activated: bool,
pub activation_state: ActivationState,
pub reserved: u8,
}
impl ActivationStatus {
const EVENT_ACTIVATED_BIT: u8 = 0x08;
const ACTIVATION_STATE_MASK: u8 = 0x07;
const RESERVED_SHIFT: u32 = 4;
const RESERVED_MASK: u8 = 0x0F;
}
impl<'a> Parse<'a> for ActivationStatus {
type Error = Error;
fn parse(body: &'a [u8]) -> Result<Self> {
need_in(body, 1, "ActivationStatus")?;
Ok(Self {
event_activated: body[0] & Self::EVENT_ACTIVATED_BIT != 0,
activation_state: ActivationState::from_u8(body[0] & Self::ACTIVATION_STATE_MASK),
reserved: body[0] >> Self::RESERVED_SHIFT,
})
}
}
impl Serialize for ActivationStatus {
type Error = Error;
fn serialized_len(&self) -> usize {
1
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
if buf.is_empty() {
return Err(Error::OutputBufferTooSmall(
broadcast_common::error::OutputBufferTooSmall { need: 1, have: 0 },
));
}
let mut b = 0u8;
if self.event_activated {
b |= Self::EVENT_ACTIVATED_BIT;
}
b |= self.activation_state.to_u8() & Self::ACTIVATION_STATE_MASK;
b |= (self.reserved & Self::RESERVED_MASK) << Self::RESERVED_SHIFT;
buf[0] = b;
Ok(1)
}
}
crate::dispatch::declare_resource_apdus! {
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum StatusQueryApdu<'a> ("status_query") {
StatusQueryReq(StatusQueryReq) = tag::STATUS_QUERY_REQ,
TrapReq(TrapReq) = tag::TRAP_REQ,
GetNextItemReq(GetNextItemReq) = tag::GET_NEXT_ITEM_REQ,
GetNextItemAck(GetNextItemAck) = tag::GET_NEXT_ITEM_ACK,
StatusAck(StatusAck<'a>) = tag::STATUS_ACK,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_query_req_round_trips_and_bites() {
let req = StatusQueryReq {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::PortProfile,
};
let bytes = req.try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x80, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02]);
assert_eq!(StatusQueryReq::parse(&bytes).unwrap(), req);
let other = StatusQueryReq {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::ViewedService,
};
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn trap_and_getnext_round_trip() {
let trap = TrapReq {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::ActivationStatus,
};
assert_eq!(
trap.try_to_bytes().unwrap(),
[0x9F, 0x80, 0x01, 0x04, 0, 0, 0, 4]
);
assert_eq!(TrapReq::parse(&trap.try_to_bytes().unwrap()).unwrap(), trap);
let gnr = GetNextItemReq {
length_form: crate::length::LengthForm::Minimal,
start_status_item: 0x0000_0007,
};
assert_eq!(
gnr.try_to_bytes().unwrap(),
[0x9F, 0x80, 0x02, 0x04, 0, 0, 0, 7]
);
assert_eq!(
GetNextItemReq::parse(&gnr.try_to_bytes().unwrap()).unwrap(),
gnr
);
let gna = GetNextItemAck {
length_form: crate::length::LengthForm::Minimal,
next_status_item: 0x0000_0002,
};
assert_eq!(
gna.try_to_bytes().unwrap(),
[0x9F, 0x80, 0x03, 0x04, 0, 0, 0, 2]
);
assert_eq!(
GetNextItemAck::parse(&gna.try_to_bytes().unwrap()).unwrap(),
gna
);
}
#[test]
fn status_item_reserved_round_trips() {
let req = StatusQueryReq {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::from_u32(0),
};
assert_eq!(req.status_item, StatusItem::Reserved(0));
assert_eq!(req.status_item.name(), "reserved");
assert_eq!(
StatusQueryReq::parse(&req.try_to_bytes().unwrap()).unwrap(),
req
);
}
#[test]
fn status_ack_with_status_bytes_round_trips_and_bites() {
let ack = StatusAck {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::ViewedService,
status_bytes: &[0x01, 0x23, 0x00],
};
let bytes = ack.try_to_bytes().unwrap();
assert_eq!(
bytes,
[0x9F, 0x80, 0x04, 0x07, 0, 0, 0, 3, 0x01, 0x23, 0x00]
);
assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
let mut other = ack.clone();
other.status_bytes = &[0x01, 0x23, 0x01];
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn status_ack_empty_status_bytes_means_unsupported() {
let ack = StatusAck {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::PortProfile,
status_bytes: &[],
};
let bytes = ack.try_to_bytes().unwrap();
assert_eq!(bytes, [0x9F, 0x80, 0x04, 0x04, 0, 0, 0, 2]);
assert_eq!(StatusAck::parse(&bytes).unwrap(), ack);
}
#[test]
fn selection_information_multi_port_round_trips_and_bites() {
let si = SelectionInformation {
time: &[0x20, 0x06, 0x18, 0x12, 0x00],
ports: alloc::vec![
InPortDescription {
in_port_id: 0x01,
in_signal_desc: &[0xAA, 0xBB],
outputs: alloc::vec![
OutputSignal {
out_port_id: 0x00,
out_signal_desc: &[0x02],
},
OutputSignal {
out_port_id: 0x01,
out_signal_desc: &[0x01],
},
],
},
InPortDescription {
in_port_id: 0x18,
in_signal_desc: &[],
outputs: alloc::vec![],
},
],
};
let bytes = si.try_to_bytes().unwrap();
assert_eq!(SelectionInformation::parse(&bytes).unwrap(), si);
let mut other = si.clone();
other.ports[0].outputs[1].out_port_id = 0x02;
assert_ne!(bytes, other.try_to_bytes().unwrap());
assert_eq!(
bytes,
[
0x20, 0x06, 0x18, 0x12, 0x00, 0x01, 0x02, 0xAA, 0xBB, 0x00, 0x06, 0x00, 0x01, 0x02, 0x01, 0x01, 0x01, 0x18, 0x00, 0x00, 0x00, ]
);
}
#[test]
fn port_profile_multi_port_round_trips_and_bites() {
let pp = PortProfile {
receiver_identification: b"ACME-TV1",
ports: alloc::vec![
PortDescription {
in_port_id: 0x00,
in_port_desc: b"RF0",
out_port_id: 0x00,
out_port_desc: b"D0",
},
PortDescription {
in_port_id: 0x18,
in_port_desc: b"CI",
out_port_id: 0x10,
out_port_desc: b"SCART",
},
],
};
let bytes = pp.try_to_bytes().unwrap();
assert_eq!(PortProfile::parse(&bytes).unwrap(), pp);
let mut other = pp.clone();
other.ports[1].out_port_id = 0x11;
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn viewed_service_multi_component_round_trips_and_bites() {
let vs = ViewedService {
service_id: 0x1234,
component_tags: alloc::vec![0x10, 0x20, 0x30],
};
let bytes = vs.try_to_bytes().unwrap();
assert_eq!(bytes, [0x12, 0x34, 0x03, 0x10, 0x20, 0x30]);
assert_eq!(ViewedService::parse(&bytes).unwrap(), vs);
let mut other = vs.clone();
other.component_tags[2] = 0x31;
assert_ne!(bytes, other.try_to_bytes().unwrap());
}
#[test]
fn activation_status_packs_bits() {
let a = ActivationStatus {
event_activated: true,
activation_state: ActivationState::On,
reserved: 0,
};
let bytes = a.try_to_bytes().unwrap();
assert_eq!(bytes, [0x0A]);
assert_eq!(ActivationStatus::parse(&bytes).unwrap(), a);
let b = ActivationStatus {
event_activated: false,
activation_state: ActivationState::StandbyActive,
reserved: 0,
};
assert_eq!(b.try_to_bytes().unwrap(), [0x01]);
assert_eq!(ActivationStatus::parse(&[0x01]).unwrap(), b);
for byte in 0x00u8..=0xFF {
let parsed = ActivationStatus::parse(&[byte]).unwrap();
assert_eq!(parsed.try_to_bytes().unwrap(), [byte], "byte {byte:#04X}");
}
}
#[test]
fn dispatch_routes_each_tag() {
let q = StatusQueryReq {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::SelectionInformation,
}
.try_to_bytes()
.unwrap();
assert!(matches!(
StatusQueryApdu::parse(&q).unwrap(),
StatusQueryApdu::StatusQueryReq(_)
));
let ack = StatusAck {
length_form: crate::length::LengthForm::Minimal,
status_item: StatusItem::ActivationStatus,
status_bytes: &[0x0A],
}
.try_to_bytes()
.unwrap();
let parsed = StatusQueryApdu::parse(&ack).unwrap();
assert!(matches!(parsed, StatusQueryApdu::StatusAck(_)));
assert_eq!(parsed.try_to_bytes().unwrap(), ack);
}
}