use core::fmt;
use crate::{GLOBAL_NETWORK, MAX_MAC_LEN, NetworkMessageType, NetworkPriority, VERSION};
pub(crate) const NETWORK_MESSAGE: u8 = 0x80;
pub(crate) const DESTINATION: u8 = 0x20;
pub(crate) const SOURCE: u8 = 0x08;
pub(crate) const EXPECTING_REPLY: u8 = 0x04;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Address<'a> {
pub network: u16,
pub mac: &'a [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Npdu<'a> {
pub expecting_reply: bool,
pub priority: NetworkPriority,
pub destination: Option<Address<'a>>,
pub source: Option<Address<'a>>,
pub hop_count: u8,
pub message_type: Option<NetworkMessageType>,
pub vendor_id: Option<u16>,
pub payload: &'a [u8],
pub payload_offset: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AddressField {
Destination,
Source,
}
impl AddressField {
#[must_use]
pub const fn length_octet(self) -> &'static str {
match self {
Self::Destination => "DLEN",
Self::Source => "SLEN",
}
}
}
impl fmt::Display for AddressField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Destination => "destination",
Self::Source => "source",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DecodeError<'a> {
AddressTooLong {
field: AddressField,
length: u8,
dnet: Option<u16>,
source: Option<Address<'a>>,
},
Malformed(Malformed),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Malformed {
Truncated,
Version,
ReservedNetwork,
EmptySource,
}
impl From<Malformed> for DecodeError<'_> {
fn from(fault: Malformed) -> Self {
Self::Malformed(fault)
}
}
impl fmt::Display for Malformed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::Truncated => "NPDU truncated",
Self::Version => "unsupported BACnet protocol version",
Self::ReservedNetwork => "NPDU names a reserved network number",
Self::EmptySource => "NPDU source SLEN=0 is invalid",
})
}
}
impl core::error::Error for Malformed {}
impl fmt::Display for DecodeError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AddressTooLong { field, length, .. } => write!(
f,
"NPDU {}={length} exceeds the {MAX_MAC_LEN}-octet address limit",
field.length_octet()
),
Self::Malformed(fault) => fault.fmt(f),
}
}
}
impl core::error::Error for DecodeError<'_> {}
impl<'a> Npdu<'a> {
#[must_use]
pub const fn apdu(payload: &'a [u8]) -> Self {
Self {
expecting_reply: false,
priority: NetworkPriority::Normal,
destination: None,
source: None,
hop_count: 255,
message_type: None,
vendor_id: None,
payload,
payload_offset: 0,
}
}
#[must_use]
pub const fn message(message_type: NetworkMessageType, payload: &'a [u8]) -> Self {
let mut npdu = Self::apdu(payload);
npdu.message_type = Some(message_type);
npdu
}
#[must_use]
pub const fn is_network_message(&self) -> bool {
self.message_type.is_some()
}
#[must_use]
pub fn is_global(&self) -> bool {
self.destination
.is_some_and(|destination| destination.network == GLOBAL_NETWORK)
}
pub fn decode(frame: &'a [u8]) -> Result<Self, DecodeError<'a>> {
let [version, control, ..] = *frame else {
return Err(Malformed::Truncated.into());
};
if version != VERSION {
return Err(Malformed::Version.into());
}
let has_source = control & SOURCE != 0;
let mut at = 2;
let mut destination = None;
if control & DESTINATION != 0 {
let (network, length) = network_and_length(frame, at)?;
if network == 0 {
return Err(Malformed::ReservedNetwork.into());
}
at += 3;
if usize::from(length) > MAX_MAC_LEN {
return Err(DecodeError::AddressTooLong {
field: AddressField::Destination,
length,
dnet: Some(network),
source: if has_source {
source_behind(frame, at + usize::from(length))
} else {
None
},
});
}
let mac = octets(frame, at, length)?;
at += mac.len();
destination = Some(Address { network, mac });
}
let mut source = None;
if has_source {
let (network, length) = network_and_length(frame, at)?;
if network == 0 || network == GLOBAL_NETWORK {
return Err(Malformed::ReservedNetwork.into());
}
at += 3;
if length == 0 {
return Err(Malformed::EmptySource.into());
}
if usize::from(length) > MAX_MAC_LEN {
return Err(DecodeError::AddressTooLong {
field: AddressField::Source,
length,
dnet: destination.map(|destination| destination.network),
source: None,
});
}
let mac = octets(frame, at, length)?;
at += mac.len();
source = Some(Address { network, mac });
}
let mut hop_count = 255;
if destination.is_some() {
hop_count = *frame.get(at).ok_or(Malformed::Truncated)?;
at += 1;
}
let mut message_type = None;
let mut vendor_id = None;
if control & NETWORK_MESSAGE != 0 {
let kind = NetworkMessageType::from_raw(*frame.get(at).ok_or(Malformed::Truncated)?);
at += 1;
if kind.is_proprietary() {
let vendor = octets(frame, at, 2)?;
vendor_id = Some(u16::from_be_bytes([vendor[0], vendor[1]]));
at += 2;
}
message_type = Some(kind);
}
Ok(Self {
expecting_reply: control & EXPECTING_REPLY != 0,
priority: NetworkPriority::from_control(control),
destination,
source,
hop_count,
message_type,
vendor_id,
payload: &frame[at..],
payload_offset: at,
})
}
}
fn network_and_length(frame: &[u8], at: usize) -> Result<(u16, u8), Malformed> {
match octets(frame, at, 3)? {
&[high, low, length] => Ok((u16::from_be_bytes([high, low]), length)),
_ => Err(Malformed::Truncated),
}
}
fn octets(frame: &[u8], at: usize, length: u8) -> Result<&[u8], Malformed> {
frame
.get(at..at + usize::from(length))
.ok_or(Malformed::Truncated)
}
fn source_behind(frame: &[u8], at: usize) -> Option<Address<'_>> {
let (network, length) = network_and_length(frame, at).ok()?;
if network == 0 || network == GLOBAL_NETWORK || length == 0 {
return None;
}
if usize::from(length) > MAX_MAC_LEN {
return None;
}
let mac = octets(frame, at + 3, length).ok()?;
Some(Address { network, mac })
}