use core::net::{Ipv4Addr, SocketAddrV4};
use bacnet_bip_codec::{BvlcResultCode, Bvll};
use bacnet_time::Instant;
use core::fmt;
use super::bdt::{distribution_address, Bdt, TWO_HOP_MASK};
use super::fdt::Fdt;
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>,
}
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(),
}
}
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: source,
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);
if self.rebroadcasts_forwarded(subnet.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 = 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);
writer.push_local(subnet.broadcast);
self.peer_targets(&mut writer, sender);
self.foreign_device_targets_into(&mut writer, cx, sender);
Ok(Distribution {
origin: sender,
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,
}
}
}