use core::net::SocketAddrV4;
use bacnet_bip_codec::{BvlcFunction, BvlcResultCode, Bvll, BvllError, BvllFrame};
use bacnet_time::Instant;
use super::addressing::Addressed;
use super::config::{ForwardedTrust, Link, Mode};
use crate::Arrival;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Datagram<'d> {
pub payload: &'d [u8],
pub source: SocketAddrV4,
pub to: Option<Addressed>,
pub path: Path,
pub truncated: bool,
}
impl<'d> Datagram<'d> {
#[must_use]
pub const fn new(payload: &'d [u8], source: SocketAddrV4, to: Option<Addressed>) -> Self {
Self {
payload,
source,
to,
path: Path::Primary,
truncated: false,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Path {
Primary,
Listener {
other_interface: bool,
},
}
pub trait Policy {
fn admit_management(
&mut self,
source: SocketAddrV4,
function: BvlcFunction,
now: Instant,
) -> bool {
let _ = (source, function, now);
true
}
fn accept_table(&mut self, ack: &Bvll<'_>) -> bool {
let _ = ack;
true
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Permissive;
impl Policy for Permissive {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Received<'d> {
Deliver(Delivery<'d>),
Nak {
to: SocketAddrV4,
code: BvlcResultCode,
malformed: bool,
},
Response(Response<'d>),
Bbmd(ForBbmd<'d>),
Discarded(Discard),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Delivery<'d> {
pub npdu: &'d [u8],
pub source: SocketAddrV4,
pub group: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Response<'d> {
pub peer: SocketAddrV4,
pub function: BvlcFunction,
pub message: Result<Bvll<'d>, BvllError>,
pub accepted: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Discard {
Truncated,
GroupSource,
UnknownDestination,
ListenerMismatch {
other_interface: bool,
},
TooShort,
NotBip,
LengthMismatch,
DestinationMismatch,
RateLimited,
UnknownFunction,
MalformedPayload,
OwnEcho,
GroupOrigin,
UnanswerableSource,
ForwardedUntrusted,
}
impl Discard {
pub(crate) const fn of_decode_error(error: &BvllError) -> Self {
match error {
BvllError::TooShort { .. } => Self::TooShort,
BvllError::NotBip { .. } => Self::NotBip,
BvllError::LengthMismatch { .. } => Self::LengthMismatch,
BvllError::UnknownFunction { .. } => Self::UnknownFunction,
_ => Self::MalformedPayload,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ForBbmd<'d> {
OriginalBroadcast(Delivery<'d>),
Forwarded {
sender: SocketAddrV4,
arrival: Arrival,
origin: SocketAddrV4,
npdu: &'d [u8],
},
Distribute {
sender: SocketAddrV4,
npdu: &'d [u8],
},
Register {
sender: SocketAddrV4,
ttl: u16,
},
ReadBdt {
sender: SocketAddrV4,
},
ReadFdt {
sender: SocketAddrV4,
},
DeleteFdtEntry {
sender: SocketAddrV4,
entry: SocketAddrV4,
},
}
impl<'d> ForBbmd<'d> {
fn of_request(message: Bvll<'d>, sender: SocketAddrV4) -> Option<Self> {
Some(match message {
Bvll::DistributeBroadcastToNetwork(npdu) => Self::Distribute { sender, npdu },
Bvll::RegisterForeignDevice { ttl } => Self::Register { sender, ttl },
Bvll::ReadBroadcastDistributionTable => Self::ReadBdt { sender },
Bvll::ReadForeignDeviceTable => Self::ReadFdt { sender },
Bvll::DeleteForeignDeviceTableEntry(entry) => Self::DeleteFdtEntry { sender, entry },
_ => return None,
})
}
}
fn rate_limited(function: BvlcFunction) -> bool {
matches!(
function,
BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE
| BvlcFunction::READ_FOREIGN_DEVICE_TABLE
| BvlcFunction::REGISTER_FOREIGN_DEVICE
| BvlcFunction::DELETE_FOREIGN_DEVICE_TABLE_ENTRY
)
}
impl Link {
pub fn receive<'d>(
&self,
datagram: &Datagram<'d>,
now: Instant,
policy: &mut impl Policy,
) -> Received<'d> {
let source = datagram.source;
if datagram.truncated {
return Received::Discarded(Discard::Truncated);
}
if self.groups().contains_ip(*source.ip()) {
return Received::Discarded(Discard::GroupSource);
}
let Some(to) = datagram.to else {
return Received::Discarded(Discard::UnknownDestination);
};
let listener = matches!(datagram.path, Path::Listener { .. });
if let Path::Listener { other_interface } = datagram.path {
if !to.broadcast {
return listener_mismatch(false);
}
if other_interface {
return listener_mismatch(true);
}
}
let frame = match BvllFrame::decode(datagram.payload) {
Ok(frame) => frame,
Err(error) => return Received::Discarded(Discard::of_decode_error(&error)),
};
let Some(arrival) = to.arrival(frame.function) else {
return Received::Discarded(Discard::DestinationMismatch);
};
if listener && arrival != Arrival::Broadcast {
return listener_mismatch(false);
}
if rate_limited(frame.function) && !policy.admit_management(source, frame.function, now) {
return Received::Discarded(Discard::RateLimited);
}
match frame.message() {
Ok(message) => self.dispatch(message, source, arrival, policy),
Err(error) => self.malformed(frame.function, error, source),
}
}
fn dispatch<'d>(
&self,
message: Bvll<'d>,
source: SocketAddrV4,
arrival: Arrival,
policy: &mut impl Policy,
) -> Received<'d> {
match message {
Bvll::OriginalUnicastNpdu(npdu) => {
if self.is_own(source) {
return Received::Discarded(Discard::OwnEcho);
}
let group = arrival == Arrival::Broadcast;
Received::Deliver(Delivery {
npdu,
source,
group,
})
}
Bvll::OriginalBroadcastNpdu(npdu) => {
if self.is_own(source) {
return Received::Discarded(Discard::OwnEcho);
}
let delivery = Delivery {
npdu,
source,
group: true,
};
match self.mode {
Mode::Bbmd => Received::Bbmd(ForBbmd::OriginalBroadcast(delivery)),
Mode::Normal | Mode::Foreign(_) => Received::Deliver(delivery),
}
}
Bvll::ForwardedNpdu { origin, npdu } => self.forwarded(source, arrival, origin, npdu),
Bvll::BvlcResult(_) => Received::Response(Response {
peer: source,
function: message.function(),
message: Ok(message),
accepted: true,
}),
Bvll::ReadBroadcastDistributionTableAck(_) | Bvll::ReadForeignDeviceTableAck(_) => {
let accepted = policy.accept_table(&message);
Received::Response(Response {
peer: source,
function: message.function(),
message: Ok(message),
accepted,
})
}
_ => self.request(message, source),
}
}
fn forwarded<'d>(
&self,
sender: SocketAddrV4,
arrival: Arrival,
origin: SocketAddrV4,
npdu: &'d [u8],
) -> Received<'d> {
if self.is_own(origin) {
return Received::Discarded(Discard::OwnEcho);
}
if self.groups().contains(origin) {
return Received::Discarded(Discard::GroupOrigin);
}
match self.mode {
Mode::Bbmd => {
if self.is_own(sender) {
return Received::Discarded(Discard::OwnEcho);
}
Received::Bbmd(ForBbmd::Forwarded {
sender,
arrival,
origin,
npdu,
})
}
Mode::Foreign(foreign)
if foreign.trust == ForwardedTrust::ConfiguredBbmd
&& (sender != foreign.bbmd || arrival != Arrival::Unicast) =>
{
Received::Discarded(Discard::ForwardedUntrusted)
}
Mode::Normal | Mode::Foreign(_) => Received::Deliver(Delivery {
npdu,
source: origin,
group: true,
}),
}
}
fn request<'d>(&self, message: Bvll<'d>, source: SocketAddrV4) -> Received<'d> {
let function = message.function();
let Some(code) = function.nak() else {
return Received::Discarded(Discard::UnknownFunction);
};
if function == BvlcFunction::DISTRIBUTE_BROADCAST_TO_NETWORK && self.is_own(source) {
return Received::Discarded(Discard::OwnEcho);
}
if self.mode == Mode::Bbmd {
if let Some(share) = ForBbmd::of_request(message, source) {
return Received::Bbmd(share);
}
}
if !self.is_answerable(source) {
return Received::Discarded(Discard::UnanswerableSource);
}
Received::Nak {
to: source,
code,
malformed: false,
}
}
fn malformed<'d>(
&self,
function: BvlcFunction,
error: BvllError,
source: SocketAddrV4,
) -> Received<'d> {
if let Some(code) = function.nak() {
if self.is_answerable(source) {
return Received::Nak {
to: source,
code,
malformed: true,
};
}
return Received::Discarded(Discard::MalformedPayload);
}
match function {
BvlcFunction::BVLC_RESULT
| BvlcFunction::READ_BROADCAST_DISTRIBUTION_TABLE_ACK
| BvlcFunction::READ_FOREIGN_DEVICE_TABLE_ACK => Received::Response(Response {
peer: source,
function,
message: Err(error),
accepted: false,
}),
_ => Received::Discarded(Discard::of_decode_error(&error)),
}
}
}
fn listener_mismatch<'d>(other_interface: bool) -> Received<'d> {
Received::Discarded(Discard::ListenerMismatch { other_interface })
}