use alloc::sync::Arc;
use bacnet_encoding::constructed::{encode_bdt_entry, encode_fdt_entry, encode_host_n_port};
use bacnet_types::bip_port::{BbmdTables, BipPortMode, ForeignRegistration};
use bacnet_types::constructed::{BACnetBDTEntry, BACnetHostNPort};
use bacnet_types::enums::{IPMode, PropertyIdentifier as P};
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use bytes::BytesMut;
#[derive(Clone, Default)]
pub(super) enum LiveMode {
#[default]
Normal,
Foreign(Arc<dyn ForeignRegistration>),
Bbmd(Arc<dyn BbmdTables>),
}
impl LiveMode {
pub(super) fn from_port(mode: BipPortMode) -> Result<Self, Error> {
Ok(match mode {
BipPortMode::Normal => Self::Normal,
BipPortMode::Foreign { registration } => Self::Foreign(registration),
BipPortMode::Bbmd {
tables: Some(tables),
} => Self::Bbmd(tables),
BipPortMode::Bbmd { tables: None } => {
return Err(Error::Encoding(
"a registered BBMD must lend its tables once started".into(),
));
}
})
}
pub(super) fn has_nat_rows(&self) -> bool {
matches!(self, Self::Bbmd(tables) if tables.nat_traversal().is_some())
}
pub(super) fn nat_on(&self) -> bool {
matches!(self, Self::Bbmd(tables) if tables.nat_traversal() == Some(true))
}
pub(super) fn can_change(&self) -> bool {
match self {
Self::Normal => false,
Self::Foreign(registration) => registration.can_change(),
Self::Bbmd(tables) => tables.can_change(),
}
}
pub(super) fn ip_mode(&self) -> IPMode {
match self {
Self::Normal => IPMode::NORMAL,
Self::Foreign(_) => IPMode::FOREIGN,
Self::Bbmd(_) => IPMode::BBMD,
}
}
pub(super) fn read(&self, property: P) -> Option<Result<PropertyValue, Error>> {
match (self, property) {
(Self::Foreign(registration), P::FD_BBMD_ADDRESS) => {
Some(host_n_port_value(®istration.bbmd()))
}
(Self::Foreign(registration), P::FD_SUBSCRIPTION_LIFETIME) => Some(Ok(
PropertyValue::Unsigned(registration.subscription_lifetime().into()),
)),
(Self::Bbmd(tables), P::BBMD_BROADCAST_DISTRIBUTION_TABLE) => {
Some(bdt_value(&tables.broadcast_distribution_table()))
}
(Self::Bbmd(tables), P::BBMD_ACCEPT_FD_REGISTRATIONS) => Some(Ok(
PropertyValue::Boolean(tables.accepts_foreign_device_registrations()),
)),
(Self::Bbmd(tables), P::BACNET_IP_NAT_TRAVERSAL) => tables
.nat_traversal()
.map(|nat| Ok(PropertyValue::Boolean(nat))),
(Self::Bbmd(tables), P::BACNET_IP_GLOBAL_ADDRESS) => tables
.global_address()
.map(|address| host_n_port_value(&address)),
(Self::Bbmd(tables), P::BBMD_FOREIGN_DEVICE_TABLE) => Some(Ok(PropertyValue::List(
tables
.foreign_device_table()
.iter()
.map(|entry| {
let mut buf = BytesMut::new();
encode_fdt_entry(&mut buf, entry);
framed(buf)
})
.collect(),
))),
_ => None,
}
}
}
pub(super) fn bdt_value(table: &[BACnetBDTEntry]) -> Result<PropertyValue, Error> {
table
.iter()
.map(|entry| {
let mut buf = BytesMut::new();
encode_bdt_entry(&mut buf, entry).map(|()| framed(buf))
})
.collect::<Result<_, _>>()
.map(PropertyValue::List)
}
pub(super) fn host_n_port_value(address: &BACnetHostNPort) -> Result<PropertyValue, Error> {
let mut buf = BytesMut::new();
encode_host_n_port(&mut buf, address).map(|()| framed(buf))
}
fn framed(buf: BytesMut) -> PropertyValue {
PropertyValue::ApplicationData(buf.to_vec())
}