use core::fmt;
use core::net::SocketAddrV4;
use bacnet_npdu::MAX_NPDU;
use bacnet_time::Duration;
use crate::LocalSubnet;
pub const MAX_DATAGRAM: usize = 4 + 6 + MAX_NPDU;
pub const MIN_RENEWAL: Duration = Duration::from_millis(100);
pub const DEFAULT_RESPONSE_TIMEOUT: Duration = Duration::from_secs(3);
pub const MIN_BUDGET: u16 = 64;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
Normal,
Foreign(Foreign),
Bbmd,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Retry {
AtRenewal,
Backoff,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ForwardedTrust {
AnySender,
ConfiguredBbmd,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Foreign {
pub bbmd: SocketAddrV4,
pub ttl: u16,
pub renewal: Duration,
pub response_timeout: Duration,
pub retry: Retry,
pub hold: u16,
pub trust: ForwardedTrust,
}
impl Foreign {
#[must_use]
pub const fn new(bbmd: SocketAddrV4, ttl: u16) -> Self {
let renewal = Duration::from_millis(ttl as u64 * 500).max(MIN_RENEWAL);
Self {
bbmd,
ttl,
renewal,
response_timeout: renewal.min(DEFAULT_RESPONSE_TIMEOUT),
retry: Retry::Backoff,
hold: 0,
trust: ForwardedTrust::ConfiguredBbmd,
}
}
#[must_use]
pub const fn ttl_duration(&self) -> Duration {
Duration::from_secs(self.ttl as u64)
}
#[must_use]
pub const fn effective_renewal(&self) -> Duration {
self.renewal.max(MIN_RENEWAL)
}
#[must_use]
pub const fn effective_response_timeout(&self) -> Duration {
self.response_timeout.min(self.effective_renewal())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Link {
pub address: SocketAddrV4,
pub subnet: LocalSubnet,
pub mode: Mode,
pub budget: Option<u16>,
}
impl Link {
pub const fn new(
address: SocketAddrV4,
subnet: LocalSubnet,
mode: Mode,
) -> Result<Self, ConfigError> {
let link = Self {
address,
subnet,
mode,
budget: Some(MAX_DATAGRAM as u16),
};
match link.validate() {
Ok(()) => Ok(link),
Err(error) => Err(error),
}
}
pub const fn validate(&self) -> Result<(), ConfigError> {
if self.address.port() == 0 {
return Err(ConfigError::OwnPort);
}
if self.subnet.is_group() && matches!(self.mode, Mode::Foreign(_)) {
return Err(ConfigError::MulticastMode);
}
if let Some(budget) = self.budget
&& budget < MIN_BUDGET
{
return Err(ConfigError::Budget);
}
if let Mode::Foreign(foreign) = &self.mode {
let ttl = foreign.ttl_duration().as_nanos();
let renewal = foreign.renewal.as_nanos();
if foreign.ttl == 0 || renewal == 0 || renewal >= ttl {
return Err(ConfigError::Renewal);
}
if foreign.response_timeout.as_nanos() == 0 {
return Err(ConfigError::ResponseTimeout);
}
if foreign.bbmd.port() == 0 {
return Err(ConfigError::BbmdPort);
}
if foreign.bbmd.ip().is_unspecified() || self.groups().contains(foreign.bbmd) {
return Err(ConfigError::BbmdAddress);
}
let (bbmd, own) = (foreign.bbmd, self.address);
if bbmd.ip().to_bits() == own.ip().to_bits() && bbmd.port() == own.port() {
return Err(ConfigError::BbmdIsSelf);
}
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConfigError {
OwnPort,
Renewal,
ResponseTimeout,
BbmdPort,
BbmdAddress,
BbmdIsSelf,
Budget,
NotForeign,
MulticastMode,
}
impl fmt::Display for ConfigError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::OwnPort => "the node's own UDP port is 0",
Self::Renewal => {
"foreign-device mode needs TTL > 0 and a renewal interval > 0 and < TTL"
}
Self::ResponseTimeout => "foreign-device mode needs a response timeout > 0",
Self::BbmdPort => "the BBMD's UDP port is 0",
Self::BbmdAddress => "the BBMD's address is unspecified or a group address",
Self::BbmdIsSelf => "the BBMD is this node's own address",
Self::Budget => "the datagram budget is below 64 octets",
Self::NotForeign => "only a foreign device can change its BBMD",
Self::MulticastMode => "a foreign device can't use a multicast group",
})
}
}
impl core::error::Error for ConfigError {}