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::traits::BACnetObject;
20
21mod audit_recipient;
22mod metadata;
23pub use audit_recipient::{AuditRecipientChangeSink, AuditWriteSource, DeviceAuthority};
24
25pub const EXECUTED_SERVICES: &[ServiceSupported] = &[
35 ServiceSupported::ACKNOWLEDGE_ALARM,
36 ServiceSupported::CONFIRMED_EVENT_NOTIFICATION,
39 ServiceSupported::GET_ALARM_SUMMARY,
40 ServiceSupported::GET_ENROLLMENT_SUMMARY,
41 ServiceSupported::SUBSCRIBE_COV,
42 ServiceSupported::ATOMIC_READ_FILE,
43 ServiceSupported::ATOMIC_WRITE_FILE,
44 ServiceSupported::ADD_LIST_ELEMENT,
45 ServiceSupported::REMOVE_LIST_ELEMENT,
46 ServiceSupported::CREATE_OBJECT,
47 ServiceSupported::DELETE_OBJECT,
48 ServiceSupported::READ_PROPERTY,
49 ServiceSupported::READ_PROPERTY_MULTIPLE,
50 ServiceSupported::WRITE_PROPERTY,
51 ServiceSupported::WRITE_PROPERTY_MULTIPLE,
52 ServiceSupported::DEVICE_COMMUNICATION_CONTROL,
53 ServiceSupported::CONFIRMED_TEXT_MESSAGE,
54 ServiceSupported::REINITIALIZE_DEVICE,
55 ServiceSupported::UNCONFIRMED_EVENT_NOTIFICATION,
56 ServiceSupported::UNCONFIRMED_TEXT_MESSAGE,
57 ServiceSupported::TIME_SYNCHRONIZATION,
58 ServiceSupported::WHO_HAS,
59 ServiceSupported::WHO_IS,
60 ServiceSupported::READ_RANGE,
61 ServiceSupported::UTC_TIME_SYNCHRONIZATION,
62 ServiceSupported::LIFE_SAFETY_OPERATION,
65 ServiceSupported::SUBSCRIBE_COV_PROPERTY,
66 ServiceSupported::GET_EVENT_INFORMATION,
67 ServiceSupported::WRITE_GROUP,
69 ServiceSupported::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
70 ServiceSupported::CONFIRMED_AUDIT_NOTIFICATION,
71 ServiceSupported::UNCONFIRMED_AUDIT_NOTIFICATION,
72 ServiceSupported::AUDIT_LOG_QUERY,
73];
74
75const SERVICES_SUPPORTED_BITS: usize = 49;
78
79fn compute_services_supported(services: &[ServiceSupported]) -> Vec<u8> {
83 let mut bits = vec![0u8; SERVICES_SUPPORTED_BITS.div_ceil(8)];
84 for service in services {
85 let n = service.to_raw() as usize;
86 if n < SERVICES_SUPPORTED_BITS {
87 bits[n / 8] |= 0x80 >> (n % 8);
88 }
89 }
90 bits
91}
92
93fn compute_object_types_supported(types: &[u32]) -> Vec<u8> {
96 let max_type = types.iter().copied().max().unwrap_or(0) as usize;
97 let num_bytes = (max_type / 8) + 1;
98 let mut bitstring = vec![0u8; num_bytes];
99 for &t in types {
100 let byte_idx = (t as usize) / 8;
101 let bit_pos = 7 - ((t as usize) % 8);
102 if byte_idx < bitstring.len() {
103 bitstring[byte_idx] |= 1 << bit_pos;
104 }
105 }
106 bitstring
107}
108
109pub struct DeviceConfig {
111 pub instance: u32,
113 pub name: String,
115 pub vendor_name: String,
117 pub vendor_id: u16,
119 pub model_name: String,
121 pub firmware_revision: String,
123 pub application_software_version: String,
125 pub max_apdu_length: u32,
127 pub segmentation_supported: Segmentation,
129 pub apdu_timeout: u32,
131 pub apdu_retries: u32,
133}
134
135impl Default for DeviceConfig {
136 fn default() -> Self {
137 Self {
138 instance: 1,
139 name: "BACnet Device".into(),
140 vendor_name: "Rusty BACnet".into(),
141 vendor_id: 0,
142 model_name: "rusty-bacnet".into(),
143 firmware_revision: "0.1.0".into(),
144 application_software_version: "0.1.0".into(),
145 max_apdu_length: 1476,
146 segmentation_supported: Segmentation::NONE,
147 apdu_timeout: 6000,
148 apdu_retries: 3,
149 }
150 }
151}
152
153pub struct DeviceObject {
155 oid: ObjectIdentifier,
156 recipient: audit_recipient::RecipientState,
157 properties: HashMap<PropertyIdentifier, PropertyValue>,
158 object_list: Vec<ObjectIdentifier>,
160 protocol_object_types_supported: Vec<u8>,
163 configured_services_supported: Vec<ServiceSupported>,
165 clock: Option<Arc<dyn ClockReader>>,
167}
168
169impl DeviceObject {
170 pub fn new(config: DeviceConfig) -> Result<Self, Error> {
172 let oid = ObjectIdentifier::new(ObjectType::DEVICE, config.instance)?;
173 let mut properties = HashMap::new();
174
175 properties.insert(
176 PropertyIdentifier::OBJECT_IDENTIFIER,
177 PropertyValue::ObjectIdentifier(oid),
178 );
179 properties.insert(
180 PropertyIdentifier::OBJECT_NAME,
181 PropertyValue::CharacterString(config.name),
182 );
183 properties.insert(
184 PropertyIdentifier::OBJECT_TYPE,
185 PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()),
186 );
187 properties.insert(
188 PropertyIdentifier::SYSTEM_STATUS,
189 PropertyValue::Enumerated(0), );
191 properties.insert(
192 PropertyIdentifier::VENDOR_NAME,
193 PropertyValue::CharacterString(config.vendor_name),
194 );
195 properties.insert(
196 PropertyIdentifier::VENDOR_IDENTIFIER,
197 PropertyValue::Unsigned(config.vendor_id as u64),
198 );
199 properties.insert(
200 PropertyIdentifier::MODEL_NAME,
201 PropertyValue::CharacterString(config.model_name),
202 );
203 properties.insert(
204 PropertyIdentifier::FIRMWARE_REVISION,
205 PropertyValue::CharacterString(config.firmware_revision),
206 );
207 properties.insert(
208 PropertyIdentifier::APPLICATION_SOFTWARE_VERSION,
209 PropertyValue::CharacterString(config.application_software_version),
210 );
211 properties.insert(
212 PropertyIdentifier::PROTOCOL_VERSION,
213 PropertyValue::Unsigned(1),
214 );
215 properties.insert(
216 PropertyIdentifier::PROTOCOL_REVISION,
217 PropertyValue::Unsigned(22), );
219 properties.insert(
220 PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
221 PropertyValue::Unsigned(config.max_apdu_length as u64),
222 );
223 properties.insert(
224 PropertyIdentifier::SEGMENTATION_SUPPORTED,
225 PropertyValue::Enumerated(config.segmentation_supported.to_raw() as u32),
226 );
227 properties.insert(
228 PropertyIdentifier::APDU_TIMEOUT,
229 PropertyValue::Unsigned(config.apdu_timeout as u64),
230 );
231 properties.insert(
232 PropertyIdentifier::NUMBER_OF_APDU_RETRIES,
233 PropertyValue::Unsigned(config.apdu_retries as u64),
234 );
235 properties.insert(
236 PropertyIdentifier::DATABASE_REVISION,
237 PropertyValue::Unsigned(0),
238 );
239 properties.insert(
240 PropertyIdentifier::DESCRIPTION,
241 PropertyValue::CharacterString(String::new()),
242 );
243
244 properties.insert(
247 PropertyIdentifier::DEVICE_ADDRESS_BINDING,
248 PropertyValue::List(Vec::new()),
249 );
250
251 properties.insert(
253 PropertyIdentifier::LAST_RESTART_REASON,
254 PropertyValue::Enumerated(0), );
256
257 properties.insert(
259 PropertyIdentifier::DEVICE_UUID,
260 PropertyValue::OctetString(vec![0u8; 16]),
261 );
262
263 if config.segmentation_supported != Segmentation::NONE {
265 let max_segments_accepted = if config.segmentation_supported == Segmentation::TRANSMIT {
266 1
267 } else {
268 65
269 };
270 properties.insert(
271 PropertyIdentifier::MAX_SEGMENTS_ACCEPTED,
272 PropertyValue::Unsigned(max_segments_accepted),
273 );
274 }
275
276 let protocol_object_types_supported = compute_object_types_supported(&[
280 ObjectType::ANALOG_INPUT.to_raw(),
281 ObjectType::ANALOG_OUTPUT.to_raw(),
282 ObjectType::ANALOG_VALUE.to_raw(),
283 ObjectType::BINARY_INPUT.to_raw(),
284 ObjectType::BINARY_OUTPUT.to_raw(),
285 ObjectType::BINARY_VALUE.to_raw(),
286 ObjectType::CALENDAR.to_raw(),
287 ObjectType::COMMAND.to_raw(),
288 ObjectType::DEVICE.to_raw(),
289 ObjectType::EVENT_ENROLLMENT.to_raw(),
290 ObjectType::FILE.to_raw(),
291 ObjectType::GROUP.to_raw(),
292 ObjectType::LOOP.to_raw(),
293 ObjectType::MULTI_STATE_INPUT.to_raw(),
294 ObjectType::MULTI_STATE_OUTPUT.to_raw(),
295 ObjectType::NOTIFICATION_CLASS.to_raw(),
296 ObjectType::PROGRAM.to_raw(),
297 ObjectType::SCHEDULE.to_raw(),
298 ObjectType::AVERAGING.to_raw(),
299 ObjectType::MULTI_STATE_VALUE.to_raw(),
300 ObjectType::TREND_LOG.to_raw(),
301 ObjectType::LIFE_SAFETY_POINT.to_raw(),
302 ObjectType::LIFE_SAFETY_ZONE.to_raw(),
303 ObjectType::ACCUMULATOR.to_raw(),
304 ObjectType::PULSE_CONVERTER.to_raw(),
305 ObjectType::EVENT_LOG.to_raw(),
306 ObjectType::GLOBAL_GROUP.to_raw(),
307 ObjectType::TREND_LOG_MULTIPLE.to_raw(),
308 ObjectType::LOAD_CONTROL.to_raw(),
309 ObjectType::STRUCTURED_VIEW.to_raw(),
310 ObjectType::ACCESS_DOOR.to_raw(),
311 ObjectType::TIMER.to_raw(),
312 ObjectType::ACCESS_CREDENTIAL.to_raw(),
313 ObjectType::ACCESS_POINT.to_raw(),
314 ObjectType::ACCESS_RIGHTS.to_raw(),
315 ObjectType::ACCESS_USER.to_raw(),
316 ObjectType::ACCESS_ZONE.to_raw(),
317 ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
318 ObjectType::BITSTRING_VALUE.to_raw(),
319 ObjectType::CHARACTERSTRING_VALUE.to_raw(),
320 ObjectType::DATEPATTERN_VALUE.to_raw(),
321 ObjectType::DATE_VALUE.to_raw(),
322 ObjectType::DATETIMEPATTERN_VALUE.to_raw(),
323 ObjectType::DATETIME_VALUE.to_raw(),
324 ObjectType::INTEGER_VALUE.to_raw(),
325 ObjectType::LARGE_ANALOG_VALUE.to_raw(),
326 ObjectType::OCTETSTRING_VALUE.to_raw(),
327 ObjectType::POSITIVE_INTEGER_VALUE.to_raw(),
328 ObjectType::TIMEPATTERN_VALUE.to_raw(),
329 ObjectType::TIME_VALUE.to_raw(),
330 ObjectType::NOTIFICATION_FORWARDER.to_raw(),
331 ObjectType::ALERT_ENROLLMENT.to_raw(),
332 ObjectType::CHANNEL.to_raw(),
333 ObjectType::LIGHTING_OUTPUT.to_raw(),
334 ObjectType::BINARY_LIGHTING_OUTPUT.to_raw(),
335 ObjectType::NETWORK_PORT.to_raw(),
336 ObjectType::ELEVATOR_GROUP.to_raw(),
337 ObjectType::ESCALATOR.to_raw(),
338 ObjectType::LIFT.to_raw(),
339 ObjectType::STAGING.to_raw(),
340 ObjectType::AUDIT_LOG.to_raw(),
341 ObjectType::AUDIT_REPORTER.to_raw(),
342 ObjectType::COLOR.to_raw(),
343 ObjectType::COLOR_TEMPERATURE.to_raw(),
344 ]);
345
346 Ok(Self {
347 oid,
348 recipient: Default::default(),
349 properties,
350 object_list: vec![oid], protocol_object_types_supported,
352 configured_services_supported: EXECUTED_SERVICES.to_vec(),
353 clock: None,
354 })
355 }
356
357 pub fn set_object_list(&mut self, oids: Vec<ObjectIdentifier>) {
359 self.object_list = oids;
360 }
361
362 pub fn set_services_supported(&mut self, services: &[ServiceSupported]) {
371 self.configured_services_supported = services.to_vec();
372 }
373
374 pub fn instance(&self) -> u32 {
376 self.oid.instance_number()
377 }
378
379 pub fn set_description(&mut self, desc: impl Into<String>) {
381 self.properties.insert(
382 PropertyIdentifier::DESCRIPTION,
383 PropertyValue::CharacterString(desc.into()),
384 );
385 }
386
387 pub fn set_device_uuid(&mut self, uuid: [u8; 16]) {
395 self.properties.insert(
396 PropertyIdentifier::DEVICE_UUID,
397 PropertyValue::OctetString(uuid.to_vec()),
398 );
399 }
400
401 fn clock_frame(&self) -> Option<ClockFrame> {
402 self.clock.as_ref()?.read_clock()
403 }
404
405 fn cov_property_present(&self, property: PropertyIdentifier) -> bool {
406 match property {
407 PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS => {
408 self.configured_services_supported.iter().any(|service| {
409 matches!(
410 *service,
411 ServiceSupported::SUBSCRIBE_COV | ServiceSupported::SUBSCRIBE_COV_PROPERTY
412 )
413 })
414 }
415 PropertyIdentifier::ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS => self
416 .configured_services_supported
417 .contains(&ServiceSupported::SUBSCRIBE_COV_PROPERTY_MULTIPLE),
418 _ => true,
419 }
420 }
421
422 fn services_supported(&self) -> Vec<u8> {
423 let clock_available = self.clock_frame().is_some();
424 let services = self
425 .configured_services_supported
426 .iter()
427 .copied()
428 .filter(|service| {
429 clock_available
430 || (*service != ServiceSupported::TIME_SYNCHRONIZATION
431 && *service != ServiceSupported::UTC_TIME_SYNCHRONIZATION)
432 })
433 .collect::<Vec<_>>();
434 compute_services_supported(&services)
435 }
436}
437
438impl BACnetObject for DeviceObject {
439 fn device_authority_internal(&mut self) -> Option<DeviceAuthority<'_>> {
440 Some(DeviceAuthority(self))
441 }
442
443 fn object_identifier(&self) -> ObjectIdentifier {
444 self.oid
445 }
446
447 fn object_name(&self) -> &str {
448 match self.properties.get(&PropertyIdentifier::OBJECT_NAME) {
449 Some(PropertyValue::CharacterString(s)) => s,
450 _ => "Unknown",
451 }
452 }
453
454 fn read_property(
455 &self,
456 property: PropertyIdentifier,
457 array_index: Option<u32>,
458 ) -> Result<PropertyValue, Error> {
459 if property == PropertyIdentifier::AUDIT_NOTIFICATION_RECIPIENT {
460 return self.read_audit_recipient(array_index);
461 }
462 if property == PropertyIdentifier::OBJECT_LIST {
463 return match array_index {
464 None => {
465 let elements = self
466 .object_list
467 .iter()
468 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
469 .collect();
470 Ok(PropertyValue::List(elements))
471 }
472 Some(0) => {
473 Ok(PropertyValue::Unsigned(self.object_list.len() as u64))
475 }
476 Some(idx) => {
477 let i = (idx - 1) as usize; if i < self.object_list.len() {
479 Ok(PropertyValue::ObjectIdentifier(self.object_list[i]))
480 } else {
481 Err(Error::Protocol {
482 class: ErrorClass::PROPERTY.to_raw() as u32,
483 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
484 })
485 }
486 }
487 };
488 }
489
490 if property == PropertyIdentifier::PROPERTY_LIST {
491 return read_property_list_property(&self.property_list(), array_index);
492 }
493
494 if matches!(
495 property,
496 PropertyIdentifier::LOCAL_DATE
497 | PropertyIdentifier::LOCAL_TIME
498 | PropertyIdentifier::UTC_OFFSET
499 | PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS
500 ) {
501 let frame = self.clock_frame().ok_or(Error::Protocol {
502 class: ErrorClass::PROPERTY.to_raw() as u32,
503 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
504 })?;
505 return Ok(match property {
506 PropertyIdentifier::LOCAL_DATE => PropertyValue::Date(frame.local_date),
507 PropertyIdentifier::LOCAL_TIME => PropertyValue::Time(frame.local_time),
508 PropertyIdentifier::UTC_OFFSET => {
509 PropertyValue::Signed(i32::from(frame.utc_offset))
510 }
511 PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS => {
512 PropertyValue::Boolean(frame.daylight_savings_status)
513 }
514 _ => unreachable!(),
515 });
516 }
517
518 if property == PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED {
519 let num_bytes = self.protocol_object_types_supported.len();
520 let total_bits = num_bytes * 8;
521 let mut max_type = 0u32;
523 for (byte_idx, &byte) in self.protocol_object_types_supported.iter().enumerate() {
524 for bit in 0..8 {
525 if byte & (1 << (7 - bit)) != 0 {
526 max_type = (byte_idx * 8 + bit) as u32;
527 }
528 }
529 }
530 let used_bits = max_type as usize + 1;
531 let unused = (total_bits - used_bits) as u8;
532 return Ok(PropertyValue::BitString {
533 unused_bits: unused,
534 data: self.protocol_object_types_supported.clone(),
535 });
536 }
537
538 if property == PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED {
539 let services_supported = self.services_supported();
540 let unused = (services_supported.len() * 8 - SERVICES_SUPPORTED_BITS) as u8;
541 return Ok(PropertyValue::BitString {
542 unused_bits: unused,
543 data: services_supported,
544 });
545 }
546
547 if matches!(
548 property,
549 PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS
550 | PropertyIdentifier::ACTIVE_COV_MULTIPLE_SUBSCRIPTIONS
551 ) {
552 return if self.cov_property_present(property) {
557 Ok(PropertyValue::ApplicationData(Vec::new()))
558 } else {
559 Err(Error::Protocol {
560 class: ErrorClass::PROPERTY.to_raw() as u32,
561 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
562 })
563 };
564 }
565
566 self.properties
567 .get(&property)
568 .cloned()
569 .ok_or(Error::Protocol {
570 class: ErrorClass::PROPERTY.to_raw() as u32,
571 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
572 })
573 }
574
575 fn write_property(
576 &mut self,
577 property: PropertyIdentifier,
578 array_index: Option<u32>,
579 value: PropertyValue,
580 _priority: Option<u8>,
581 ) -> Result<(), Error> {
582 if property == PropertyIdentifier::AUDIT_NOTIFICATION_RECIPIENT {
583 return self.write_audit_recipient(array_index, value, _priority, None);
584 }
585 if property == PropertyIdentifier::DESCRIPTION {
586 if array_index.is_some() {
587 return Err(Error::Protocol {
588 class: ErrorClass::PROPERTY.to_raw() as u32,
589 code: ErrorCode::PROPERTY_IS_NOT_AN_ARRAY.to_raw() as u32,
590 });
591 }
592 if value == PropertyValue::Null {
595 return Ok(());
596 }
597 if let PropertyValue::CharacterString(_) = &value {
598 self.properties.insert(property, value);
599 return Ok(());
600 }
601 return Err(Error::Protocol {
602 class: ErrorClass::PROPERTY.to_raw() as u32,
603 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
604 });
605 }
606 Err(crate::common::unhandled_write_error(
607 self.property_metadata().as_ref(),
608 property,
609 array_index,
610 ))
611 }
612
613 fn property_metadata(&self) -> Cow<'_, [crate::property_metadata::PropertyMetadata]> {
614 metadata::for_object(self)
615 }
616
617 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
618 Cow::Owned(
621 self.property_metadata()
622 .iter()
623 .map(|row| row.property_identifier)
624 .collect(),
625 )
626 }
627
628 fn is_createable(&self) -> bool {
630 false
631 }
632 fn is_deleteable(&self) -> bool {
633 false
634 }
635
636 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
637 self.clock = clock;
638 }
639}
640
641#[cfg(test)]
642mod tests;
643
644#[cfg(test)]
645mod service_profile_tests;