use bacnet_encoding::apdu::{encode_apdu, validate_max_apdu_length, Apdu, UnconfirmedRequest};
use bacnet_objects::database::ObjectDatabase;
use bacnet_objects::device::{DeviceConfig, DeviceObject};
use bacnet_objects::network_port::{BipPortConfig, NetworkPortObject};
use bacnet_objects::traits::BACnetObject;
use bacnet_services::who_is::IAmRequest;
use bacnet_types::enums::{NetworkType, ObjectType, Segmentation, ServiceSupported};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bacnet_types::MacAddr;
use bytes::BytesMut;
use std::net::Ipv4Addr;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct NetworkPortEntry {
pub instance: u32,
pub network_type: u32,
pub network_number: u32,
pub mac: MacAddr,
pub ip: [u8; 4],
pub udp_port: u16,
}
impl NetworkPortEntry {
pub fn bip(instance: u32, network_number: u32, ip: Ipv4Addr, udp_port: u16) -> Self {
let o = ip.octets();
let mut mac = MacAddr::new();
mac.extend_from_slice(&o);
mac.extend_from_slice(&udp_port.to_be_bytes());
Self {
instance,
network_type: NetworkType::IPV4.to_raw(),
network_number,
mac,
ip: o,
udp_port,
}
}
fn build_object(&self) -> Result<NetworkPortObject, Error> {
let network_number = u16::try_from(self.network_number)
.map_err(|_| Error::Encoding("network number must be 0..65534".into()))?;
let name = format!("port-{}", self.instance);
let apdu_length = 1476;
if self.network_type == NetworkType::IPV4.to_raw() {
let [a, b, c, d] = self.ip;
let [hi, lo] = self.udp_port.to_be_bytes();
if self.mac.as_slice() != [a, b, c, d, hi, lo] {
return Err(Error::Encoding(
"B/IP MAC must match configured IPv4 address and UDP port".into(),
));
}
NetworkPortObject::new_bip(
self.instance,
name,
BipPortConfig {
network_number,
apdu_length,
ip_address: self.ip,
udp_port: self.udp_port,
..Default::default()
},
)
} else {
NetworkPortObject::new_non_bip(
self.instance,
name,
NetworkType::from_raw(self.network_type),
network_number,
self.mac.clone(),
apdu_length,
)
}
}
pub fn sc(instance: u32, network_number: u32, vmac: [u8; 6]) -> Self {
Self {
instance,
network_type: NetworkType::VIRTUAL.to_raw(),
network_number,
mac: MacAddr::from_slice(&vmac),
ip: [0; 4],
udp_port: 0,
}
}
}
#[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: 6000,
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> {
validate_max_apdu_length(max_apdu)?;
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_sc_port(
self,
instance: u32,
network_number: u32,
vmac: [u8; 6],
) -> Result<Self, Error> {
self.with_network_port(NetworkPortEntry::sc(instance, network_number, vmac))
}
pub(crate) fn validate_registered_bip(
&self,
oid: ObjectIdentifier,
config: &BipPortConfig,
) -> Result<(), 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()?;
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(())
}
pub(crate) fn publish_registered_bip(
&mut self,
oid: ObjectIdentifier,
address: std::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 client_max_apdu(&self) -> u16 {
self.max_apdu_length
}
pub fn server_max_apdu(&self) -> u16 {
self.max_apdu_length
}
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 iam_request(&self) -> IAmRequest {
IAmRequest {
object_identifier: self.device_oid(),
max_apdu_length: u32::from(self.max_apdu_length),
segmentation_supported: self.segmentation_supported,
vendor_id: self.vendor_id,
}
}
pub fn encode_iam_apdu(&self) -> Result<Vec<u8>, Error> {
let mut service = BytesMut::new();
self.iam_request().encode(&mut service);
let mut buf = BytesMut::new();
encode_apdu(
&mut buf,
&Apdu::UnconfirmedRequest(UnconfirmedRequest {
service_choice: bacnet_types::enums::UnconfirmedServiceChoice::I_AM,
service_request: service.freeze(),
}),
)?;
Ok(buf.to_vec())
}
pub fn build_database(&self) -> Result<ObjectDatabase, Error> {
let mut device = DeviceObject::new(DeviceConfig {
instance: self.instance,
name: self.name.clone(),
vendor_name: "Rusty BACnet".into(),
vendor_id: self.vendor_id,
model_name: "rusty-bacnet-endpoint".into(),
firmware_revision: env!("CARGO_PKG_VERSION").into(),
application_software_version: env!("CARGO_PKG_VERSION").into(),
max_apdu_length: u32::from(self.max_apdu_length),
segmentation_supported: self.segmentation_supported,
apdu_segment_timeout: self.apdu_segment_timeout_ms,
apdu_timeout: 6000,
apdu_retries: 3,
})?;
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 apply_to_session_config(&self, config: &mut crate::session::SessionConfig) {
config.max_apdu_length = self.max_apdu_length;
}
pub fn apply_to_client_config(&self, config: &mut bacnet_client::client::ClientConfig) {
config.max_apdu_length = self.max_apdu_length;
}
pub fn apply_to_server_config(&self, config: &mut bacnet_server::server::ServerConfig) {
config.max_apdu_length = u32::from(self.max_apdu_length);
config.segmentation_supported = self.segmentation_supported;
config.apdu_segment_timeout_ms = self.apdu_segment_timeout_ms;
config.vendor_id = self.vendor_id;
}
pub fn server_config(&self) -> bacnet_server::server::ServerConfig {
let mut config = bacnet_server::server::ServerConfig::default();
self.apply_to_server_config(&mut config);
config
}
pub fn client_config(&self) -> bacnet_client::client::ClientConfig {
let mut config = bacnet_client::client::ClientConfig::default();
self.apply_to_client_config(&mut config);
config
}
}
pub fn build_database_with_extra(
identity: &DeviceIdentity,
extra: Vec<Box<dyn bacnet_objects::traits::BACnetObject>>,
) -> Result<ObjectDatabase, Error> {
let mut device = DeviceObject::new(DeviceConfig {
instance: identity.instance,
name: identity.name.clone(),
vendor_name: "Rusty BACnet".into(),
vendor_id: identity.vendor_id,
model_name: "rusty-bacnet-endpoint".into(),
firmware_revision: env!("CARGO_PKG_VERSION").into(),
application_software_version: env!("CARGO_PKG_VERSION").into(),
max_apdu_length: u32::from(identity.max_apdu_length),
segmentation_supported: identity.segmentation_supported,
apdu_segment_timeout: identity.apdu_segment_timeout_ms,
apdu_timeout: 6000,
apdu_retries: 3,
})?;
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)
}