use core::fmt;
use core::net::SocketAddrV4;
use bacnet_bip_codec::{Bvll, HEADER_LENGTH, MAX_BVLL_LENGTH};
use bacnet_npdu::MAX_NPDU;
use super::config::{Link, Mode};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Outgoing<'n> {
pub to: SocketAddrV4,
pub message: Bvll<'n>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cast {
Unicast,
Broadcast,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SendRefusal {
TooLong {
max: usize,
},
HoldFull,
GroupDestination,
SocketFull,
SocketError,
}
impl fmt::Display for SendRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TooLong { max } => write!(f, "NPDU longer than the {max} octets B/IP sends"),
Self::HoldFull => f.write_str("no room to hold a broadcast until registration"),
Self::GroupDestination => {
f.write_str("a unicast can't go to a group address; send a broadcast instead")
}
Self::SocketFull => f.write_str("the socket can't take the datagram now"),
Self::SocketError => f.write_str("the socket couldn't send the datagram"),
}
}
}
impl core::error::Error for SendRefusal {}
const BROADCAST_OVERHEAD: usize = HEADER_LENGTH + 6;
impl Link {
#[must_use]
pub fn max_npdu(&self, cast: Cast) -> usize {
let overhead = match cast {
Cast::Unicast => HEADER_LENGTH,
Cast::Broadcast => BROADCAST_OVERHEAD,
};
match self.budget {
Some(budget) => usize::from(budget).saturating_sub(overhead).min(MAX_NPDU),
None => MAX_BVLL_LENGTH - overhead,
}
}
pub fn unicast<'n>(
&self,
to: SocketAddrV4,
npdu: &'n [u8],
) -> Result<Outgoing<'n>, SendRefusal> {
if self.groups().contains(to) {
return Err(SendRefusal::GroupDestination);
}
self.fits(npdu, Cast::Unicast)?;
Ok(Outgoing {
to,
message: Bvll::OriginalUnicastNpdu(npdu),
})
}
pub fn broadcast<'n>(&self, npdu: &'n [u8]) -> Result<(Outgoing<'n>, bool), SendRefusal> {
self.fits(npdu, Cast::Broadcast)?;
let local = Outgoing {
to: self.subnet.broadcast,
message: Bvll::OriginalBroadcastNpdu(npdu),
};
Ok(match self.mode {
Mode::Normal => (local, false),
Mode::Bbmd => (local, true),
Mode::Foreign(foreign) => (
Outgoing {
to: foreign.bbmd,
message: Bvll::DistributeBroadcastToNetwork(npdu),
},
false,
),
})
}
fn fits(&self, npdu: &[u8], cast: Cast) -> Result<(), SendRefusal> {
if self.budget.is_none() {
return Ok(());
}
let max = self.max_npdu(cast);
if npdu.len() > max {
return Err(SendRefusal::TooLong { max });
}
Ok(())
}
}