use crate::error::{Error, Result};
use crate::objects;
use crate::tag::ApduTag;
use alloc::vec::Vec;
use broadcast_common::{Parse, Serialize};
pub mod tag {
use crate::tag::ApduTag;
pub const COMMS_INFO_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x07);
pub const COMMS_INFO_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x08);
pub const COMMS_IP_CONFIG_REQ: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x09);
pub const COMMS_IP_CONFIG_REPLY: ApduTag = ApduTag::from_bytes(0x9F, 0x8C, 0x0A);
}
pub const IP_ADDR_LEN: usize = 16;
pub const MAC_LEN: usize = 6;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum ConnectionState {
Disconnected,
Connected,
Reserved(u8),
}
impl ConnectionState {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v & 0x03 {
0x00 => Self::Disconnected,
0x01 => Self::Connected,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Disconnected => 0x00,
Self::Connected => 0x01,
Self::Reserved(v) => v & 0x03,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Disconnected => "disconnected",
Self::Connected => "connected",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(ConnectionState, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum IpProtocolVersion {
Ipv4,
Ipv6,
Reserved(u8),
}
impl IpProtocolVersion {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x01 => Self::Ipv4,
0x02 => Self::Ipv6,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::Ipv4 => 0x01,
Self::Ipv6 => 0x02,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::Ipv4 => "ipv4",
Self::Ipv6 => "ipv6",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(IpProtocolVersion, Reserved);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CommsInfoReq;
impl<'a> Parse<'a> for CommsInfoReq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
objects::parse_empty_apdu(bytes, tag::COMMS_INFO_REQ, "comms_info_req")?;
Ok(Self)
}
}
impl Serialize for CommsInfoReq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::empty_apdu_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::serialize_empty_apdu(tag::COMMS_INFO_REQ, buf)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CommsInfoReply {
pub lts_id: u8,
pub status: bool,
pub source_ip_address: [u8; IP_ADDR_LEN],
pub source_port: u16,
pub input_delivery_pid: u16,
}
const INFO_REPLY_BODY: usize = 1 + 1 + IP_ADDR_LEN + 2 + 2;
const STATUS_BIT: u8 = 0x01;
const INPUT_DELIVERY_PID_MASK: u16 = 0x1FFF;
impl<'a> Parse<'a> for CommsInfoReply {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body = objects::parse_apdu_header(bytes, tag::COMMS_INFO_REPLY, "comms_info_reply")?;
if body.len() < INFO_REPLY_BODY {
return Err(Error::BufferTooShort {
need: INFO_REPLY_BODY,
have: body.len(),
what: "comms_info_reply",
});
}
let lts_id = body[0];
let status = body[1] & STATUS_BIT != 0;
let mut source_ip_address = [0u8; IP_ADDR_LEN];
source_ip_address.copy_from_slice(&body[2..2 + IP_ADDR_LEN]);
let p = 2 + IP_ADDR_LEN;
let source_port = u16::from_be_bytes([body[p], body[p + 1]]);
let input_delivery_pid =
u16::from_be_bytes([body[p + 2], body[p + 3]]) & INPUT_DELIVERY_PID_MASK;
Ok(Self {
lts_id,
status,
source_ip_address,
source_port,
input_delivery_pid,
})
}
}
impl Serialize for CommsInfoReply {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(INFO_REPLY_BODY)
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = objects::write_apdu_header(tag::COMMS_INFO_REPLY, INFO_REPLY_BODY, buf)?;
buf[pos] = self.lts_id;
buf[pos + 1] = u8::from(self.status);
buf[pos + 2..pos + 2 + IP_ADDR_LEN].copy_from_slice(&self.source_ip_address);
let p = pos + 2 + IP_ADDR_LEN;
buf[p..p + 2].copy_from_slice(&self.source_port.to_be_bytes());
buf[p + 2..p + 4]
.copy_from_slice(&(self.input_delivery_pid & INPUT_DELIVERY_PID_MASK).to_be_bytes());
Ok(pos + INFO_REPLY_BODY)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CommsIpConfigReq;
impl<'a> Parse<'a> for CommsIpConfigReq {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
objects::parse_empty_apdu(bytes, tag::COMMS_IP_CONFIG_REQ, "comms_IP_config_req")?;
Ok(Self)
}
}
impl Serialize for CommsIpConfigReq {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::empty_apdu_len()
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
objects::serialize_empty_apdu(tag::COMMS_IP_CONFIG_REQ, buf)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct IpConfig {
pub ip_address: [u8; IP_ADDR_LEN],
pub network_mask: [u8; IP_ADDR_LEN],
pub default_gateway: [u8; IP_ADDR_LEN],
pub dhcp_server_address: [u8; IP_ADDR_LEN],
pub dns_server_addresses: Vec<[u8; IP_ADDR_LEN]>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct CommsIpConfigReply {
pub connection_state: ConnectionState,
pub physical_address: [u8; MAC_LEN],
pub ip_config: Option<IpConfig>,
}
const IP_CONFIG_PREFIX: usize = 1 + MAC_LEN;
const IP_CONFIG_FIXED: usize = 4 * IP_ADDR_LEN + 1;
const CONNECTION_STATE_CONNECTED: u8 = 0x01;
impl CommsIpConfigReply {
fn body_len(&self) -> usize {
IP_CONFIG_PREFIX
+ match &self.ip_config {
Some(c) => IP_CONFIG_FIXED + c.dns_server_addresses.len() * IP_ADDR_LEN,
None => 0,
}
}
}
fn read_addr(body: &[u8], pos: usize) -> [u8; IP_ADDR_LEN] {
let mut a = [0u8; IP_ADDR_LEN];
a.copy_from_slice(&body[pos..pos + IP_ADDR_LEN]);
a
}
impl<'a> Parse<'a> for CommsIpConfigReply {
type Error = Error;
fn parse(bytes: &'a [u8]) -> Result<Self> {
let body =
objects::parse_apdu_header(bytes, tag::COMMS_IP_CONFIG_REPLY, "comms_IP_config_reply")?;
if body.len() < IP_CONFIG_PREFIX {
return Err(Error::BufferTooShort {
need: IP_CONFIG_PREFIX,
have: body.len(),
what: "comms_IP_config_reply",
});
}
let connection_state = ConnectionState::from_u8(body[0] >> 6);
let mut physical_address = [0u8; MAC_LEN];
physical_address.copy_from_slice(&body[1..1 + MAC_LEN]);
let ip_config = if connection_state.to_u8() == CONNECTION_STATE_CONNECTED {
if body.len() < IP_CONFIG_PREFIX + IP_CONFIG_FIXED {
return Err(Error::BufferTooShort {
need: IP_CONFIG_PREFIX + IP_CONFIG_FIXED,
have: body.len(),
what: "comms_IP_config_reply ip_config",
});
}
let mut p = IP_CONFIG_PREFIX;
let ip_address = read_addr(body, p);
p += IP_ADDR_LEN;
let network_mask = read_addr(body, p);
p += IP_ADDR_LEN;
let default_gateway = read_addr(body, p);
p += IP_ADDR_LEN;
let dhcp_server_address = read_addr(body, p);
p += IP_ADDR_LEN;
let n = body[p] as usize;
p += 1;
if body.len() < p + n * IP_ADDR_LEN {
return Err(Error::BufferTooShort {
need: p + n * IP_ADDR_LEN,
have: body.len(),
what: "comms_IP_config_reply dns_servers",
});
}
let mut dns_server_addresses = Vec::with_capacity(n);
for _ in 0..n {
dns_server_addresses.push(read_addr(body, p));
p += IP_ADDR_LEN;
}
Some(IpConfig {
ip_address,
network_mask,
default_gateway,
dhcp_server_address,
dns_server_addresses,
})
} else {
None
};
Ok(Self {
connection_state,
physical_address,
ip_config,
})
}
}
impl Serialize for CommsIpConfigReply {
type Error = Error;
fn serialized_len(&self) -> usize {
objects::apdu_len(self.body_len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let body_len = self.body_len();
let mut pos = objects::write_apdu_header(tag::COMMS_IP_CONFIG_REPLY, body_len, buf)?;
buf[pos] = self.connection_state.to_u8() << 6;
buf[pos + 1..pos + 1 + MAC_LEN].copy_from_slice(&self.physical_address);
pos += IP_CONFIG_PREFIX;
if let Some(c) = &self.ip_config {
buf[pos..pos + IP_ADDR_LEN].copy_from_slice(&c.ip_address);
pos += IP_ADDR_LEN;
buf[pos..pos + IP_ADDR_LEN].copy_from_slice(&c.network_mask);
pos += IP_ADDR_LEN;
buf[pos..pos + IP_ADDR_LEN].copy_from_slice(&c.default_gateway);
pos += IP_ADDR_LEN;
buf[pos..pos + IP_ADDR_LEN].copy_from_slice(&c.dhcp_server_address);
pos += IP_ADDR_LEN;
buf[pos] = c.dns_server_addresses.len() as u8;
pos += 1;
for a in &c.dns_server_addresses {
buf[pos..pos + IP_ADDR_LEN].copy_from_slice(a);
pos += IP_ADDR_LEN;
}
}
Ok(pos)
}
}
pub const HYBRID_DESCRIPTOR_TAG: u8 = 0x05;
pub const MULTICAST_DESCRIPTOR_TAG: u8 = 0x06;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum IpConnectionType {
IpDescriptor,
HostnameDescriptor,
MulticastDescriptor,
Reserved(u8),
}
impl IpConnectionType {
#[must_use]
pub fn from_u8(v: u8) -> Self {
match v {
0x03 => Self::IpDescriptor,
0x04 => Self::HostnameDescriptor,
0x06 => Self::MulticastDescriptor,
other => Self::Reserved(other),
}
}
#[must_use]
pub const fn to_u8(self) -> u8 {
match self {
Self::IpDescriptor => 0x03,
Self::HostnameDescriptor => 0x04,
Self::MulticastDescriptor => 0x06,
Self::Reserved(v) => v,
}
}
#[must_use]
pub fn name(&self) -> &'static str {
match self {
Self::IpDescriptor => "ip_descriptor",
Self::HostnameDescriptor => "hostname_descriptor",
Self::MulticastDescriptor => "multicast_descriptor",
Self::Reserved(_) => "reserved",
}
}
}
broadcast_common::impl_spec_display!(IpConnectionType, Reserved);
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct HybridDescriptor<'a> {
pub lts_id: u8,
pub ip_connection_type: IpConnectionType,
#[cfg_attr(feature = "serde", serde(borrow, with = "crate::objects::bytes_serde"))]
pub inner: &'a [u8],
}
const HYBRID_FIXED: usize = 1 + 1;
impl<'a> HybridDescriptor<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<Self> {
let data = parse_descriptor_header(bytes, HYBRID_DESCRIPTOR_TAG, "hybrid_descriptor")?;
if data.len() < HYBRID_FIXED {
return Err(Error::BufferTooShort {
need: HYBRID_FIXED,
have: data.len(),
what: "hybrid_descriptor",
});
}
Ok(Self {
lts_id: data[0],
ip_connection_type: IpConnectionType::from_u8(data[1]),
inner: &data[HYBRID_FIXED..],
})
}
fn data_len(&self) -> usize {
HYBRID_FIXED + self.inner.len()
}
}
impl Serialize for HybridDescriptor<'_> {
type Error = Error;
fn serialized_len(&self) -> usize {
descriptor_len(self.data_len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let pos = write_descriptor_header(HYBRID_DESCRIPTOR_TAG, self.data_len(), buf)?;
buf[pos] = self.lts_id;
buf[pos + 1] = self.ip_connection_type.to_u8();
buf[pos + HYBRID_FIXED..pos + self.data_len()].copy_from_slice(self.inner);
Ok(pos + self.data_len())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
pub struct MulticastDescriptor {
pub ip_protocol_version: IpProtocolVersion,
pub ip_address: [u8; IP_ADDR_LEN],
pub multicast_port: u16,
pub include_sources: bool,
pub source_addresses: Vec<[u8; IP_ADDR_LEN]>,
}
const MULTICAST_FIXED: usize = 1 + IP_ADDR_LEN + 2 + 1 + 1;
const INCLUDE_SOURCES_BIT: u8 = 0x01;
impl MulticastDescriptor {
pub fn parse(bytes: &[u8]) -> Result<Self> {
let data =
parse_descriptor_header(bytes, MULTICAST_DESCRIPTOR_TAG, "multicast_descriptor")?;
if data.len() < MULTICAST_FIXED {
return Err(Error::BufferTooShort {
need: MULTICAST_FIXED,
have: data.len(),
what: "multicast_descriptor",
});
}
let ip_protocol_version = IpProtocolVersion::from_u8(data[0]);
let ip_address = read_addr(data, 1);
let multicast_port = u16::from_be_bytes([data[1 + IP_ADDR_LEN], data[2 + IP_ADDR_LEN]]);
let flags_pos = 3 + IP_ADDR_LEN;
let include_sources = data[flags_pos] & INCLUDE_SOURCES_BIT != 0;
let n = data[flags_pos + 1] as usize;
let mut pos = MULTICAST_FIXED;
if data.len() < pos + n * IP_ADDR_LEN {
return Err(Error::BufferTooShort {
need: pos + n * IP_ADDR_LEN,
have: data.len(),
what: "multicast_descriptor sources",
});
}
let mut source_addresses = Vec::with_capacity(n);
for _ in 0..n {
source_addresses.push(read_addr(data, pos));
pos += IP_ADDR_LEN;
}
Ok(Self {
ip_protocol_version,
ip_address,
multicast_port,
include_sources,
source_addresses,
})
}
fn data_len(&self) -> usize {
MULTICAST_FIXED + self.source_addresses.len() * IP_ADDR_LEN
}
}
impl Serialize for MulticastDescriptor {
type Error = Error;
fn serialized_len(&self) -> usize {
descriptor_len(self.data_len())
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
let mut pos = write_descriptor_header(MULTICAST_DESCRIPTOR_TAG, self.data_len(), buf)?;
buf[pos] = self.ip_protocol_version.to_u8();
buf[pos + 1..pos + 1 + IP_ADDR_LEN].copy_from_slice(&self.ip_address);
let p = pos + 1 + IP_ADDR_LEN;
buf[p..p + 2].copy_from_slice(&self.multicast_port.to_be_bytes());
buf[p + 2] = u8::from(self.include_sources);
buf[p + 3] = self.source_addresses.len() as u8;
pos += MULTICAST_FIXED;
for a in &self.source_addresses {
buf[pos..pos + IP_ADDR_LEN].copy_from_slice(a);
pos += IP_ADDR_LEN;
}
Ok(pos)
}
}
const DESCRIPTOR_HEADER: usize = 2;
fn parse_descriptor_header<'a>(
bytes: &'a [u8],
expected_tag: u8,
what: &'static str,
) -> Result<&'a [u8]> {
if bytes.len() < DESCRIPTOR_HEADER {
return Err(Error::BufferTooShort {
need: DESCRIPTOR_HEADER,
have: bytes.len(),
what,
});
}
if bytes[0] != expected_tag {
return Err(Error::InvalidObject {
what,
reason: "unexpected descriptor_tag",
});
}
let len = bytes[1] as usize;
let end = DESCRIPTOR_HEADER + len;
if bytes.len() < end {
return Err(Error::LengthMismatch {
what,
declared: len,
actual: bytes.len().saturating_sub(DESCRIPTOR_HEADER),
});
}
Ok(&bytes[DESCRIPTOR_HEADER..end])
}
fn descriptor_len(data_len: usize) -> usize {
DESCRIPTOR_HEADER + data_len
}
fn write_descriptor_header(tag: u8, data_len: usize, buf: &mut [u8]) -> Result<usize> {
let total = descriptor_len(data_len);
if buf.len() < total {
return Err(Error::OutputBufferTooSmall {
need: total,
have: buf.len(),
});
}
if data_len > u8::MAX as usize {
return Err(Error::LengthTooLarge(data_len));
}
buf[0] = tag;
buf[1] = data_len as u8;
Ok(DESCRIPTOR_HEADER)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum LscV4Apdu {
CommsInfoReq(CommsInfoReq),
CommsInfoReply(CommsInfoReply),
CommsIpConfigReq(CommsIpConfigReq),
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum LscV4ReplyApdu {
CommsIpConfigReply(CommsIpConfigReply),
}
impl LscV4Apdu {
pub fn parse(body: &[u8]) -> Result<Self> {
if body.len() < 3 {
return Err(Error::BufferTooShort {
need: 3,
have: body.len(),
what: "lsc_v4 apdu_tag",
});
}
let t = ApduTag::from_bytes(body[0], body[1], body[2]);
match t {
tag::COMMS_INFO_REQ => Ok(Self::CommsInfoReq(CommsInfoReq::parse(body)?)),
tag::COMMS_INFO_REPLY => Ok(Self::CommsInfoReply(CommsInfoReply::parse(body)?)),
tag::COMMS_IP_CONFIG_REQ => Ok(Self::CommsIpConfigReq(CommsIpConfigReq::parse(body)?)),
_ => Err(Error::UnexpectedApduTag {
got: t.as_u24(),
expected: tag::COMMS_INFO_REQ.as_u24(),
what: "lsc_v4",
}),
}
}
}
pub fn parse_ip_config_reply(body: &[u8]) -> Result<CommsIpConfigReply> {
CommsIpConfigReply::parse(body)
}
impl Serialize for LscV4Apdu {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::CommsInfoReq(o) => o.serialized_len(),
Self::CommsInfoReply(o) => o.serialized_len(),
Self::CommsIpConfigReq(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::CommsInfoReq(o) => o.serialize_into(buf),
Self::CommsInfoReply(o) => o.serialize_into(buf),
Self::CommsIpConfigReq(o) => o.serialize_into(buf),
}
}
}
impl Serialize for LscV4ReplyApdu {
type Error = Error;
fn serialized_len(&self) -> usize {
match self {
Self::CommsIpConfigReply(o) => o.serialized_len(),
}
}
fn serialize_into(&self, buf: &mut [u8]) -> Result<usize> {
match self {
Self::CommsIpConfigReply(o) => o.serialize_into(buf),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const IP_A: [u8; IP_ADDR_LEN] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xC0, 0xA8, 0x01,
0x0A,
];
const IP_B: [u8; IP_ADDR_LEN] = [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x08, 0x08, 0x08,
0x08,
];
#[test]
fn info_req_round_trips() {
let bytes = CommsInfoReq.to_bytes();
assert_eq!(bytes, [0x9F, 0x8C, 0x07, 0x00]);
assert_eq!(CommsInfoReq::parse(&bytes).unwrap(), CommsInfoReq);
}
#[test]
fn info_reply_round_trips_and_bites() {
let r = CommsInfoReply {
lts_id: 0x07,
status: true,
source_ip_address: IP_A,
source_port: 0x1234,
input_delivery_pid: 0x0100,
};
let bytes = r.to_bytes();
assert_eq!(bytes[0..4], [0x9F, 0x8C, 0x08, 0x16]);
assert_eq!(bytes[4], 0x07);
assert_eq!(bytes[5], 0x01); assert_eq!(&bytes[6..22], &IP_A);
assert_eq!(&bytes[22..24], &[0x12, 0x34]);
assert_eq!(&bytes[24..26], &[0x01, 0x00]);
assert_eq!(CommsInfoReply::parse(&bytes).unwrap(), r);
let mut other = r;
other.status = false;
assert_eq!(other.to_bytes()[5], 0x00);
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn info_reply_pid_is_13_bit_masked() {
let bytes = [
0x9F, 0x8C, 0x08, 0x16, 0x00, 0x00, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0x00, 0x00, 0xFF, 0xFE,
];
let r = CommsInfoReply::parse(&bytes).unwrap();
assert_eq!(r.input_delivery_pid, 0x1FFE);
}
#[test]
fn ip_config_req_round_trips() {
let bytes = CommsIpConfigReq.to_bytes();
assert_eq!(bytes, [0x9F, 0x8C, 0x09, 0x00]);
assert_eq!(CommsIpConfigReq::parse(&bytes).unwrap(), CommsIpConfigReq);
}
#[test]
fn ip_config_reply_disconnected_round_trips() {
let r = CommsIpConfigReply {
connection_state: ConnectionState::Disconnected,
physical_address: [0x00, 0x11, 0x22, 0x33, 0x44, 0x55],
ip_config: None,
};
let bytes = r.to_bytes();
assert_eq!(
bytes,
[
0x9F, 0x8C, 0x0A, 0x07, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55
]
);
assert_eq!(CommsIpConfigReply::parse(&bytes).unwrap(), r);
}
#[test]
fn ip_config_reply_connected_two_dns_round_trips_and_bites() {
let r = CommsIpConfigReply {
connection_state: ConnectionState::Connected,
physical_address: [0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F],
ip_config: Some(IpConfig {
ip_address: IP_A,
network_mask: IP_B,
default_gateway: IP_A,
dhcp_server_address: IP_B,
dns_server_addresses: alloc::vec![IP_A, IP_B],
}),
};
let bytes = r.to_bytes();
assert_eq!(bytes[0..4], [0x9F, 0x8C, 0x0A, (7 + 65 + 32) as u8]);
assert_eq!(bytes[4], 0x40);
assert_eq!(&bytes[5..11], &[0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]);
assert_eq!(bytes[4 + 7 + 4 * IP_ADDR_LEN], 0x02);
assert_eq!(CommsIpConfigReply::parse(&bytes).unwrap(), r);
let mut other = r.clone();
if let Some(c) = &mut other.ip_config {
c.dns_server_addresses.pop();
}
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn hybrid_descriptor_round_trips_and_bites() {
let h = HybridDescriptor {
lts_id: 0x05,
ip_connection_type: IpConnectionType::IpDescriptor,
inner: &[0xDE, 0xAD],
};
let bytes = h.to_bytes();
assert_eq!(bytes, [0x05, 0x04, 0x05, 0x03, 0xDE, 0xAD]);
assert_eq!(HybridDescriptor::parse(&bytes).unwrap(), h);
let mut other = h;
other.lts_id = 0x06;
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn multicast_descriptor_two_sources_round_trips_and_bites() {
let m = MulticastDescriptor {
ip_protocol_version: IpProtocolVersion::Ipv4,
ip_address: IP_A,
multicast_port: 0x1389,
include_sources: true,
source_addresses: alloc::vec![IP_A, IP_B],
};
let bytes = m.to_bytes();
assert_eq!(bytes[0], MULTICAST_DESCRIPTOR_TAG);
assert_eq!(bytes[1], 53);
assert_eq!(bytes[2], 0x01); assert_eq!(&bytes[3..19], &IP_A);
assert_eq!(&bytes[19..21], &[0x13, 0x89]);
assert_eq!(bytes[21], 0x01); assert_eq!(bytes[22], 0x02); assert_eq!(MulticastDescriptor::parse(&bytes).unwrap(), m);
let mut other = m.clone();
other.include_sources = false;
assert_eq!(other.to_bytes()[21], 0x00);
assert_ne!(bytes, other.to_bytes());
}
#[test]
fn multicast_descriptor_any_source() {
let m = MulticastDescriptor {
ip_protocol_version: IpProtocolVersion::Ipv6,
ip_address: IP_B,
multicast_port: 5004,
include_sources: false,
source_addresses: Vec::new(),
};
let bytes = m.to_bytes();
assert_eq!(bytes[1], 21); assert_eq!(MulticastDescriptor::parse(&bytes).unwrap(), m);
}
#[test]
fn dispatch_routes_fixed_tags() {
assert!(matches!(
LscV4Apdu::parse(&CommsInfoReq.to_bytes()).unwrap(),
LscV4Apdu::CommsInfoReq(_)
));
let reply = CommsInfoReply {
lts_id: 0,
status: false,
source_ip_address: [0; IP_ADDR_LEN],
source_port: 0,
input_delivery_pid: 0,
};
assert!(matches!(
LscV4Apdu::parse(&reply.to_bytes()).unwrap(),
LscV4Apdu::CommsInfoReply(_)
));
assert!(matches!(
LscV4Apdu::parse(&CommsIpConfigReq.to_bytes()).unwrap(),
LscV4Apdu::CommsIpConfigReq(_)
));
let cfg = CommsIpConfigReply {
connection_state: ConnectionState::Disconnected,
physical_address: [0; MAC_LEN],
ip_config: None,
};
let cb = cfg.to_bytes();
assert_eq!(parse_ip_config_reply(&cb).unwrap(), cfg);
assert!(matches!(
LscV4Apdu::parse(&cb),
Err(Error::UnexpectedApduTag { .. })
));
}
}