use crate::prelude::*;
use alloc::borrow::Cow;
use bacnet_types::MacAddr;
use bacnet_types::enums::{NetworkType, ObjectType, PropertyIdentifier, Reliability};
use bacnet_types::error::Error;
use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
use crate::common::{self, read_common_properties};
use crate::traits::BACnetObject;
mod bip6_config;
mod bip_config;
mod bip_mode;
mod ip_config;
mod metadata;
mod multicast;
mod pending;
mod registration;
use bacnet_types::network_number::NetworkNumber;
pub use bip_config::BipPortConfig;
pub(crate) use bip6_config::Bip6Bind;
pub use bip6_config::Bip6PortConfig;
use ip_config::IpConfig;
pub(crate) use ip_config::{PortAddress, PortBind};
pub struct NetworkPortObject {
oid: ObjectIdentifier,
name: String,
description: String,
status_flags: StatusFlags,
out_of_service: bool,
reliability: Reliability,
network_type: NetworkType,
network_number: NetworkNumber,
mac_address: MacAddr,
apdu_length: u32,
ip: Option<IpConfig>,
mode: bip_mode::LiveMode,
pending: pending::Pending,
activation: bool,
multicast: multicast::Multicast,
binding: alloc::sync::Weak<()>,
}
impl NetworkPortObject {
pub fn new_bip(
instance: u32,
name: impl Into<String>,
config: BipPortConfig,
) -> Result<Self, Error> {
Self::new_ip(instance, name.into(), IpConfig::V4(config))
}
pub fn new_bip6(
instance: u32,
name: impl Into<String>,
config: Bip6PortConfig,
) -> Result<Self, Error> {
Self::new_ip(instance, name.into(), IpConfig::V6(config))
}
fn new_ip(instance: u32, name: String, config: IpConfig) -> Result<Self, Error> {
config.validate(instance)?;
Ok(Self {
oid: ObjectIdentifier::new(ObjectType::NETWORK_PORT, instance)?,
name,
description: String::new(),
status_flags: StatusFlags::empty(),
out_of_service: false,
reliability: Reliability::NO_FAULT_DETECTED,
network_type: config.network_type(),
network_number: NetworkNumber::configured(config.network_number())
.expect("validated Network Number"),
mac_address: config.mac(),
apdu_length: config.apdu_length(),
ip: Some(config),
mode: Default::default(),
pending: Default::default(),
activation: false,
multicast: Default::default(),
binding: alloc::sync::Weak::new(),
})
}
pub fn new_non_bip(
instance: u32,
name: impl Into<String>,
network_type: NetworkType,
network_number: u16,
mac_address: MacAddr,
apdu_length: u32,
) -> Result<Self, Error> {
if !matches!(network_type, NetworkType::ETHERNET | NetworkType::VIRTUAL)
|| network_number == u16::MAX
|| apdu_length < 50
{
return Err(common::value_out_of_range_error());
}
Ok(Self {
oid: ObjectIdentifier::new(ObjectType::NETWORK_PORT, instance)?,
name: name.into(),
description: String::new(),
status_flags: StatusFlags::empty(),
out_of_service: false,
reliability: Reliability::NO_FAULT_DETECTED,
network_type,
network_number: NetworkNumber::configured(network_number)
.expect("validated Network Number"),
mac_address,
apdu_length,
ip: None,
mode: Default::default(),
pending: Default::default(),
activation: false,
multicast: Default::default(),
binding: alloc::sync::Weak::new(),
})
}
pub fn set_description(&mut self, desc: impl Into<String>) {
self.description = desc.into();
}
}
impl BACnetObject for NetworkPortObject {
fn object_identifier(&self) -> ObjectIdentifier {
self.oid
}
fn object_name(&self) -> &str {
&self.name
}
fn read_property(
&self,
property: PropertyIdentifier,
array_index: Option<u32>,
) -> Result<PropertyValue, Error> {
use PropertyIdentifier as P;
if let Some(result) = read_common_properties!(self, property, array_index) {
return result;
}
match property {
P::OBJECT_TYPE => Ok(PropertyValue::Enumerated(ObjectType::NETWORK_PORT.to_raw())),
P::NETWORK_TYPE => Ok(PropertyValue::Enumerated(self.network_type.to_raw())),
P::PROTOCOL_LEVEL => Ok(PropertyValue::Enumerated(2)),
P::NETWORK_NUMBER => Ok(PropertyValue::Unsigned(
self.network_number.snapshot().0.into(),
)),
P::NETWORK_NUMBER_QUALITY => Ok(PropertyValue::Enumerated(
self.network_number.snapshot().1.into(),
)),
P::MAC_ADDRESS => Ok(PropertyValue::OctetString(self.mac_address.to_vec())),
P::APDU_LENGTH => Ok(PropertyValue::Unsigned(self.apdu_length.into())),
P::LINK_SPEED => Ok(PropertyValue::Real(0.0)),
P::CHANGES_PENDING => Ok(PropertyValue::Boolean(self.changes_pending())),
_ => {
let config = self
.ip
.as_ref()
.ok_or_else(common::unknown_property_error)?;
if property == config.mode_property() {
return Ok(PropertyValue::Enumerated(self.mode.ip_mode().to_raw()));
}
if pending::nat_row(property) && !self.serves_nat() {
return Err(common::unknown_property_error());
}
if let Some(result) = self.read_multicast(property) {
return result;
}
config
.read(property, array_index)
.or_else(|| self.pending_value(property))
.or_else(|| self.mode.read(property))
.unwrap_or_else(|| Err(common::unknown_property_error()))
}
}
}
fn write_property(
&mut self,
property: PropertyIdentifier,
array_index: Option<u32>,
value: PropertyValue,
_priority: Option<u8>,
) -> Result<(), Error> {
if property == PropertyIdentifier::OUT_OF_SERVICE && self.is_bound() {
return Err(common::write_access_denied_error());
}
if let Some(result) =
common::write_out_of_service(&mut self.out_of_service, property, &value)
{
return result;
}
if let Some(result) = common::write_description(&mut self.description, property, &value) {
return result;
}
if let Some(result) = self.write_pending(property, array_index, &value) {
return result;
}
if let Some(result) = self.write_multicast(property, array_index, &value) {
return result;
}
if self
.property_metadata()
.iter()
.any(|row| row.property_identifier == property)
{
Err(common::write_access_denied_error())
} else {
Err(common::unknown_property_error())
}
}
fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
metadata::for_object(self)
}
fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
crate::property_metadata::property_list_from_metadata(self.property_metadata().as_ref())
}
fn is_createable(&self) -> bool {
false
}
fn is_deleteable(&self) -> bool {
false
}
}
#[cfg(test)]
mod bip6_tests;
#[cfg(test)]
mod foreign_pending_tests;
#[cfg(test)]
mod mode_tests;
#[cfg(test)]
mod multicast_tests;
#[cfg(test)]
mod nat_pending_tests;
#[cfg(test)]
mod pending_tests;
#[cfg(test)]
mod tests;