use core::fmt;
use core::net::{Ipv4Addr, SocketAddrV4};
use bacnet_bip_codec::{BvlcResultCode, FdtEntries, FdtEntry};
use bacnet_time::{Duration, Instant};
pub const GRACE_PERIOD: Duration = Duration::from_secs(30);
const GRACE_WHOLE_SECONDS: u64 = GRACE_PERIOD.as_secs();
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Registration {
pub address: SocketAddrV4,
pub ttl: u16,
pub registered_at: Instant,
}
const EMPTY_SLOT: Registration = Registration::new(
SocketAddrV4::new(Ipv4Addr::UNSPECIFIED, 0),
0,
Instant::ZERO,
);
impl Registration {
#[must_use]
pub const fn new(address: SocketAddrV4, ttl: u16, registered_at: Instant) -> Self {
Self {
address,
ttl,
registered_at,
}
}
#[must_use]
pub const fn lifetime(&self) -> Duration {
Duration::from_secs(self.ttl as u64 + GRACE_WHOLE_SECONDS)
}
#[must_use]
pub fn is_expired_at(&self, now: Instant) -> bool {
now.saturating_duration_since(self.registered_at) > self.lifetime()
}
#[must_use]
pub fn seconds_remaining_at(&self, now: Instant) -> u16 {
let lifetime_secs = u64::from(self.ttl) + GRACE_WHOLE_SECONDS;
let passed = now.saturating_duration_since(self.registered_at).as_secs();
u16::try_from(lifetime_secs.saturating_sub(passed)).unwrap_or(u16::MAX)
}
#[must_use]
pub fn wire_entry_at(&self, now: Instant) -> FdtEntry {
FdtEntry::new(self.address, self.ttl, self.seconds_remaining_at(now))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct RegistrationLimits {
pub min_ttl: u16,
pub max_ttl: u16,
pub max_entries_per_source: usize,
pub reserved_capacity: usize,
}
impl RegistrationLimits {
pub const DEFAULT: Self = Self {
min_ttl: 1,
max_ttl: 3600,
max_entries_per_source: 8,
reserved_capacity: 0,
};
}
impl Default for RegistrationLimits {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Reservation {
Ordinary,
Reserved,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Registered {
New,
Renewed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum RegistrationRefusal {
TimeToLive,
NotAdmitted,
SourceQuota,
TableFull,
}
impl RegistrationRefusal {
#[must_use]
pub const fn result_code(self) -> BvlcResultCode {
BvlcResultCode::REGISTER_FOREIGN_DEVICE_NAK
}
}
impl fmt::Display for RegistrationRefusal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::TimeToLive => "time-to-live outside the accepted range",
Self::NotAdmitted => "registration not admitted",
Self::SourceQuota => "too many registrations from this IPv4 address",
Self::TableFull => "foreign device table full",
})
}
}
impl core::error::Error for RegistrationRefusal {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use]
#[non_exhaustive]
pub struct RegisterOutcome {
pub result: Result<Registered, RegistrationRefusal>,
pub purged: usize,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct StorageFull {
pub needed: usize,
pub available: usize,
}
impl fmt::Display for StorageFull {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"storage full: need {} entries, have {}",
self.needed, self.available
)
}
}
impl core::error::Error for StorageFull {}
#[derive(Clone)]
pub struct Fdt<const N: usize> {
slots: [Registration; N],
len: usize,
}
impl<const N: usize> fmt::Debug for Fdt<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.registrations()).finish()
}
}
impl<const N: usize> Default for Fdt<N> {
fn default() -> Self {
Self::new()
}
}
impl<const N: usize> Fdt<N> {
pub const CAPACITY: usize = N;
#[must_use]
pub const fn new() -> Self {
Self {
slots: [EMPTY_SLOT; N],
len: 0,
}
}
#[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 registrations(&self) -> &[Registration] {
&self.slots[..self.len]
}
pub fn live_at(&self, now: Instant) -> impl DoubleEndedIterator<Item = &Registration> + '_ {
self.registrations()
.iter()
.filter(move |entry| !entry.is_expired_at(now))
}
#[must_use]
pub fn get(&self, address: SocketAddrV4) -> Option<&Registration> {
self.registrations()
.iter()
.find(|entry| entry.address == address)
}
#[must_use]
pub fn is_registered_at(&self, address: SocketAddrV4, now: Instant) -> bool {
self.get(address)
.is_some_and(|entry| !entry.is_expired_at(now))
}
pub fn register(
&mut self,
source: SocketAddrV4,
ttl: u16,
now: Instant,
limits: &RegistrationLimits,
reservation: Reservation,
admit: impl FnOnce() -> bool,
) -> RegisterOutcome {
let purged = self.purge(now);
let result = self.admit_and_store(source, ttl, now, limits, reservation, admit);
RegisterOutcome { result, purged }
}
fn admit_and_store(
&mut self,
source: SocketAddrV4,
ttl: u16,
now: Instant,
limits: &RegistrationLimits,
reservation: Reservation,
admit: impl FnOnce() -> bool,
) -> Result<Registered, RegistrationRefusal> {
if ttl == 0 || ttl < limits.min_ttl || ttl > limits.max_ttl {
return Err(RegistrationRefusal::TimeToLive);
}
if !admit() {
return Err(RegistrationRefusal::NotAdmitted);
}
let registration = Registration::new(source, ttl, now);
if let Some(slot) = self.slots[..self.len]
.iter_mut()
.find(|entry| entry.address == source)
{
*slot = registration;
return Ok(Registered::Renewed);
}
let from_source = self
.registrations()
.iter()
.filter(|entry| entry.address.ip() == source.ip())
.count();
if from_source >= limits.max_entries_per_source {
return Err(RegistrationRefusal::SourceQuota);
}
let limit = match reservation {
Reservation::Reserved => N,
Reservation::Ordinary => N.saturating_sub(limits.reserved_capacity),
};
if self.len >= limit {
return Err(RegistrationRefusal::TableFull);
}
self.slots[self.len] = registration;
self.len += 1;
Ok(Registered::New)
}
pub fn delete(&mut self, address: SocketAddrV4) -> bool {
self.retain(|entry| entry.address != address) > 0
}
pub fn purge(&mut self, now: Instant) -> usize {
self.retain(|entry| !entry.is_expired_at(now))
}
pub fn restore(&mut self, registration: Registration) -> Result<(), StorageFull> {
let address = registration.address;
if let Some(slot) = self.slots[..self.len]
.iter_mut()
.find(|entry| entry.address == address)
{
*slot = registration;
return Ok(());
}
let Some(slot) = self.slots.get_mut(self.len) else {
return Err(StorageFull {
needed: self.len + 1,
available: N,
});
};
*slot = registration;
self.len += 1;
Ok(())
}
pub fn wire_entries<'o>(
&self,
now: Instant,
out: &'o mut [FdtEntry],
) -> Result<FdtEntries<'o>, StorageFull> {
let mut written = 0;
let mut live = self.live_at(now);
for entry in live.by_ref() {
let FdtEntry {
address,
ttl,
remaining,
} = entry.wire_entry_at(now);
let Some(row) = out.get_mut(written) else {
return Err(StorageFull {
needed: written + 1 + live.count(),
available: out.len(),
});
};
row.address.set_ip(*address.ip());
row.address.set_port(address.port());
row.ttl = ttl;
row.remaining = remaining;
written += 1;
}
Ok(FdtEntries::from_entries(&out[..written]))
}
fn retain(&mut self, mut keep: impl FnMut(&Registration) -> bool) -> usize {
let mut kept = 0;
for index in 0..self.len {
let Registration {
address,
ttl,
registered_at,
} = self.slots[index];
if keep(&self.slots[index]) {
let slot = &mut self.slots[kept];
slot.address = address;
slot.ttl = ttl;
slot.registered_at = registered_at;
kept += 1;
}
}
let removed = self.len - kept;
self.len = kept;
removed
}
}