use crate::cobs;
use crate::crc::{crc8, crc16};
use crate::{
BROADCAST_MAC, FrameError, FrameSink, HEADER_LENGTH, MAX_COBS_LENGTH, MAX_STANDARD_MPDU_DATA,
MIN_COBS_LENGTH, PREAMBLE,
};
pub(crate) const DATA_OFFSET: usize = PREAMBLE.len() + HEADER_LENGTH;
const TRAILER_LENGTH: usize = 2;
pub(crate) const fn wire_length(length: usize) -> usize {
if length == 0 {
DATA_OFFSET
} else {
DATA_OFFSET + length + TRAILER_LENGTH
}
}
macro_rules! named_codes {
(
$(#[$meta:meta])*
pub struct $Name:ident($inner:ty);
$(
$(#[$vmeta:meta])*
const $VARIANT:ident = $val:expr_2021;
)*
) => {
$(#[$meta])*
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct $Name($inner);
impl $Name {
$(
$(#[$vmeta])*
#[doc = ""]
#[doc = concat!("Wire value `", stringify!($val), "`.")]
pub const $VARIANT: Self = Self($val);
)*
pub const ALL_NAMED: &[(&str, Self)] = &[
$( (stringify!($VARIANT), Self($val)), )*
];
#[inline]
#[must_use]
pub const fn from_raw(value: $inner) -> Self {
Self(value)
}
#[inline]
#[must_use]
pub const fn to_raw(self) -> $inner {
self.0
}
#[must_use]
pub const fn name(self) -> Option<&'static str> {
match self.0 {
$( $val => Some(stringify!($VARIANT)), )*
_ => None,
}
}
#[must_use]
pub const fn is_defined(self) -> bool {
self.name().is_some()
}
}
impl core::fmt::Debug for $Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.name() {
Some(name) => write!(f, "{}::{}", stringify!($Name), name),
None => write!(f, "{}({})", stringify!($Name), self.0),
}
}
}
impl core::fmt::Display for $Name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self.name() {
Some(name) => f.write_str(name),
None => write!(f, "{}", self.0),
}
}
}
};
}
named_codes! {
pub struct FrameType(u8);
const TOKEN = 0;
const POLL_FOR_MASTER = 1;
const REPLY_TO_POLL_FOR_MASTER = 2;
const TEST_REQUEST = 3;
const TEST_RESPONSE = 4;
const BACNET_DATA_EXPECTING_REPLY = 5;
const BACNET_DATA_NOT_EXPECTING_REPLY = 6;
const REPLY_POSTPONED = 7;
const BACNET_EXTENDED_DATA_EXPECTING_REPLY = 32;
const BACNET_EXTENDED_DATA_NOT_EXPECTING_REPLY = 33;
}
impl FrameType {
#[must_use]
pub const fn allows_data(self) -> bool {
!matches!(self.to_raw(), 0 | 1 | 2 | 7)
}
#[must_use]
pub const fn is_cobs(self) -> bool {
let raw = self.to_raw();
raw >= 32 && raw <= 127
}
#[must_use]
pub const fn is_extended_data(self) -> bool {
matches!(self.to_raw(), 32 | 33)
}
#[must_use]
pub const fn is_proprietary(self) -> bool {
self.to_raw() >= 128
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FrameHeader {
pub frame_type: FrameType,
pub destination: u8,
pub source: u8,
}
impl FrameHeader {
#[must_use]
pub const fn new(frame_type: FrameType, destination: u8, source: u8) -> Self {
Self {
frame_type,
destination,
source,
}
}
}
pub(crate) fn check_header_fields(
frame_type: FrameType,
source: u8,
length: usize,
) -> Result<(), FrameError> {
if source == BROADCAST_MAC {
return Err(FrameError::BroadcastSource);
}
if frame_type.is_cobs() {
if !(MIN_COBS_LENGTH..=MAX_COBS_LENGTH).contains(&length) {
return Err(FrameError::InvalidCobsLength { length });
}
if !frame_type.is_extended_data() {
return Err(FrameError::UnsupportedCobsFrame {
raw_type: frame_type.to_raw(),
});
}
} else if length > MAX_STANDARD_MPDU_DATA {
return Err(FrameError::Oversize {
length,
maximum: MAX_STANDARD_MPDU_DATA,
});
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Frame<'a> {
pub header: FrameHeader,
pub data: &'a [u8],
}
impl<'a> Frame<'a> {
#[must_use]
pub const fn new(frame_type: FrameType, destination: u8, source: u8, data: &'a [u8]) -> Self {
Self {
header: FrameHeader::new(frame_type, destination, source),
data,
}
}
pub fn encode_into(self, output: &mut [u8]) -> Result<usize, FrameError> {
let mut sink = output;
self.encode_to_sink(&mut sink)
}
pub fn encode_to_sink<S: FrameSink + ?Sized>(self, sink: &mut S) -> Result<usize, FrameError> {
let length = self.checked_length()?;
let total = wire_length(length);
if let Some(available) = sink.remaining_capacity()
&& available < total
{
return Err(FrameError::BufferExhausted {
needed: total,
available,
});
}
let fields = [
self.header.frame_type.to_raw(),
self.header.destination,
self.header.source,
(length >> 8) as u8,
length as u8,
];
let mut head = [0u8; DATA_OFFSET];
head[..PREAMBLE.len()].copy_from_slice(&PREAMBLE);
head[PREAMBLE.len()..DATA_OFFSET - 1].copy_from_slice(&fields);
head[DATA_OFFSET - 1] = crc8(&fields);
sink.write(&head)?;
if self.header.frame_type.is_cobs() {
cobs::write_fields(self.data, sink)?;
} else if length != 0 {
sink.write(self.data)?;
sink.write(&crc16(self.data).to_le_bytes())?;
}
Ok(total)
}
fn checked_length(&self) -> Result<usize, FrameError> {
let frame_type = self.header.frame_type;
let length = if frame_type.is_cobs() {
cobs::frame_length(self.data)
} else {
self.data.len()
};
check_header_fields(frame_type, self.header.source, length)?;
if length != 0 && !frame_type.allows_data() {
return Err(FrameError::UnexpectedData {
raw_type: frame_type.to_raw(),
length,
});
}
if length < 2 && frame_type.is_proprietary() {
return Err(FrameError::MissingVendorId {
raw_type: frame_type.to_raw(),
length,
});
}
Ok(length)
}
}