use core::net::SocketAddrV4;
use bacnet_bip_codec::Bvll;
use bacnet_time::Instant;
use super::{Datagrams, HeldBroadcast, Ingress, PortCounters, PortError, count};
use crate::node::{
Action, Admission, Cast, Datagram, Foreign, Inbound, Link, LocalFailure, Mode, Node, Policy,
SendRefusal,
};
use bacnet_npdu::MAX_NPDU;
#[derive(Debug)]
pub struct NodePort<'h, const X: usize = 4, const N: usize = MAX_NPDU> {
node: Node<X>,
held: &'h mut [HeldBroadcast<N>],
held_len: usize,
counters: PortCounters,
}
impl<'h, const X: usize, const N: usize> NodePort<'h, X, N> {
pub fn new(node: Node<X>, held: &'h mut [HeldBroadcast<N>]) -> Result<Self, PortError> {
check_hold::<N>(node.link(), held.len())?;
Ok(Self {
node,
held,
held_len: 0,
counters: PortCounters::default(),
})
}
#[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(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 retarget(
&mut self,
foreign: Foreign,
now: Instant,
out: &mut impl Datagrams,
) -> Result<(), PortError> {
let mut link = *self.node.link();
link.mode = Mode::Foreign(foreign);
check_hold::<N>(&link, self.held.len())?;
self.drain(now, out);
self.node.retarget(foreign, now).map_err(PortError::Link)?;
self.drain(now, out);
Ok(())
}
pub fn unicast(
&mut self,
to: SocketAddrV4,
npdu: &[u8],
now: Instant,
out: &mut impl Datagrams,
) -> Result<(), SendRefusal> {
self.on_deadline(now, out);
let outgoing = self.node.link().unicast(to, npdu)?;
out.send(outgoing.to, &outgoing.message)
}
pub fn broadcast(
&mut self,
npdu: &[u8],
now: Instant,
out: &mut impl Datagrams,
) -> Result<Admission, SendRefusal> {
self.on_deadline(now, out);
let result = self.admit(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 admit(
&mut self,
npdu: &[u8],
now: Instant,
out: &mut impl Datagrams,
) -> Result<Admission, SendRefusal> {
let (outgoing, _) = self.node.link().broadcast(npdu)?;
let admission = self.node.admit_broadcast(now)?;
match admission {
Admission::Send => out.send(outgoing.to, &outgoing.message)?,
Admission::Hold => {
let slot = self
.held
.get_mut(self.held_len)
.ok_or(SendRefusal::HoldFull)?;
let room = slot
.npdu
.get_mut(..npdu.len())
.ok_or(SendRefusal::TooLong { max: N })?;
room.copy_from_slice(npdu);
slot.len = npdu.len() as u16;
self.held_len += 1;
}
}
Ok(admission)
}
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)
.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(_) => {}
}
}
}
}
fn check_hold<const N: usize>(link: &Link, available: usize) -> Result<(), PortError> {
let hold = match link.mode {
Mode::Normal => 0,
Mode::Foreign(foreign) => usize::from(foreign.hold),
Mode::Bbmd => return Err(PortError::Mode),
};
if hold > available {
return Err(PortError::HeldStorage {
needed: hold,
available,
});
}
let longest = link.max_npdu(Cast::Broadcast);
if hold > 0 && longest > N {
return Err(PortError::HeldLength {
needed: longest,
available: N,
});
}
Ok(())
}