bacnet_objects/device_identity/
mod.rs1mod network_port;
3use crate::{
4 database::ObjectDatabase,
5 device::{DeviceConfig, DeviceObject},
6 network_port::BipPortConfig,
7 traits::BACnetObject,
8};
9use bacnet_types::{
10 enums::{NetworkType, ObjectType, PropertyIdentifier as P, Segmentation, ServiceSupported},
11 error::Error,
12 primitives::{ObjectIdentifier, PropertyValue},
13};
14pub use network_port::NetworkPortEntry;
15use std::net::Ipv4Addr;
16
17#[derive(Clone, Debug)]
34pub struct DeviceIdentity {
35 instance: u32,
36 vendor_id: u16,
37 max_apdu_length: u16,
38 segmentation_supported: Segmentation,
39 apdu_segment_timeout_ms: u64,
40 services: Vec<ServiceSupported>,
41 network_ports: Vec<NetworkPortEntry>,
42 device_uuid: [u8; 16],
43 name: String,
44}
45
46impl DeviceIdentity {
47 pub fn new(instance: u32, vendor_id: u16) -> Result<Self, Error> {
56 ObjectIdentifier::new(ObjectType::DEVICE, instance)?;
57 Ok(Self {
58 instance,
59 vendor_id,
60 max_apdu_length: 1476,
61 segmentation_supported: Segmentation::NONE,
62 apdu_segment_timeout_ms: 5000,
63 services: vec![ServiceSupported::READ_PROPERTY],
64 network_ports: Vec::new(),
65 device_uuid: [0; 16],
66 name: format!("device-{instance}"),
67 })
68 }
69
70 pub fn with_max_apdu(mut self, max_apdu: u16) -> Result<Self, Error> {
76 if max_apdu < 50 {
77 return Err(Error::Encoding(
78 "Device max-APDU must be at least 50".into(),
79 ));
80 }
81 self.max_apdu_length = max_apdu;
82 Ok(self)
83 }
84
85 pub fn with_segmentation(mut self, segmentation: Segmentation) -> Self {
91 self.segmentation_supported = segmentation;
92 self
93 }
94
95 pub fn with_apdu_segment_timeout_ms(mut self, timeout_ms: u64) -> Self {
98 self.apdu_segment_timeout_ms = timeout_ms;
99 self
100 }
101
102 pub fn with_services(mut self, services: &[ServiceSupported]) -> Self {
112 self.services = services.to_vec();
113 self
114 }
115
116 pub fn with_device_uuid(mut self, uuid: [u8; 16]) -> Self {
122 self.device_uuid = uuid;
123 self
124 }
125
126 pub fn with_name(mut self, name: impl Into<String>) -> Self {
128 self.name = name.into();
129 self
130 }
131
132 pub fn with_network_port(mut self, entry: NetworkPortEntry) -> Result<Self, Error> {
139 entry.build_object()?;
140 if self
141 .network_ports
142 .iter()
143 .any(|e| e.instance == entry.instance)
144 {
145 return Err(Error::Encoding(format!(
146 "duplicate Network-Port instance {}",
147 entry.instance
148 )));
149 }
150 self.network_ports.push(entry);
151 Ok(self)
152 }
153
154 pub fn with_bip_port(
156 self,
157 instance: u32,
158 network_number: u32,
159 ip: Ipv4Addr,
160 udp_port: u16,
161 ) -> Result<Self, Error> {
162 self.with_network_port(NetworkPortEntry::bip(
163 instance,
164 network_number,
165 ip,
166 udp_port,
167 ))
168 }
169
170 pub fn with_sc_port(
172 self,
173 instance: u32,
174 network_number: u32,
175 vmac: [u8; 6],
176 ) -> Result<Self, Error> {
177 self.with_network_port(NetworkPortEntry::sc(instance, network_number, vmac))
178 }
179
180 #[doc(hidden)]
181 pub fn validate_registered_bip_internal(
182 &self,
183 oid: ObjectIdentifier,
184 config: &BipPortConfig,
185 ) -> Result<(), Error> {
186 let entry = self
187 .network_ports
188 .iter()
189 .find(|entry| entry.instance == oid.instance_number())
190 .ok_or_else(|| Error::Encoding("selected Network Port missing from identity".into()))?;
191 entry.build_object()?;
192 if entry.network_type != NetworkType::IPV4.to_raw()
193 || entry.ip != config.ip_address
194 || entry.udp_port != config.udp_port
195 || entry.network_number != u32::from(config.network_number)
196 {
197 return Err(Error::Encoding(
198 "selected identity Network Port differs from database".into(),
199 ));
200 }
201 Ok(())
202 }
203
204 #[doc(hidden)]
205 pub fn publish_registered_bip_internal(
206 &mut self,
207 oid: ObjectIdentifier,
208 address: std::net::SocketAddrV4,
209 ) {
210 let entry = self
211 .network_ports
212 .iter_mut()
213 .find(|entry| entry.instance == oid.instance_number())
214 .expect("validated identity registration");
215 *entry = NetworkPortEntry::bip(
216 entry.instance,
217 entry.network_number,
218 *address.ip(),
219 address.port(),
220 );
221 }
222
223 pub fn instance(&self) -> u32 {
225 self.instance
226 }
227
228 pub fn vendor_id(&self) -> u16 {
230 self.vendor_id
231 }
232
233 pub fn max_apdu_length(&self) -> u16 {
235 self.max_apdu_length
236 }
237
238 pub fn name(&self) -> &str {
240 &self.name
241 }
242
243 pub fn apdu_segment_timeout_ms(&self) -> u64 {
245 self.apdu_segment_timeout_ms
246 }
247
248 pub fn resolve(&self, local_receive_capacity: u16) -> Result<Self, Error> {
250 let mut identity = self.clone();
251 identity.max_apdu_length = identity.max_apdu_length.min(local_receive_capacity);
252 identity.validate()?;
253 Ok(identity)
254 }
255
256 pub fn validate(&self) -> Result<(), Error> {
258 ObjectIdentifier::new(ObjectType::DEVICE, self.instance)?;
259 if self.max_apdu_length < 50 {
260 return Err(Error::Encoding(
261 "Device max-APDU must be at least 50".into(),
262 ));
263 }
264 if self.segmentation_supported.to_raw() > Segmentation::NONE.to_raw() {
265 return Err(Error::Encoding(
266 "invalid Device segmentation support".into(),
267 ));
268 }
269 if self.segmentation_supported != Segmentation::NONE && self.apdu_segment_timeout_ms == 0 {
270 return Err(Error::Encoding(
271 "APDU_Segment_Timeout must be positive when segmentation is supported".into(),
272 ));
273 }
274 Ok(())
275 }
276
277 pub fn validate_device(&self, device: &dyn BACnetObject) -> Result<(), Error> {
279 self.validate()?;
280 if device.object_identifier() != self.device_oid() {
281 return Err(Error::Encoding(
282 "selected Device identifier must match identity".into(),
283 ));
284 }
285 for (property, expected) in [
286 (
287 P::OBJECT_IDENTIFIER,
288 PropertyValue::ObjectIdentifier(self.device_oid()),
289 ),
290 (
291 P::VENDOR_IDENTIFIER,
292 PropertyValue::Unsigned(u64::from(self.vendor_id)),
293 ),
294 (
295 P::MAX_APDU_LENGTH_ACCEPTED,
296 PropertyValue::Unsigned(u64::from(self.max_apdu_length)),
297 ),
298 (
299 P::SEGMENTATION_SUPPORTED,
300 PropertyValue::Enumerated(u32::from(self.segmentation_supported.to_raw())),
301 ),
302 ] {
303 if device.read_property(property, None)? != expected {
304 return Err(Error::Encoding(format!(
305 "selected Device {property:?} must match identity"
306 )));
307 }
308 }
309 if self.segmentation_supported != Segmentation::NONE
310 && device.read_property(P::APDU_SEGMENT_TIMEOUT, None)?
311 != PropertyValue::Unsigned(self.apdu_segment_timeout_ms)
312 {
313 return Err(Error::Encoding(
314 "selected Device APDU_Segment_Timeout must match identity".into(),
315 ));
316 }
317 Ok(())
318 }
319
320 pub fn segmentation(&self) -> Segmentation {
322 self.segmentation_supported
323 }
324
325 pub fn services(&self) -> &[ServiceSupported] {
327 &self.services
328 }
329
330 pub fn network_ports(&self) -> &[NetworkPortEntry] {
332 &self.network_ports
333 }
334
335 pub fn device_uuid(&self) -> [u8; 16] {
337 self.device_uuid
338 }
339
340 pub fn device_oid(&self) -> ObjectIdentifier {
342 ObjectIdentifier::new(ObjectType::DEVICE, self.instance)
343 .expect("identity instance was validated at construction")
344 }
345
346 pub fn build_database(&self) -> Result<ObjectDatabase, Error> {
352 let mut device = DeviceObject::new(DeviceConfig {
353 identity: self.clone(),
354 firmware_revision: env!("CARGO_PKG_VERSION").into(),
355 application_software_version: env!("CARGO_PKG_VERSION").into(),
356 ..DeviceConfig::default()
357 })?;
358 device.set_services_supported(&self.services);
359 device.set_device_uuid(self.device_uuid);
360 let device_oid = device.object_identifier();
361
362 let mut oids = vec![device_oid];
363 let mut ports = Vec::new();
364 for entry in &self.network_ports {
365 let port = entry.build_object()?;
366 oids.push(port.object_identifier());
367 ports.push(port);
368 }
369 device.set_object_list(oids);
370
371 let mut db = ObjectDatabase::new();
372 db.add(Box::new(device))?;
373 for port in ports {
374 db.add(Box::new(port))?;
375 }
376 Ok(db)
377 }
378}
379
380pub fn build_database_with_extra(
400 identity: &DeviceIdentity,
401 extra: Vec<Box<dyn crate::traits::BACnetObject>>,
402) -> Result<ObjectDatabase, Error> {
403 let mut device = DeviceObject::new(DeviceConfig {
404 identity: identity.clone(),
405 firmware_revision: env!("CARGO_PKG_VERSION").into(),
406 application_software_version: env!("CARGO_PKG_VERSION").into(),
407 ..DeviceConfig::default()
408 })?;
409 device.set_services_supported(&identity.services);
410 device.set_device_uuid(identity.device_uuid);
411 let device_oid = device.object_identifier();
412
413 let mut oids = vec![device_oid];
414 let mut ports = Vec::new();
415 for entry in &identity.network_ports {
416 let port = entry.build_object()?;
417 oids.push(port.object_identifier());
418 ports.push(port);
419 }
420 for obj in &extra {
421 oids.push(obj.object_identifier());
422 }
423 device.set_object_list(oids);
424
425 let mut db = ObjectDatabase::new();
426 db.add(Box::new(device))?;
427 for port in ports {
428 db.add(Box::new(port))?;
429 }
430 for obj in extra {
431 db.add(obj)?;
432 }
433 Ok(db)
434}
435
436impl Default for DeviceIdentity {
437 fn default() -> Self {
438 Self::new(1, 0)
439 .expect("default Device instance is valid")
440 .with_name("BACnet Device")
441 }
442}