use core::net::{Ipv4Addr, SocketAddrV4};
use bacnet_bip_codec::{BvlcResultCode, Bvll};
use bacnet_time::Instant;
use core::fmt;
use super::bdt::{Bdt, BdtError, TWO_HOP_MASK, distribution_address};
use super::fdt::Fdt;
use super::nat::Nat;
use crate::{Arrival, LocalSubnet};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Via {
LocalBroadcast,
Peer,
ForeignDevice,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Target {
pub destination: SocketAddrV4,
pub via: Via,
}
impl Target {
pub const UNUSED: Self = Self {
destination: SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
via: Via::LocalBroadcast,
};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Context {
pub now: Instant,
pub fdt_fanout: usize,
}
impl Context {
#[must_use]
pub const fn new(now: Instant, fdt_fanout: usize) -> Self {
Self { now, fdt_fanout }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Fanout<'o> {
pub targets: &'o [Target],
pub deduplicated: usize,
pub fanout_capped: bool,
pub dropped: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Distribution<'n, 'o> {
pub origin: SocketAddrV4,
pub npdu: &'n [u8],
pub fanout: Fanout<'o>,
}
impl<'n, 'o> Distribution<'n, 'o> {
#[must_use]
pub const fn message(&self) -> Bvll<'n> {
Bvll::ForwardedNpdu {
origin: self.origin,
npdu: self.npdu,
}
}
pub fn transmissions(
&self,
) -> impl ExactSizeIterator<Item = (SocketAddrV4, Bvll<'n>)> + use<'n, 'o> {
let message = self.message();
self.fanout
.targets
.iter()
.map(move |target| (target.destination, message))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ForwardRefusal {
NotFromPeer,
OwnEcho,
}
impl ForwardRefusal {
#[must_use]
pub const fn result_code(self) -> Option<BvlcResultCode> {
match self {
Self::NotFromPeer | Self::OwnEcho => None,
}
}
}
impl fmt::Display for ForwardRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::NotFromPeer => "Forwarded-NPDU from a sender that is no BDT peer",
Self::OwnEcho => "Forwarded-NPDU from this BBMD's own address",
})
}
}
impl core::error::Error for ForwardRefusal {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DistributeRefusal {
NotRegistered,
}
impl DistributeRefusal {
#[must_use]
pub const fn result_code(self) -> BvlcResultCode {
match self {
Self::NotRegistered => BvlcResultCode::DISTRIBUTE_BROADCAST_TO_NETWORK_NAK,
}
}
}
impl fmt::Display for DistributeRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::NotRegistered => {
"Distribute-Broadcast-To-Network from a sender with no live registration"
}
})
}
}
impl core::error::Error for DistributeRefusal {}
#[derive(Debug, Clone)]
pub struct Tables<const B: usize, const F: usize> {
pub bdt: Bdt<B>,
pub fdt: Fdt<F>,
nat: Option<Nat>,
}
impl<const B: usize, const F: usize> Tables<B, F> {
pub const MAX_TARGETS: usize = B + F + 1;
#[must_use]
pub const fn new(own: SocketAddrV4) -> Self {
Self {
bdt: Bdt::new(own),
fdt: Fdt::new(),
nat: None,
}
}
#[must_use]
pub const fn nat(&self) -> Option<&Nat> {
self.nat.as_ref()
}
pub fn set_nat(&mut self, nat: Option<Nat>, local: SocketAddrV4) -> Result<(), BdtError> {
let own = Self::own_address_for(nat.as_ref(), local)?;
let drop = self.leaving_nat(nat.as_ref());
self.bdt.reconfigure(own, nat.is_some(), drop)?;
self.nat = nat;
Ok(())
}
fn leaving_nat(&self, nat: Option<&Nat>) -> Option<SocketAddrV4> {
match (&self.nat, nat) {
(Some(old), None) => Some(old.global),
_ => None,
}
}
pub fn set_nat_and_bdt(
&mut self,
nat: Option<Nat>,
local: SocketAddrV4,
table: bacnet_bip_codec::BdtEntries<'_>,
) -> Result<(), BdtError> {
let own = Self::own_address_for(nat.as_ref(), local)?;
let mut bdt = self.bdt.with_nat(own, nat.is_some());
bdt.set_without(table, self.leaving_nat(nat.as_ref()))?;
self.bdt = bdt;
self.nat = nat;
Ok(())
}
fn own_address_for(nat: Option<&Nat>, local: SocketAddrV4) -> Result<SocketAddrV4, BdtError> {
match nat {
Some(nat) => {
let entry = bacnet_bip_codec::BdtEntry::new(nat.global, TWO_HOP_MASK);
super::validate_entry(entry)
.map_err(|reason| BdtError::Invalid { entry, reason })?;
Ok(nat.global)
}
None => Ok(local),
}
}
fn origin_for(&self, source: SocketAddrV4, private: impl FnOnce(&Nat) -> bool) -> SocketAddrV4 {
match &self.nat {
Some(nat) if private(nat) => nat.global,
_ => source,
}
}
pub fn broadcast_targets<'o>(
&self,
cx: &Context,
exclude: SocketAddrV4,
out: &'o mut [Target],
) -> Fanout<'o> {
let mut writer = Writer::new(out);
self.peer_targets(&mut writer, exclude);
self.foreign_device_targets_into(&mut writer, cx, exclude);
writer.finish()
}
pub fn foreign_device_targets<'o>(
&self,
cx: &Context,
exclude: SocketAddrV4,
out: &'o mut [Target],
) -> Fanout<'o> {
let mut writer = Writer::new(out);
self.foreign_device_targets_into(&mut writer, cx, exclude);
writer.finish()
}
pub fn original_broadcast<'n, 'o>(
&self,
cx: &Context,
source: SocketAddrV4,
npdu: &'n [u8],
out: &'o mut [Target],
) -> Distribution<'n, 'o> {
Distribution {
origin: self.origin_for(source, |_| true),
npdu,
fanout: self.broadcast_targets(cx, source, out),
}
}
pub fn own_broadcast<'n, 'o>(
&self,
cx: &Context,
npdu: &'n [u8],
out: &'o mut [Target],
) -> Distribution<'n, 'o> {
self.original_broadcast(cx, self.bdt.own_address(), npdu, out)
}
pub fn forwarded_npdu<'o>(
&self,
cx: &Context,
subnet: &LocalSubnet,
sender: SocketAddrV4,
arrival: Arrival,
origin: SocketAddrV4,
out: &'o mut [Target],
) -> Result<Fanout<'o>, ForwardRefusal> {
if sender == self.bdt.own_address() {
return Err(ForwardRefusal::OwnEcho);
}
if !self.bdt.contains(sender) {
return Err(ForwardRefusal::NotFromPeer);
}
let mut writer = Writer::new(out);
let mask = subnet.mask.filter(|_| !subnet.is_group());
if !subnet.alone && self.rebroadcasts_forwarded(mask, sender, arrival) {
writer.push_local(subnet.broadcast);
}
self.foreign_device_targets_into(&mut writer, cx, origin);
Ok(writer.finish())
}
#[must_use]
pub fn rebroadcasts_forwarded(
&self,
subnet_mask: Option<Ipv4Addr>,
sender: SocketAddrV4,
arrival: Arrival,
) -> bool {
let own = self.bdt.own_address();
let peers_send_here = self.bdt.row(own).is_none_or(|row| row.mask == TWO_HOP_MASK);
let sender_on_subnet = self.nat.is_none()
&& sender.port() == own.port()
&& subnet_mask.is_some_and(|mask| {
(sender.ip().to_bits() ^ own.ip().to_bits()) & mask.to_bits() == 0
});
peers_send_here && arrival == Arrival::Unicast && !sender_on_subnet
}
pub fn distribute_broadcast<'n, 'o>(
&self,
cx: &Context,
subnet: &LocalSubnet,
sender: SocketAddrV4,
npdu: &'n [u8],
out: &'o mut [Target],
) -> Result<Distribution<'n, 'o>, DistributeRefusal> {
if !self.fdt.is_registered_at(sender, cx.now) {
return Err(DistributeRefusal::NotRegistered);
}
let mut writer = Writer::new(out);
if !subnet.alone {
writer.push_local(subnet.broadcast);
}
self.peer_targets(&mut writer, sender);
self.foreign_device_targets_into(&mut writer, cx, sender);
Ok(Distribution {
origin: self.origin_for(sender, |nat| nat.is_private(*sender.ip())),
npdu,
fanout: writer.finish(),
})
}
fn peer_targets(&self, writer: &mut Writer<'_>, exclude: SocketAddrV4) {
let own = self.bdt.own_address();
for row in self.bdt.rows() {
if row.address == own || row.address == exclude {
continue;
}
writer.push_unique(distribution_address(*row), Via::Peer);
}
writer.peers_end = writer.len;
}
fn foreign_device_targets_into(
&self,
writer: &mut Writer<'_>,
cx: &Context,
exclude: SocketAddrV4,
) {
let mut sent = 0;
for entry in self.fdt.live_at(cx.now) {
if entry.address == exclude {
continue;
}
if writer.is_peer_target(entry.address) {
writer.deduplicated += 1;
continue;
}
if sent >= cx.fdt_fanout {
writer.fanout_capped = true;
break;
}
writer.push(entry.address, Via::ForeignDevice);
sent += 1;
}
}
}
struct Writer<'o> {
out: &'o mut [Target],
len: usize,
peers_start: usize,
peers_end: usize,
deduplicated: usize,
fanout_capped: bool,
dropped: usize,
}
impl<'o> Writer<'o> {
fn new(out: &'o mut [Target]) -> Self {
Self {
out,
len: 0,
peers_start: 0,
peers_end: 0,
deduplicated: 0,
fanout_capped: false,
dropped: 0,
}
}
fn push(&mut self, destination: SocketAddrV4, via: Via) {
match self.out.get_mut(self.len) {
Some(slot) => {
*slot = Target { destination, via };
self.len += 1;
}
None => self.dropped += 1,
}
}
fn push_local(&mut self, destination: SocketAddrV4) {
self.push(destination, Via::LocalBroadcast);
self.peers_start = self.len;
self.peers_end = self.len;
}
fn push_unique(&mut self, destination: SocketAddrV4, via: Via) {
if self.out[self.peers_start..self.len]
.iter()
.any(|target| target.destination == destination)
{
self.deduplicated += 1;
} else {
self.push(destination, via);
}
}
fn is_peer_target(&self, destination: SocketAddrV4) -> bool {
self.out[self.peers_start..self.peers_end]
.iter()
.any(|target| target.destination == destination)
}
fn finish(self) -> Fanout<'o> {
let Self {
out,
len,
deduplicated,
fanout_capped,
dropped,
..
} = self;
Fanout {
targets: &out[..len],
deduplicated,
fanout_capped,
dropped,
}
}
}