use core::net::SocketAddrV4;
use bacnet_bip_codec::{Bvll, FdtEntry};
use bacnet_time::Instant;
use super::{Datagrams, Ingress, PortCounters, PortError, add, count, fan_out};
use crate::bbmd::{Context, Rules, Tables, Target};
use crate::node::{
Datagram, Delivery, Discard, ForBbmd, Link, Mode, Policy, Received, SendRefusal,
};
#[derive(Debug)]
pub struct BbmdPort<'t, const B: usize, const F: usize> {
link: Link,
tables: &'t mut Tables<B, F>,
targets: &'t mut [Target],
rows: &'t mut [FdtEntry],
counters: PortCounters,
latest: Instant,
}
impl<'t, const B: usize, const F: usize> BbmdPort<'t, B, F> {
pub fn new(
link: Link,
tables: &'t mut Tables<B, F>,
targets: &'t mut [Target],
rows: &'t mut [FdtEntry],
) -> Result<Self, PortError> {
if link.mode != Mode::Bbmd {
return Err(PortError::Mode);
}
link.validate().map_err(PortError::Link)?;
if targets.len() < Tables::<B, F>::MAX_TARGETS {
return Err(PortError::TargetStorage {
needed: Tables::<B, F>::MAX_TARGETS,
available: targets.len(),
});
}
if rows.len() < F {
return Err(PortError::RowStorage {
needed: F,
available: rows.len(),
});
}
tables
.bdt
.set_serves_group(link.subnet.is_group())
.map_err(PortError::Bdt)?;
Ok(Self {
link,
tables,
targets,
rows,
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 link(&self) -> &Link {
&self.link
}
#[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 + Rules),
out: &mut impl Datagrams,
) -> Ingress<'d> {
let now = self.clock(now);
let link = self.link;
match link.receive(datagram, now, policy) {
Received::Deliver(delivery) => Ingress::Npdu(delivery),
Received::Nak { to, code, .. } => {
count(&mut self.counters, out.send(to, &Bvll::BvlcResult(code)));
Ingress::Handled
}
Received::Discarded(why) => Ingress::Discarded(why),
Received::Bbmd(share) => self.share(share, &link, now, policy, out),
Received::Response(_) => Ingress::Handled,
}
}
fn share<'d>(
&mut self,
share: ForBbmd<'d>,
link: &Link,
now: Instant,
rules: &mut impl Rules,
out: &mut impl Datagrams,
) -> Ingress<'d> {
debug_assert_eq!(
self.tables.bdt.serves_group(),
link.subnet.is_group(),
"the BDT's group rule no longer matches the link"
);
let cx = Context::new(now, F);
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(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)
}
ForBbmd::Management(request) => {
if !link.is_answerable(request.sender()) {
return Ingress::Discarded(Discard::UnanswerableSource);
}
let answer = self.tables.serve(request, now, rules, self.rows);
count(&mut self.counters, out.send(answer.to, &answer.message));
return Ingress::Served(answer.served);
}
};
Ingress::Npdu(Delivery {
npdu,
source,
group: true,
})
}
pub fn unicast(
&mut self,
to: SocketAddrV4,
npdu: &[u8],
now: Instant,
out: &mut impl Datagrams,
) -> Result<(), SendRefusal> {
let _ = self.clock(now);
let outgoing = self.link.unicast(to, npdu)?;
out.send(outgoing.to, &outgoing.message)
}
pub fn broadcast(
&mut self,
npdu: &[u8],
now: Instant,
out: &mut impl Datagrams,
) -> Result<(), SendRefusal> {
let now = self.clock(now);
let (outgoing, _) = self.link.broadcast(npdu)?;
out.send(outgoing.to, &outgoing.message)?;
let cx = Context::new(now, F);
let sent = self.tables.own_broadcast(&cx, npdu, self.targets);
add(&mut self.counters, fan_out(out, sent.transmissions()));
Ok(())
}
}