mod mac;
mod write;
use core::fmt;
pub use mac::{Mac, MacTooLong};
pub use write::WriteError;
use crate::message::MessageType;
use crate::MAX_MAC_LEN;
pub const VERSION: u8 = 1;
pub const GLOBAL_NETWORK: u16 = 0xFFFF;
const NETWORK_MESSAGE: u8 = 0x80;
const DESTINATION: u8 = 0x20;
const SOURCE: u8 = 0x08;
const EXPECTING_REPLY: u8 = 0x04;
const PRIORITY: u8 = 0x03;
#[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: u8,
pub destination: Option<Address<'a>>,
pub source: Option<Address<'a>>,
pub hop_count: u8,
pub message_type: Option<MessageType>,
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,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ParseError<'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 ParseError<'_> {
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 ParseError<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::AddressTooLong { field, length, .. } => {
let octet = match field {
AddressField::Destination => "DLEN",
AddressField::Source => "SLEN",
};
write!(
f,
"NPDU {octet}={length} exceeds the {MAX_MAC_LEN}-octet address limit"
)
}
Self::Malformed(fault) => fault.fmt(f),
}
}
}
impl core::error::Error for ParseError<'_> {}
impl<'a> Npdu<'a> {
#[must_use]
pub const fn apdu(payload: &'a [u8]) -> Self {
Self {
expecting_reply: false,
priority: 0,
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: MessageType, 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 parse(frame: &'a [u8]) -> Result<Self, ParseError<'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(ParseError::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(ParseError::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 = MessageType::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: control & PRIORITY,
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 })
}