use core::fmt;
use core::net::{Ipv4Addr, SocketAddrV4};
use bacnet_bip_codec::{BdtEntries, BdtEntry};
pub const TWO_HOP_MASK: Ipv4Addr = Ipv4Addr::BROADCAST;
const EMPTY_ROW: BdtEntry = BdtEntry::new(
SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
Ipv4Addr::UNSPECIFIED,
);
#[must_use]
pub const fn distribution_address(row: BdtEntry) -> SocketAddrV4 {
let ip = row.address.ip().to_bits() | !row.mask.to_bits();
SocketAddrV4::new(Ipv4Addr::from_bits(ip), row.address.port())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EntryError {
ZeroPort,
UnspecifiedAddress,
LimitedBroadcastAddress,
MulticastAddress,
NonContiguousMask,
MaskTooWide,
NetworkAddress,
DirectedBroadcastAddress,
LimitedBroadcastTarget,
}
impl EntryError {
#[must_use]
pub const fn reason(self) -> &'static str {
match self {
Self::ZeroPort => "invalid port 0",
Self::UnspecifiedAddress => "unspecified IP",
Self::LimitedBroadcastAddress => "limited-broadcast",
Self::MulticastAddress => "multicast IP",
Self::NonContiguousMask => "non-contiguous mask",
Self::MaskTooWide => "mask prefix too wide (< /8)",
Self::NetworkAddress => "subnet network address",
Self::DirectedBroadcastAddress => "subnet directed-broadcast",
Self::LimitedBroadcastTarget => "limited-broadcast target",
}
}
}
impl fmt::Display for EntryError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.reason())
}
}
impl core::error::Error for EntryError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BdtError {
Invalid {
entry: BdtEntry,
reason: EntryError,
},
ConflictingMasks {
entry: BdtEntry,
},
OneHopBehindNat {
entry: BdtEntry,
},
OneHopForGroup {
entry: BdtEntry,
},
NoOwnRow {
own: SocketAddrV4,
},
Capacity {
needed: usize,
available: usize,
},
}
impl fmt::Display for BdtError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Invalid { entry, reason } => {
write!(f, "BDT row {} mask {}: {reason}", entry.address, entry.mask)
}
Self::ConflictingMasks { entry } => write!(
f,
"BDT rows for {} with conflicting masks (one is {})",
entry.address, entry.mask
),
Self::OneHopBehindNat { entry } => write!(
f,
"BDT row {} mask {}: behind a NAT router every mask must be all ones",
entry.address, entry.mask
),
Self::OneHopForGroup { entry } => write!(
f,
"BDT row {} mask {}: a BBMD that serves a multicast group needs an all-ones mask \
on its own row",
entry.address, entry.mask
),
Self::NoOwnRow { own } => write!(
f,
"behind a NAT router the BDT must list the global address {own} as this BBMD's own row"
),
Self::Capacity { needed, available } => {
write!(
f,
"BDT of {needed} rows exceeds the capacity of {available}"
)
}
}
}
}
impl core::error::Error for BdtError {}
pub const fn validate_entry(entry: BdtEntry) -> Result<(), EntryError> {
let ip = entry.address.ip().to_bits();
let mask = entry.mask.to_bits();
let host = !mask;
let contiguous = host & host.wrapping_add(1) == 0;
let prefix = mask.count_ones();
let first_octet = ip >> 24;
let reason = if entry.address.port() == 0 {
EntryError::ZeroPort
} else if ip == 0 {
EntryError::UnspecifiedAddress
} else if ip == u32::MAX {
EntryError::LimitedBroadcastAddress
} else if matches!(first_octet, 224..=239) {
EntryError::MulticastAddress
} else if !contiguous {
EntryError::NonContiguousMask
} else if prefix < 8 {
EntryError::MaskTooWide
} else if prefix < 32 && ip & host == 0 {
EntryError::NetworkAddress
} else if prefix < 32 && ip | mask == u32::MAX {
EntryError::DirectedBroadcastAddress
} else if ip | host == u32::MAX {
EntryError::LimitedBroadcastTarget
} else {
return Ok(());
};
Err(reason)
}
struct Canonical {
stored: usize,
distinct: usize,
}
fn canonical_rows(table: BdtEntries<'_>, out: &mut [BdtEntry]) -> Result<Canonical, BdtError> {
let mut stored = 0;
let mut distinct = 0;
let mut overflow_from = None;
for (index, entry) in table.iter().enumerate() {
if let Err(reason) = validate_entry(entry) {
return Err(BdtError::Invalid { entry, reason });
}
let earlier = match overflow_from {
None => out[..stored]
.iter()
.find(|row| row.address == entry.address)
.copied(),
Some(from) => out[..stored]
.iter()
.copied()
.chain(table.iter().take(index).skip(from))
.find(|row| row.address == entry.address),
};
match earlier {
Some(row) if row.mask != entry.mask => {
return Err(BdtError::ConflictingMasks { entry });
}
Some(_) => {}
None => {
distinct += 1;
match out.get_mut(stored) {
Some(slot) if overflow_from.is_none() => {
*slot = entry;
stored += 1;
}
_ => {
overflow_from.get_or_insert(index);
}
}
}
}
}
Ok(Canonical { stored, distinct })
}
pub fn canonicalize(table: BdtEntries<'_>, out: &mut [BdtEntry]) -> Result<usize, BdtError> {
let found = canonical_rows(table, out)?;
if found.distinct > found.stored {
return Err(BdtError::Capacity {
needed: found.distinct,
available: out.len(),
});
}
Ok(found.stored)
}
#[derive(Clone)]
pub struct Bdt<const N: usize> {
rows: [BdtEntry; N],
len: usize,
own: SocketAddrV4,
own_row_appended: bool,
behind_nat: bool,
serves_group: bool,
}
impl<const N: usize> fmt::Debug for Bdt<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bdt")
.field("own", &self.own)
.field("rows", &self.rows())
.field("own_row_appended", &self.own_row_appended)
.field("behind_nat", &self.behind_nat)
.field("serves_group", &self.serves_group)
.finish()
}
}
impl<const N: usize> Bdt<N> {
pub const CAPACITY: usize = N;
#[must_use]
pub const fn new(own: SocketAddrV4) -> Self {
Self {
rows: [EMPTY_ROW; N],
len: 0,
own,
own_row_appended: false,
behind_nat: false,
serves_group: false,
}
}
pub(super) const fn with_nat(&self, own: SocketAddrV4, behind_nat: bool) -> Self {
let mut bdt = Self::new(own);
bdt.behind_nat = behind_nat;
bdt.serves_group = self.serves_group;
bdt
}
#[must_use]
pub const fn serves_group(&self) -> bool {
self.serves_group
}
pub fn set_serves_group(&mut self, serves: bool) -> Result<(), BdtError> {
if serves {
Self::check_group_row(self.own, self.rows().iter().copied())?;
}
self.serves_group = serves;
Ok(())
}
fn check_group_row(
own: SocketAddrV4,
mut rows: impl Iterator<Item = BdtEntry>,
) -> Result<(), BdtError> {
match rows.find(|row| row.address == own && row.mask != TWO_HOP_MASK) {
Some(entry) => Err(BdtError::OneHopForGroup { entry }),
None => Ok(()),
}
}
#[must_use]
pub const fn behind_nat(&self) -> bool {
self.behind_nat
}
#[must_use]
pub const fn own_address(&self) -> SocketAddrV4 {
self.own
}
#[must_use]
pub fn entries(&self) -> BdtEntries<'_> {
BdtEntries::from_entries(self.rows())
}
#[must_use]
pub fn rows(&self) -> &[BdtEntry] {
&self.rows[..self.len]
}
#[must_use]
pub fn configured_rows(&self) -> &[BdtEntry] {
&self.rows[..self.len - usize::from(self.own_row_appended)]
}
#[must_use]
pub const fn len(&self) -> usize {
self.len
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn row(&self, address: SocketAddrV4) -> Option<BdtEntry> {
self.rows()
.iter()
.find(|row| row.address == address)
.copied()
}
#[must_use]
pub fn contains(&self, address: SocketAddrV4) -> bool {
self.row(address).is_some()
}
pub fn set(&mut self, table: BdtEntries<'_>) -> Result<(), BdtError> {
let mut next = self.with_nat(self.own, self.behind_nat);
let found = canonical_rows(table, &mut next.rows)?;
let has_own = table.iter().any(|row| row.address == self.own);
if self.behind_nat {
if let Some(entry) = table.iter().find(|row| row.mask != TWO_HOP_MASK) {
return Err(BdtError::OneHopBehindNat { entry });
}
if !has_own && found.distinct > 0 {
return Err(BdtError::NoOwnRow { own: self.own });
}
}
if self.serves_group {
Self::check_group_row(self.own, table.iter())?;
}
let needed = found.distinct + usize::from(!has_own && !self.behind_nat);
if needed > N {
return Err(BdtError::Capacity {
needed,
available: N,
});
}
next.len = found.stored;
if !self.behind_nat {
next.ensure_own_row();
}
*self = next;
Ok(())
}
pub fn set_own_address(&mut self, own: SocketAddrV4) -> Result<(), BdtError> {
if own == self.own {
return Ok(());
}
if self.behind_nat {
return self.reconfigure(own, true, None);
}
let configured = self.configured_rows();
if self.serves_group {
Self::check_group_row(own, configured.iter().copied())?;
}
let has_own = configured.iter().any(|row| row.address == own);
let needed = configured.len() + usize::from(!has_own);
if needed > N {
return Err(BdtError::Capacity {
needed,
available: N,
});
}
if self.own_row_appended {
self.len -= 1;
self.own_row_appended = false;
}
self.own = own;
self.ensure_own_row();
Ok(())
}
pub(super) fn reconfigure(
&mut self,
own: SocketAddrV4,
behind_nat: bool,
drop: Option<SocketAddrV4>,
) -> Result<(), BdtError> {
if self.is_empty() && !self.behind_nat {
self.own = own;
self.behind_nat = behind_nat;
return Ok(());
}
let mut next = self.with_nat(own, behind_nat);
next.set_without(BdtEntries::from_entries(self.configured_rows()), drop)?;
*self = next;
Ok(())
}
pub(super) fn set_without(
&mut self,
table: BdtEntries<'_>,
drop: Option<SocketAddrV4>,
) -> Result<(), BdtError> {
let Some(drop) = drop else {
return self.set(table);
};
let mut kept = [EMPTY_ROW; N];
let mut len = 0;
for row in table.iter().filter(|row| row.address != drop) {
let Some(slot) = kept.get_mut(len) else {
return self.set(table);
};
*slot = row;
len += 1;
}
self.set(BdtEntries::from_entries(&kept[..len]))
}
fn ensure_own_row(&mut self) {
if !self.contains(self.own) {
let BdtEntry { address, mask } = BdtEntry::new(self.own, TWO_HOP_MASK);
let row = &mut self.rows[self.len];
row.address.set_ip(*address.ip());
row.address.set_port(address.port());
row.mask = mask;
self.len += 1;
self.own_row_appended = true;
}
}
}