use core::fmt;
use core::net::SocketAddrV4;
use bacnet_bip_codec::{address_from_slice, address_to_octets, Bvll};
use bacnet_bip_core::bbmd::{Context, ForwardRefusal, Tables, Target};
use bacnet_bip_core::node::{
self, Action, Admission, Cast, Datagram, Delivery, Discard, ForBbmd, Inbound, Link,
LocalFailure, Mode, Node, Policy,
};
use crate::router::{Dest, Group, Limits, PortConfig, Send, SendRefusal};
use crate::{Arrived, Instant, Mac, PortId, MAX_NPDU};
pub trait Datagrams {
fn send(&mut self, to: SocketAddrV4, message: &Bvll<'_>) -> Result<(), SendRefusal>;
}
#[must_use]
pub fn port_config(link: &Link, network: u16) -> PortConfig {
let limit = |cast| link.max_npdu(cast).min(MAX_NPDU) as u16;
PortConfig {
network,
mac: Mac::from_array(address_to_octets(link.address)),
max_npdu: Limits {
unicast: limit(Cast::Unicast),
broadcast: limit(Cast::Broadcast),
},
}
}
#[must_use]
pub fn group(link: &Link, mac: &[u8]) -> Group {
match address_from_slice(mac) {
Ok(address) if link.groups().contains(address) => {
if address.port() == link.address.port() {
Group::Broadcast
} else {
Group::Refuse
}
}
_ => Group::No,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ingress<'d> {
Npdu(Arrived<'d>),
Handled,
Discarded(Discard),
NotForwarded(ForwardRefusal),
Management(ForBbmd<'d>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum PortError {
Mode,
HeldStorage {
needed: usize,
available: usize,
},
HeldLength {
needed: usize,
available: usize,
},
TargetStorage {
needed: usize,
available: usize,
},
}
impl fmt::Display for PortError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Mode => f.write_str("the node's mode doesn't suit this port"),
Self::HeldStorage { needed, available } => write!(
f,
"the node holds {needed} broadcasts, with slots for {available}"
),
Self::HeldLength { needed, available } => write!(
f,
"a held broadcast may be {needed} octets, with slots of {available}"
),
Self::TargetStorage { needed, available } => write!(
f,
"a BBMD forwards to up to {needed} targets, with room for {available}"
),
}
}
}
impl core::error::Error for PortError {}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct PortCounters {
pub refused: u32,
}
#[derive(Clone)]
pub struct HeldBroadcast<const N: usize = MAX_NPDU> {
len: u16,
npdu: [u8; N],
}
impl<const N: usize> HeldBroadcast<N> {
pub const EMPTY: Self = {
assert!(
N <= u16::MAX as usize,
"a held broadcast is at most 65,535 octets"
);
Self {
len: 0,
npdu: [0; N],
}
};
}
impl<const N: usize> Default for HeldBroadcast<N> {
fn default() -> Self {
Self::EMPTY
}
}
impl<const N: usize> fmt::Debug for HeldBroadcast<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HeldBroadcast")
.field("len", &self.len)
.finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct BipPort<'h, const X: usize = 4, const N: usize = MAX_NPDU> {
port: PortId,
node: Node<X>,
held: &'h mut [HeldBroadcast<N>],
held_len: usize,
counters: PortCounters,
}
impl<'h, const X: usize, const N: usize> BipPort<'h, X, N> {
pub fn new(
port: PortId,
node: Node<X>,
held: &'h mut [HeldBroadcast<N>],
) -> Result<Self, PortError> {
let link = node.link();
let hold = match link.mode {
Mode::Normal => 0,
Mode::Foreign(foreign) => usize::from(foreign.hold),
Mode::Bbmd => return Err(PortError::Mode),
};
if hold > held.len() {
return Err(PortError::HeldStorage {
needed: hold,
available: held.len(),
});
}
let longest = link.max_npdu(Cast::Broadcast);
if hold > 0 && longest > N {
return Err(PortError::HeldLength {
needed: longest,
available: N,
});
}
Ok(Self {
port,
node,
held,
held_len: 0,
counters: PortCounters::default(),
})
}
#[must_use]
pub const fn port(&self) -> PortId {
self.port
}
#[must_use]
pub fn config(&self, network: u16) -> PortConfig {
port_config(self.node.link(), network)
}
#[must_use]
pub fn group(&self, mac: &[u8]) -> Group {
group(self.node.link(), mac)
}
#[must_use]
pub const fn node(&self) -> &Node<X> {
&self.node
}
pub fn node_mut(&mut self) -> &mut Node<X> {
&mut self.node
}
#[must_use = "the counts are gone once taken; add them to your totals"]
pub fn take_counters(&mut self) -> PortCounters {
core::mem::take(&mut self.counters)
}
#[must_use]
pub const fn counters(&self) -> PortCounters {
self.counters
}
pub fn receive<'d>(
&mut self,
datagram: &Datagram<'d>,
now: Instant,
policy: &mut impl Policy,
out: &mut impl Datagrams,
) -> Ingress<'d> {
self.drain(now, out);
let ingress = match self.node.receive(datagram, now, policy) {
Inbound::Deliver(delivery) => Ingress::Npdu(arrived(self.port, &delivery)),
Inbound::Nak { to, code, .. } => {
count(&mut self.counters, out.send(to, &Bvll::BvlcResult(code)));
Ingress::Handled
}
Inbound::Discarded(why) => Ingress::Discarded(why),
Inbound::Bbmd(_)
| Inbound::Matched { .. }
| Inbound::Duplicate { .. }
| Inbound::Unmatched { .. } => Ingress::Handled,
};
self.drain(now, out);
ingress
}
pub fn start(&mut self, now: Instant, out: &mut impl Datagrams) {
self.on_deadline(now, out);
}
pub fn send(
&mut self,
send: &Send<'_>,
now: Instant,
out: &mut impl Datagrams,
) -> Result<(), SendRefusal> {
if send.port != self.port {
return Err(SendRefusal::Other);
}
self.on_deadline(now, out);
let result = match send.to {
Dest::Unicast(mac) => unicast(self.node.link(), mac, send.npdu, out),
Dest::Broadcast => self.broadcast(send.npdu, now, out),
};
self.drain(now, out);
result
}
#[must_use]
pub fn next_deadline(&self) -> Option<Instant> {
self.node.next_deadline()
}
pub fn on_deadline(&mut self, now: Instant, out: &mut impl Datagrams) {
self.node.on_deadline(now);
self.drain(now, out);
}
fn broadcast(
&mut self,
npdu: &[u8],
now: Instant,
out: &mut impl Datagrams,
) -> Result<(), SendRefusal> {
let (outgoing, _) = self.node.link().broadcast(npdu).map_err(refusal)?;
match self.node.admit_broadcast(now).map_err(refusal)? {
Admission::Send => out.send(outgoing.to, &outgoing.message),
Admission::Hold => {
let slot = self.held.get_mut(self.held_len).ok_or(SendRefusal::Full)?;
let room = slot
.npdu
.get_mut(..npdu.len())
.ok_or(SendRefusal::TooLong)?;
room.copy_from_slice(npdu);
slot.len = npdu.len() as u16;
self.held_len += 1;
Ok(())
}
}
}
fn drain(&mut self, now: Instant, out: &mut impl Datagrams) {
loop {
match self.node.poll_action() {
Action::Idle => return,
Action::Register { exchange, to, ttl } => {
if out.send(to, &Bvll::RegisterForeignDevice { ttl }).is_ok() {
self.node.exchange_sent(exchange, now);
} else {
let failure = LocalFailure::SendFailed;
self.node.exchange_failed(exchange, failure, now);
}
}
Action::ReleaseHeld => {
let link = *self.node.link();
for slot in &self.held[..self.held_len] {
let npdu = &slot.npdu[..usize::from(slot.len)];
let sent = link
.broadcast(npdu)
.map_err(refusal)
.and_then(|(outgoing, _)| out.send(outgoing.to, &outgoing.message));
count(&mut self.counters, sent);
}
self.held_len = 0;
}
Action::DiscardHeld => self.held_len = 0,
Action::ExchangeEnded { .. } | Action::Registration(_) => {}
}
}
}
}
#[derive(Debug)]
pub struct BbmdPort<'t, const B: usize, const F: usize, const X: usize = 4> {
port: PortId,
node: Node<X>,
tables: &'t mut Tables<B, F>,
targets: &'t mut [Target],
counters: PortCounters,
latest: Instant,
}
impl<'t, const B: usize, const F: usize, const X: usize> BbmdPort<'t, B, F, X> {
pub fn new(
port: PortId,
node: Node<X>,
tables: &'t mut Tables<B, F>,
targets: &'t mut [Target],
) -> Result<Self, PortError> {
if node.link().mode != Mode::Bbmd {
return Err(PortError::Mode);
}
if targets.len() < Tables::<B, F>::MAX_TARGETS {
return Err(PortError::TargetStorage {
needed: Tables::<B, F>::MAX_TARGETS,
available: targets.len(),
});
}
Ok(Self {
port,
node,
tables,
targets,
counters: PortCounters::default(),
latest: Instant::ZERO,
})
}
fn clock(&mut self, now: Instant) -> Instant {
self.latest = self.latest.max(now);
self.latest
}
#[must_use]
pub const fn port(&self) -> PortId {
self.port
}
#[must_use]
pub fn config(&self, network: u16) -> PortConfig {
port_config(self.node.link(), network)
}
#[must_use]
pub fn group(&self, mac: &[u8]) -> Group {
group(self.node.link(), mac)
}
#[must_use]
pub const fn node(&self) -> &Node<X> {
&self.node
}
pub fn node_mut(&mut self) -> &mut Node<X> {
&mut self.node
}
#[must_use]
pub fn tables(&self) -> &Tables<B, F> {
self.tables
}
pub fn tables_mut(&mut self) -> &mut Tables<B, F> {
self.tables
}
#[must_use = "the counts are gone once taken; add them to your totals"]
pub fn take_counters(&mut self) -> PortCounters {
core::mem::take(&mut self.counters)
}
#[must_use]
pub const fn counters(&self) -> PortCounters {
self.counters
}
pub fn receive<'d>(
&mut self,
datagram: &Datagram<'d>,
now: Instant,
policy: &mut impl Policy,
out: &mut impl Datagrams,
) -> Ingress<'d> {
let now = self.clock(now);
let link = *self.node.link();
let ingress = match self.node.receive(datagram, now, policy) {
Inbound::Deliver(delivery) => Ingress::Npdu(arrived(self.port, &delivery)),
Inbound::Nak { to, code, .. } => {
count(&mut self.counters, out.send(to, &Bvll::BvlcResult(code)));
Ingress::Handled
}
Inbound::Discarded(why) => Ingress::Discarded(why),
Inbound::Bbmd(share) => self.share(share, &link, now, out),
Inbound::Matched { .. } | Inbound::Duplicate { .. } | Inbound::Unmatched { .. } => {
Ingress::Handled
}
};
self.drain();
ingress
}
fn share<'d>(
&mut self,
share: ForBbmd<'d>,
link: &Link,
now: Instant,
out: &mut impl Datagrams,
) -> Ingress<'d> {
let cx = Context::new(now, F);
let port = self.port;
let (source, npdu) = match share {
ForBbmd::OriginalBroadcast(delivery) => {
let sent = self.tables.original_broadcast(
&cx,
delivery.source,
delivery.npdu,
self.targets,
);
add(&mut self.counters, fan_out(out, sent.transmissions()));
return Ingress::Npdu(arrived(port, &delivery));
}
ForBbmd::Forwarded {
sender,
arrival,
origin,
npdu,
} => {
let targets = self.tables.forwarded_npdu(
&cx,
&link.subnet,
sender,
arrival,
origin,
self.targets,
);
let fanout = match targets {
Ok(fanout) => fanout,
Err(why) => return Ingress::NotForwarded(why),
};
let message = Bvll::ForwardedNpdu { origin, npdu };
let copies = fanout
.targets
.iter()
.map(|target| (target.destination, message));
add(&mut self.counters, fan_out(out, copies));
(origin, npdu)
}
ForBbmd::Distribute { sender, npdu } => {
let distribution =
self.tables
.distribute_broadcast(&cx, &link.subnet, sender, npdu, self.targets);
match distribution {
Ok(sent) => add(&mut self.counters, fan_out(out, sent.transmissions())),
Err(why) => {
let nak = Bvll::BvlcResult(why.result_code());
count(&mut self.counters, out.send(sender, &nak));
return Ingress::Handled;
}
}
(sender, npdu)
}
management => return Ingress::Management(management),
};
Ingress::Npdu(Arrived {
port,
source: Mac::from_array(address_to_octets(source)),
link_group: true,
npdu,
})
}
pub fn send(
&mut self,
send: &Send<'_>,
now: Instant,
out: &mut impl Datagrams,
) -> Result<(), SendRefusal> {
if send.port != self.port {
return Err(SendRefusal::Other);
}
let now = self.clock(now);
let link = self.node.link();
match send.to {
Dest::Unicast(mac) => unicast(link, mac, send.npdu, out),
Dest::Broadcast => {
let (outgoing, _) = link.broadcast(send.npdu).map_err(refusal)?;
out.send(outgoing.to, &outgoing.message)?;
let cx = Context::new(now, F);
let sent = self.tables.own_broadcast(&cx, send.npdu, self.targets);
add(&mut self.counters, fan_out(out, sent.transmissions()));
Ok(())
}
}
}
#[must_use]
pub fn next_deadline(&self) -> Option<Instant> {
self.node.next_deadline()
}
pub fn on_deadline(&mut self, now: Instant) {
let now = self.clock(now);
self.node.on_deadline(now);
self.drain();
}
fn drain(&mut self) {
while self.node.poll_action() != Action::Idle {}
}
}
fn arrived<'d>(port: PortId, delivery: &Delivery<'d>) -> Arrived<'d> {
Arrived {
port,
source: Mac::from_array(address_to_octets(delivery.source)),
link_group: delivery.group,
npdu: delivery.npdu,
}
}
fn unicast(
link: &Link,
mac: &[u8],
npdu: &[u8],
out: &mut impl Datagrams,
) -> Result<(), SendRefusal> {
let to = address_from_slice(mac).map_err(|_| SendRefusal::Other)?;
let outgoing = link.unicast(to, npdu).map_err(refusal)?;
out.send(outgoing.to, &outgoing.message)
}
fn refusal(why: node::SendRefusal) -> SendRefusal {
match why {
node::SendRefusal::TooLong { .. } => SendRefusal::TooLong,
node::SendRefusal::HoldFull => SendRefusal::Full,
_ => SendRefusal::Other,
}
}
fn fan_out<'n>(
out: &mut impl Datagrams,
copies: impl Iterator<Item = (SocketAddrV4, Bvll<'n>)>,
) -> u32 {
copies.fold(0, |refused: u32, (to, message)| {
refused.saturating_add(u32::from(out.send(to, &message).is_err()))
})
}
fn add(counters: &mut PortCounters, n: u32) {
counters.refused = counters.refused.saturating_add(n);
}
fn count(counters: &mut PortCounters, sent: Result<(), SendRefusal>) {
add(counters, u32::from(sent.is_err()));
}