1use std::borrow::Cow;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use bacnet_types::enums::{
12 ErrorClass, ErrorCode, ObjectType, PropertyIdentifier, Segmentation, ServiceSupported,
13};
14use bacnet_types::error::Error;
15use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
16
17use crate::clock::{ClockFrame, ClockReader};
18use crate::common::read_property_list_property;
19use crate::object_profile::{ObjectProfile, ProfileState, TagsPersistence};
20use crate::traits::BACnetObject;
21
22mod audit_recipient;
23mod metadata;
24pub use audit_recipient::{AuditRecipientChangeSink, AuditWriteSource, DeviceAuthority};
25
26pub const EXECUTED_SERVICES: &[ServiceSupported] = &[
36 ServiceSupported::ACKNOWLEDGE_ALARM,
37 ServiceSupported::CONFIRMED_EVENT_NOTIFICATION,
40 ServiceSupported::GET_ALARM_SUMMARY,
41 ServiceSupported::GET_ENROLLMENT_SUMMARY,
42 ServiceSupported::SUBSCRIBE_COV,
43 ServiceSupported::ATOMIC_READ_FILE,
44 ServiceSupported::ATOMIC_WRITE_FILE,
45 ServiceSupported::ADD_LIST_ELEMENT,
46 ServiceSupported::REMOVE_LIST_ELEMENT,
47 ServiceSupported::CREATE_OBJECT,
48 ServiceSupported::DELETE_OBJECT,
49 ServiceSupported::READ_PROPERTY,
50 ServiceSupported::READ_PROPERTY_MULTIPLE,
51 ServiceSupported::WRITE_PROPERTY,
52 ServiceSupported::WRITE_PROPERTY_MULTIPLE,
53 ServiceSupported::DEVICE_COMMUNICATION_CONTROL,
54 ServiceSupported::CONFIRMED_TEXT_MESSAGE,
55 ServiceSupported::REINITIALIZE_DEVICE,
56 ServiceSupported::UNCONFIRMED_EVENT_NOTIFICATION,
57 ServiceSupported::UNCONFIRMED_TEXT_MESSAGE,
58 ServiceSupported::TIME_SYNCHRONIZATION,
59 ServiceSupported::WHO_HAS,
60 ServiceSupported::WHO_IS,
61 ServiceSupported::READ_RANGE,
62 ServiceSupported::UTC_TIME_SYNCHRONIZATION,
63 ServiceSupported::LIFE_SAFETY_OPERATION,
66 ServiceSupported::SUBSCRIBE_COV_PROPERTY,
67 ServiceSupported::GET_EVENT_INFORMATION,
68 ServiceSupported::WRITE_GROUP,
70 ServiceSupported::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
71 ServiceSupported::CONFIRMED_AUDIT_NOTIFICATION,
72 ServiceSupported::UNCONFIRMED_AUDIT_NOTIFICATION,
73 ServiceSupported::AUDIT_LOG_QUERY,
74];
75
76const SERVICES_SUPPORTED_BITS: usize = 49;
79
80fn compute_services_supported(services: &[ServiceSupported]) -> Vec<u8> {
84 let mut bits = vec![0u8; SERVICES_SUPPORTED_BITS.div_ceil(8)];
85 for service in services {
86 let n = service.to_raw() as usize;
87 if n < SERVICES_SUPPORTED_BITS {
88 bits[n / 8] |= 0x80 >> (n % 8);
89 }
90 }
91 bits
92}
93
94fn compute_object_types_supported(types: &[u32]) -> Vec<u8> {
97 let max_type = types.iter().copied().max().unwrap_or(0) as usize;
98 let num_bytes = (max_type / 8) + 1;
99 let mut bitstring = vec![0u8; num_bytes];
100 for &t in types {
101 let byte_idx = (t as usize) / 8;
102 let bit_pos = 7 - ((t as usize) % 8);
103 if byte_idx < bitstring.len() {
104 bitstring[byte_idx] |= 1 << bit_pos;
105 }
106 }
107 bitstring
108}
109
110pub struct DeviceConfig {
112 pub instance: u32,
114 pub name: String,
116 pub vendor_name: String,
118 pub vendor_id: u16,
120 pub model_name: String,
122 pub firmware_revision: String,
124 pub application_software_version: String,
126 pub max_apdu_length: u32,
128 pub segmentation_supported: Segmentation,
130 pub apdu_segment_timeout: u64,
132 pub apdu_timeout: u32,
134 pub apdu_retries: u32,
136}
137
138impl Default for DeviceConfig {
139 fn default() -> Self {
140 Self {
141 instance: 1,
142 name: "BACnet Device".into(),
143 vendor_name: "Rusty BACnet".into(),
144 vendor_id: 0,
145 model_name: "rusty-bacnet".into(),
146 firmware_revision: "0.1.0".into(),
147 application_software_version: "0.1.0".into(),
148 max_apdu_length: 1476,
149 segmentation_supported: Segmentation::NONE,
150 apdu_segment_timeout: 5000,
151 apdu_timeout: 6000,
152 apdu_retries: 3,
153 }
154 }
155}
156
157pub struct DeviceObject {
159 oid: ObjectIdentifier,
160 recipient: audit_recipient::RecipientState,
161 properties: HashMap<PropertyIdentifier, PropertyValue>,
162 profile: ProfileState,
163 object_list: Vec<ObjectIdentifier>,
165 protocol_object_types_supported: Vec<u8>,
168 configured_services_supported: Vec<ServiceSupported>,
170 clock: Option<Arc<dyn ClockReader>>,
172}
173
174impl DeviceObject {
175 pub fn new(config: DeviceConfig) -> Result<Self, Error> {
177 let oid = ObjectIdentifier::new(ObjectType::DEVICE, config.instance)?;
178 let mut properties = HashMap::new();
179
180 properties.insert(
181 PropertyIdentifier::OBJECT_IDENTIFIER,
182 PropertyValue::ObjectIdentifier(oid),
183 );
184 properties.insert(
185 PropertyIdentifier::OBJECT_NAME,
186 PropertyValue::CharacterString(config.name),
187 );
188 properties.insert(
189 PropertyIdentifier::OBJECT_TYPE,
190 PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()),
191 );
192 properties.insert(
193 PropertyIdentifier::SYSTEM_STATUS,
194 PropertyValue::Enumerated(0), );
196 properties.insert(
197 PropertyIdentifier::VENDOR_NAME,
198 PropertyValue::CharacterString(config.vendor_name),
199 );
200 properties.insert(
201 PropertyIdentifier::VENDOR_IDENTIFIER,
202 PropertyValue::Unsigned(config.vendor_id as u64),
203 );
204 properties.insert(
205 PropertyIdentifier::MODEL_NAME,
206 PropertyValue::CharacterString(config.model_name),
207 );
208 properties.insert(
209 PropertyIdentifier::FIRMWARE_REVISION,
210 PropertyValue::CharacterString(config.firmware_revision),
211 );
212 properties.insert(
213 PropertyIdentifier::APPLICATION_SOFTWARE_VERSION,
214 PropertyValue::CharacterString(config.application_software_version),
215 );
216 properties.insert(
217 PropertyIdentifier::PROTOCOL_VERSION,
218 PropertyValue::Unsigned(1),
219 );
220 properties.insert(
221 PropertyIdentifier::PROTOCOL_REVISION,
222 PropertyValue::Unsigned(22), );
224 properties.insert(
225 PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
226 PropertyValue::Unsigned(config.max_apdu_length as u64),
227 );
228 properties.insert(
229 PropertyIdentifier::SEGMENTATION_SUPPORTED,
230 PropertyValue::Enumerated(config.segmentation_supported.to_raw() as u32),
231 );
232 properties.insert(
233 PropertyIdentifier::APDU_TIMEOUT,
234 PropertyValue::Unsigned(config.apdu_timeout as u64),
235 );
236 properties.insert(
237 PropertyIdentifier::NUMBER_OF_APDU_RETRIES,
238 PropertyValue::Unsigned(config.apdu_retries as u64),
239 );
240 properties.insert(
241 PropertyIdentifier::DATABASE_REVISION,
242 PropertyValue::Unsigned(0),
243 );
244 properties.insert(
245 PropertyIdentifier::DESCRIPTION,
246 PropertyValue::CharacterString(String::new()),
247 );
248
249 properties.insert(
252 PropertyIdentifier::DEVICE_ADDRESS_BINDING,
253 PropertyValue::List(Vec::new()),
254 );
255
256 properties.insert(
258 PropertyIdentifier::LAST_RESTART_REASON,
259 PropertyValue::Enumerated(0), );
261
262 properties.insert(
264 PropertyIdentifier::DEVICE_UUID,
265 PropertyValue::OctetString(vec![0u8; 16]),
266 );
267
268 if config.segmentation_supported != Segmentation::NONE {
270 if config.apdu_segment_timeout == 0 {
271 return Err(Error::Encoding(
272 "APDU_Segment_Timeout must be positive when segmentation is supported".into(),
273 ));
274 }
275 properties.insert(
276 PropertyIdentifier::APDU_SEGMENT_TIMEOUT,
277 PropertyValue::Unsigned(config.apdu_segment_timeout),
278 );
279 let max_segments_accepted = if config.segmentation_supported == Segmentation::TRANSMIT {
280 1
281 } else {
282 65
283 };
284 properties.insert(
285 PropertyIdentifier::MAX_SEGMENTS_ACCEPTED,
286 PropertyValue::Unsigned(max_segments_accepted),
287 );
288 }
289
290 let protocol_object_types_supported = compute_object_types_supported(&[
294 ObjectType::ANALOG_INPUT.to_raw(),
295 ObjectType::ANALOG_OUTPUT.to_raw(),
296 ObjectType::ANALOG_VALUE.to_raw(),
297 ObjectType::BINARY_INPUT.to_raw(),
298 ObjectType::BINARY_OUTPUT.to_raw(),
299 ObjectType::BINARY_VALUE.to_raw(),
300 ObjectType::CALENDAR.to_raw(),
301 ObjectType::COMMAND.to_raw(),
302 ObjectType::DEVICE.to_raw(),
303 ObjectType::EVENT_ENROLLMENT.to_raw(),
304 ObjectType::FILE.to_raw(),
305 ObjectType::GROUP.to_raw(),
306 ObjectType::LOOP.to_raw(),
307 ObjectType::MULTI_STATE_INPUT.to_raw(),
308 ObjectType::MULTI_STATE_OUTPUT.to_raw(),
309 ObjectType::NOTIFICATION_CLASS.to_raw(),
310 ObjectType::PROGRAM.to_raw(),
311 ObjectType::SCHEDULE.to_raw(),
312 ObjectType::AVERAGING.to_raw(),
313 ObjectType::MULTI_STATE_VALUE.to_raw(),
314 ObjectType::TREND_LOG.to_raw(),
315 ObjectType::LIFE_SAFETY_POINT.to_raw(),
316 ObjectType::LIFE_SAFETY_ZONE.to_raw(),
317 ObjectType::ACCUMULATOR.to_raw(),
318 ObjectType::PULSE_CONVERTER.to_raw(),
319 ObjectType::EVENT_LOG.to_raw(),
320 ObjectType::GLOBAL_GROUP.to_raw(),
321 ObjectType::TREND_LOG_MULTIPLE.to_raw(),
322 ObjectType::LOAD_CONTROL.to_raw(),
323 ObjectType::STRUCTURED_VIEW.to_raw(),
324 ObjectType::ACCESS_DOOR.to_raw(),
325 ObjectType::TIMER.to_raw(),
326 ObjectType::ACCESS_CREDENTIAL.to_raw(),
327 ObjectType::ACCESS_POINT.to_raw(),
328 ObjectType::ACCESS_RIGHTS.to_raw(),
329 ObjectType::ACCESS_USER.to_raw(),
330 ObjectType::ACCESS_ZONE.to_raw(),
331 ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
332 ObjectType::BITSTRING_VALUE.to_raw(),
333 ObjectType::CHARACTERSTRING_VALUE.to_raw(),
334 ObjectType::DATEPATTERN_VALUE.to_raw(),
335 ObjectType::DATE_VALUE.to_raw(),
336 ObjectType::DATETIMEPATTERN_VALUE.to_raw(),
337 ObjectType::DATETIME_VALUE.to_raw(),
338 ObjectType::INTEGER_VALUE.to_raw(),
339 ObjectType::LARGE_ANALOG_VALUE.to_raw(),
340 ObjectType::OCTETSTRING_VALUE.to_raw(),
341 ObjectType::POSITIVE_INTEGER_VALUE.to_raw(),
342 ObjectType::TIMEPATTERN_VALUE.to_raw(),
343 ObjectType::TIME_VALUE.to_raw(),
344 ObjectType::NOTIFICATION_FORWARDER.to_raw(),
345 ObjectType::ALERT_ENROLLMENT.to_raw(),
346 ObjectType::CHANNEL.to_raw(),
347 ObjectType::LIGHTING_OUTPUT.to_raw(),
348 ObjectType::BINARY_LIGHTING_OUTPUT.to_raw(),
349 ObjectType::NETWORK_PORT.to_raw(),
350 ObjectType::ELEVATOR_GROUP.to_raw(),
351 ObjectType::ESCALATOR.to_raw(),
352 ObjectType::LIFT.to_raw(),
353 ObjectType::STAGING.to_raw(),
354 ObjectType::AUDIT_LOG.to_raw(),
355 ObjectType::AUDIT_REPORTER.to_raw(),
356 ObjectType::COLOR.to_raw(),
357 ObjectType::COLOR_TEMPERATURE.to_raw(),
358 ]);
359
360 Ok(Self {
361 oid,
362 recipient: Default::default(),
363 profile: ProfileState::default(),
364 properties,
365 object_list: vec![oid], protocol_object_types_supported,
367 configured_services_supported: EXECUTED_SERVICES.to_vec(),
368 clock: None,
369 })
370 }
371
372 pub fn with_tags_persistence(
376 config: DeviceConfig,
377 persistence: Arc<dyn TagsPersistence>,
378 ) -> Result<Self, Error> {
379 let mut object = Self::new(config)?;
380 object.profile = ProfileState::persistent(object.oid, persistence)?;
381 Ok(object)
382 }
383
384 pub fn set_profile(&mut self, profile: ObjectProfile) -> Result<(), Error> {
389 self.profile.provision(profile)
390 }
391
392 pub fn wait_for_tag_saves(&self) {
395 self.profile.wait_for_saves();
396 }
397
398 pub fn set_object_list(&mut self, oids: Vec<ObjectIdentifier>) {
400 self.object_list = oids;
401 }
402
403 pub fn set_services_supported(&mut self, services: &[ServiceSupported]) {
412 self.configured_services_supported = services.to_vec();
413 }
414
415 pub fn instance(&self) -> u32 {
417 self.oid.instance_number()
418 }
419
420 pub fn set_description(&mut self, desc: impl Into<String>) {
422 self.properties.insert(
423 PropertyIdentifier::DESCRIPTION,
424 PropertyValue::CharacterString(desc.into()),
425 );
426 }
427
428 pub fn set_device_uuid(&mut self, uuid: [u8; 16]) {
436 self.properties.insert(
437 PropertyIdentifier::DEVICE_UUID,
438 PropertyValue::OctetString(uuid.to_vec()),
439 );
440 }
441
442 fn clock_frame(&self) -> Option<ClockFrame> {
443 self.clock.as_ref()?.read_clock()
444 }
445
446 fn cov_property_present(&self, property: PropertyIdentifier) -> bool {
447 match property {
448 PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS => {
449 self.configured_services_supported.iter().any(|service| {
450 matches!(
451 *service,
452 ServiceSupported::SUBSCRIBE_COV | ServiceSupported::SUBSCRIBE_COV_PROPERTY
453 )
454 })
455 }
456 PropertyIdentifier::ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS => self
457 .configured_services_supported
458 .contains(&ServiceSupported::SUBSCRIBE_COV_PROPERTY_MULTIPLE),
459 _ => true,
460 }
461 }
462
463 fn services_supported(&self) -> Vec<u8> {
464 let clock_available = self.clock_frame().is_some();
465 let services = self
466 .configured_services_supported
467 .iter()
468 .copied()
469 .filter(|service| {
470 clock_available
471 || (*service != ServiceSupported::TIME_SYNCHRONIZATION
472 && *service != ServiceSupported::UTC_TIME_SYNCHRONIZATION)
473 })
474 .collect::<Vec<_>>();
475 compute_services_supported(&services)
476 }
477}
478
479impl BACnetObject for DeviceObject {
480 fn device_authority_internal(&mut self) -> Option<DeviceAuthority<'_>> {
481 Some(DeviceAuthority(self))
482 }
483
484 fn object_identifier(&self) -> ObjectIdentifier {
485 self.oid
486 }
487
488 fn object_name(&self) -> &str {
489 match self.properties.get(&PropertyIdentifier::OBJECT_NAME) {
490 Some(PropertyValue::CharacterString(s)) => s,
491 _ => "Unknown",
492 }
493 }
494
495 fn read_property(
496 &self,
497 property: PropertyIdentifier,
498 array_index: Option<u32>,
499 ) -> Result<PropertyValue, Error> {
500 if property == PropertyIdentifier::AUDIT_NOTIFICATION_RECIPIENT {
501 return self.read_audit_recipient(array_index);
502 }
503 if property == PropertyIdentifier::OBJECT_LIST {
504 return match array_index {
505 None => {
506 let elements = self
507 .object_list
508 .iter()
509 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
510 .collect();
511 Ok(PropertyValue::List(elements))
512 }
513 Some(0) => {
514 Ok(PropertyValue::Unsigned(self.object_list.len() as u64))
516 }
517 Some(idx) => {
518 let i = (idx - 1) as usize; if i < self.object_list.len() {
520 Ok(PropertyValue::ObjectIdentifier(self.object_list[i]))
521 } else {
522 Err(Error::Protocol {
523 class: ErrorClass::PROPERTY.to_raw() as u32,
524 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
525 })
526 }
527 }
528 };
529 }
530
531 if property == PropertyIdentifier::PROPERTY_LIST {
532 return read_property_list_property(&self.property_list(), array_index);
533 }
534
535 if matches!(
536 property,
537 PropertyIdentifier::LOCAL_DATE
538 | PropertyIdentifier::LOCAL_TIME
539 | PropertyIdentifier::UTC_OFFSET
540 | PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS
541 ) {
542 let frame = self.clock_frame().ok_or(Error::Protocol {
543 class: ErrorClass::PROPERTY.to_raw() as u32,
544 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
545 })?;
546 return Ok(match property {
547 PropertyIdentifier::LOCAL_DATE => PropertyValue::Date(frame.local_date),
548 PropertyIdentifier::LOCAL_TIME => PropertyValue::Time(frame.local_time),
549 PropertyIdentifier::UTC_OFFSET => {
550 PropertyValue::Signed(i32::from(frame.utc_offset))
551 }
552 PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS => {
553 PropertyValue::Boolean(frame.daylight_savings_status)
554 }
555 _ => unreachable!(),
556 });
557 }
558
559 if property == PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED {
560 let num_bytes = self.protocol_object_types_supported.len();
561 let total_bits = num_bytes * 8;
562 let mut max_type = 0u32;
564 for (byte_idx, &byte) in self.protocol_object_types_supported.iter().enumerate() {
565 for bit in 0..8 {
566 if byte & (1 << (7 - bit)) != 0 {
567 max_type = (byte_idx * 8 + bit) as u32;
568 }
569 }
570 }
571 let used_bits = max_type as usize + 1;
572 let unused = (total_bits - used_bits) as u8;
573 return Ok(PropertyValue::BitString {
574 unused_bits: unused,
575 data: self.protocol_object_types_supported.clone(),
576 });
577 }
578
579 if property == PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED {
580 let services_supported = self.services_supported();
581 let unused = (services_supported.len() * 8 - SERVICES_SUPPORTED_BITS) as u8;
582 return Ok(PropertyValue::BitString {
583 unused_bits: unused,
584 data: services_supported,
585 });
586 }
587
588 if matches!(
589 property,
590 PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS
591 | PropertyIdentifier::ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS
592 ) {
593 return if self.cov_property_present(property) {
598 Ok(PropertyValue::ApplicationData(Vec::new()))
599 } else {
600 Err(Error::Protocol {
601 class: ErrorClass::PROPERTY.to_raw() as u32,
602 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
603 })
604 };
605 }
606
607 if let Some(value) = self.profile.read(property, array_index) {
608 return value;
609 }
610 self.properties
611 .get(&property)
612 .cloned()
613 .ok_or(Error::Protocol {
614 class: ErrorClass::PROPERTY.to_raw() as u32,
615 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
616 })
617 }
618
619 fn write_property(
620 &mut self,
621 property: PropertyIdentifier,
622 array_index: Option<u32>,
623 value: PropertyValue,
624 _priority: Option<u8>,
625 ) -> Result<(), Error> {
626 if property == PropertyIdentifier::AUDIT_NOTIFICATION_RECIPIENT {
627 return self.write_audit_recipient(array_index, value, _priority, None);
628 }
629 if let Some(result) = self.profile.write(property, array_index, &value) {
630 return result;
631 }
632 if property == PropertyIdentifier::DESCRIPTION {
633 if array_index.is_some() {
634 return Err(Error::Protocol {
635 class: ErrorClass::PROPERTY.to_raw() as u32,
636 code: ErrorCode::PROPERTY_IS_NOT_AN_ARRAY.to_raw() as u32,
637 });
638 }
639 if value == PropertyValue::Null {
642 return Ok(());
643 }
644 if let PropertyValue::CharacterString(_) = &value {
645 self.properties.insert(property, value);
646 return Ok(());
647 }
648 return Err(Error::Protocol {
649 class: ErrorClass::PROPERTY.to_raw() as u32,
650 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
651 });
652 }
653 Err(crate::common::unhandled_write_error(
654 self.property_metadata().as_ref(),
655 property,
656 array_index,
657 ))
658 }
659
660 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
661 self.profile.capability()
662 }
663
664 fn advance_monotonic_time_internal(&mut self, now: std::time::Duration) -> bool {
665 self.profile.expire(now);
666 false
667 }
668
669 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
670 metadata::for_object(self)
671 }
672
673 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
674 Cow::Owned(
677 self.property_metadata()
678 .iter()
679 .map(|row| row.property_identifier)
680 .collect(),
681 )
682 }
683
684 fn is_createable(&self) -> bool {
686 false
687 }
688 fn is_deleteable(&self) -> bool {
689 false
690 }
691
692 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
693 self.clock = clock;
694 }
695}
696
697#[cfg(test)]
698mod tests;
699
700#[cfg(test)]
701mod service_profile_tests;