mod network_port;
use crate::prelude::*;
use crate::{
database::ObjectDatabase,
device::{DeviceConfig, DeviceObject},
network_port::{Bip6PortConfig, BipPortConfig},
traits::BACnetObject,
};
use bacnet_types::{
enums::{NetworkType, ObjectType, PropertyIdentifier as P, Segmentation, ServiceSupported},
error::Error,
primitives::{ObjectIdentifier, PropertyValue},
};
use core::net::{Ipv4Addr, Ipv6Addr};
pub use network_port::NetworkPortEntry;
#[derive(Clone, Debug)]
pub struct DeviceIdentity {
instance: u32,
vendor_id: u16,
max_apdu_length: u16,
segmentation_supported: Segmentation,
apdu_segment_timeout_ms: u64,
services: Vec<ServiceSupported>,
network_ports: Vec<NetworkPortEntry>,
device_uuid: [u8; 16],
name: String,
}
impl DeviceIdentity {
pub fn new(instance: u32, vendor_id: u16) -> Result<Self, Error> {
ObjectIdentifier::new(ObjectType::DEVICE, instance)?;
Ok(Self {
instance,
vendor_id,
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
apdu_segment_timeout_ms: 5000,
services: vec![ServiceSupported::READ_PROPERTY],
network_ports: Vec::new(),
device_uuid: [0; 16],
name: format!("device-{instance}"),
})
}
pub fn with_max_apdu(mut self, max_apdu: u16) -> Result<Self, Error> {
if max_apdu < 50 {
return Err(Error::Encoding(
"Device max-APDU must be at least 50".into(),
));
}
self.max_apdu_length = max_apdu;
Ok(self)
}
pub fn with_segmentation(mut self, segmentation: Segmentation) -> Self {
self.segmentation_supported = segmentation;
self
}
pub fn with_apdu_segment_timeout_ms(mut self, timeout_ms: u64) -> Self {
self.apdu_segment_timeout_ms = timeout_ms;
self
}
pub fn with_services(mut self, services: &[ServiceSupported]) -> Self {
self.services = services.to_vec();
self
}
pub fn with_device_uuid(mut self, uuid: [u8; 16]) -> Self {
self.device_uuid = uuid;
self
}
pub fn with_name(mut self, name: impl Into<String>) -> Self {
self.name = name.into();
self
}
pub fn with_network_port(mut self, entry: NetworkPortEntry) -> Result<Self, Error> {
entry.build_object()?;
if self
.network_ports
.iter()
.any(|e| e.instance == entry.instance)
{
return Err(Error::Encoding(format!(
"duplicate Network-Port instance {}",
entry.instance
)));
}
self.network_ports.push(entry);
Ok(self)
}
pub fn with_bip_port(
self,
instance: u32,
network_number: u32,
ip: Ipv4Addr,
udp_port: u16,
) -> Result<Self, Error> {
self.with_network_port(NetworkPortEntry::bip(
instance,
network_number,
ip,
udp_port,
))
}
pub fn with_bip6_port(
self,
instance: u32,
network_number: u32,
ip: Ipv6Addr,
udp_port: u16,
) -> Result<Self, Error> {
self.with_network_port(NetworkPortEntry::bip6(
instance,
network_number,
ip,
udp_port,
))
}
pub fn with_sc_port(
self,
instance: u32,
network_number: u32,
vmac: [u8; 6],
) -> Result<Self, Error> {
self.with_network_port(NetworkPortEntry::sc(instance, network_number, vmac))
}
fn registered_entry(&self, oid: ObjectIdentifier) -> Result<&NetworkPortEntry, Error> {
let entry = self
.network_ports
.iter()
.find(|entry| entry.instance == oid.instance_number())
.ok_or_else(|| Error::Encoding("selected Network Port missing from identity".into()))?;
entry.build_object()?;
Ok(entry)
}
#[doc(hidden)]
pub fn validate_registered_bip_internal(
&self,
oid: ObjectIdentifier,
config: &BipPortConfig,
) -> Result<(), Error> {
let entry = self.registered_entry(oid)?;
if entry.network_type != NetworkType::IPV4.to_raw()
|| entry.ip != config.ip_address
|| entry.udp_port != config.udp_port
|| entry.network_number != u32::from(config.network_number)
{
return Err(Error::Encoding(
"selected identity Network Port differs from database".into(),
));
}
Ok(())
}
#[doc(hidden)]
pub fn validate_registered_bip6_internal(
&self,
oid: ObjectIdentifier,
config: &Bip6PortConfig,
) -> Result<(), Error> {
let entry = self.registered_entry(oid)?;
if entry.network_type != NetworkType::IPV6.to_raw()
|| entry.ipv6 != config.ip_address
|| entry.udp_port != config.udp_port
|| entry.network_number != u32::from(config.network_number)
{
return Err(Error::Encoding(
"selected identity Network Port differs from database".into(),
));
}
Ok(())
}
#[doc(hidden)]
pub fn publish_registered_bip6_internal(
&mut self,
oid: ObjectIdentifier,
address: core::net::SocketAddrV6,
vmac: [u8; 3],
) {
let entry = self
.network_ports
.iter_mut()
.find(|entry| entry.instance == oid.instance_number())
.expect("validated identity registration");
*entry = NetworkPortEntry::bip6_with_vmac(
entry.instance,
entry.network_number,
*address.ip(),
address.port(),
vmac,
);
}
#[doc(hidden)]
pub fn publish_registered_bip_internal(
&mut self,
oid: ObjectIdentifier,
address: core::net::SocketAddrV4,
) {
let entry = self
.network_ports
.iter_mut()
.find(|entry| entry.instance == oid.instance_number())
.expect("validated identity registration");
*entry = NetworkPortEntry::bip(
entry.instance,
entry.network_number,
*address.ip(),
address.port(),
);
}
pub fn instance(&self) -> u32 {
self.instance
}
pub fn vendor_id(&self) -> u16 {
self.vendor_id
}
pub fn max_apdu_length(&self) -> u16 {
self.max_apdu_length
}
pub fn name(&self) -> &str {
&self.name
}
pub fn apdu_segment_timeout_ms(&self) -> u64 {
self.apdu_segment_timeout_ms
}
pub fn resolve(&self, local_receive_capacity: u16) -> Result<Self, Error> {
let mut identity = self.clone();
identity.max_apdu_length = identity.max_apdu_length.min(local_receive_capacity);
identity.validate()?;
Ok(identity)
}
pub fn validate(&self) -> Result<(), Error> {
ObjectIdentifier::new(ObjectType::DEVICE, self.instance)?;
if self.max_apdu_length < 50 {
return Err(Error::Encoding(
"Device max-APDU must be at least 50".into(),
));
}
if self.segmentation_supported.to_raw() > Segmentation::NONE.to_raw() {
return Err(Error::Encoding(
"invalid Device segmentation support".into(),
));
}
if self.segmentation_supported != Segmentation::NONE && self.apdu_segment_timeout_ms == 0 {
return Err(Error::Encoding(
"APDU_Segment_Timeout must be positive when segmentation is supported".into(),
));
}
Ok(())
}
pub fn validate_device(&self, device: &dyn BACnetObject) -> Result<(), Error> {
self.validate()?;
if device.object_identifier() != self.device_oid() {
return Err(Error::Encoding(
"selected Device identifier must match identity".into(),
));
}
for (property, expected) in [
(
P::OBJECT_IDENTIFIER,
PropertyValue::ObjectIdentifier(self.device_oid()),
),
(
P::VENDOR_IDENTIFIER,
PropertyValue::Unsigned(u64::from(self.vendor_id)),
),
(
P::MAX_APDU_LENGTH_ACCEPTED,
PropertyValue::Unsigned(u64::from(self.max_apdu_length)),
),
(
P::SEGMENTATION_SUPPORTED,
PropertyValue::Enumerated(u32::from(self.segmentation_supported.to_raw())),
),
] {
if device.read_property(property, None)? != expected {
return Err(Error::Encoding(format!(
"selected Device {property:?} must match identity"
)));
}
}
if self.segmentation_supported != Segmentation::NONE
&& device.read_property(P::APDU_SEGMENT_TIMEOUT, None)?
!= PropertyValue::Unsigned(self.apdu_segment_timeout_ms)
{
return Err(Error::Encoding(
"selected Device APDU_Segment_Timeout must match identity".into(),
));
}
Ok(())
}
pub fn segmentation(&self) -> Segmentation {
self.segmentation_supported
}
pub fn services(&self) -> &[ServiceSupported] {
&self.services
}
pub fn network_ports(&self) -> &[NetworkPortEntry] {
&self.network_ports
}
pub fn device_uuid(&self) -> [u8; 16] {
self.device_uuid
}
pub fn device_oid(&self) -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::DEVICE, self.instance)
.expect("identity instance was validated at construction")
}
pub fn build_database(&self) -> Result<ObjectDatabase, Error> {
let mut device = DeviceObject::new(DeviceConfig {
identity: self.clone(),
firmware_revision: env!("CARGO_PKG_VERSION").into(),
application_software_version: env!("CARGO_PKG_VERSION").into(),
..DeviceConfig::default()
})?;
device.set_services_supported(&self.services);
device.set_device_uuid(self.device_uuid);
let device_oid = device.object_identifier();
let mut oids = vec![device_oid];
let mut ports = Vec::new();
for entry in &self.network_ports {
let port = entry.build_object()?;
oids.push(port.object_identifier());
ports.push(port);
}
device.set_object_list(oids);
let mut db = ObjectDatabase::new();
db.add(Box::new(device))?;
for port in ports {
db.add(Box::new(port))?;
}
Ok(db)
}
}
pub fn build_database_with_extra(
identity: &DeviceIdentity,
extra: Vec<Box<dyn crate::traits::BACnetObject>>,
) -> Result<ObjectDatabase, Error> {
let mut device = DeviceObject::new(DeviceConfig {
identity: identity.clone(),
firmware_revision: env!("CARGO_PKG_VERSION").into(),
application_software_version: env!("CARGO_PKG_VERSION").into(),
..DeviceConfig::default()
})?;
device.set_services_supported(&identity.services);
device.set_device_uuid(identity.device_uuid);
let device_oid = device.object_identifier();
let mut oids = vec![device_oid];
let mut ports = Vec::new();
for entry in &identity.network_ports {
let port = entry.build_object()?;
oids.push(port.object_identifier());
ports.push(port);
}
for obj in &extra {
oids.push(obj.object_identifier());
}
device.set_object_list(oids);
let mut db = ObjectDatabase::new();
db.add(Box::new(device))?;
for port in ports {
db.add(Box::new(port))?;
}
for obj in extra {
db.add(obj)?;
}
Ok(db)
}
impl Default for DeviceIdentity {
fn default() -> Self {
Self::new(1, 0)
.expect("default Device instance is valid")
.with_name("BACnet Device")
}
}