1use std::borrow::Cow;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11use bacnet_types::constructed::BACnetCOVSubscription;
12use bacnet_types::enums::{
13 ErrorClass, ErrorCode, ObjectType, PropertyIdentifier, Segmentation, ServiceSupported,
14};
15use bacnet_types::error::Error;
16use bacnet_types::primitives::{ObjectIdentifier, PropertyValue};
17
18use crate::clock::{ClockFrame, ClockReader};
19use crate::common::read_property_list_property;
20use crate::traits::BACnetObject;
21
22pub const EXECUTED_SERVICES: &[ServiceSupported] = &[
32 ServiceSupported::ACKNOWLEDGE_ALARM,
33 ServiceSupported::GET_ALARM_SUMMARY,
34 ServiceSupported::GET_ENROLLMENT_SUMMARY,
35 ServiceSupported::SUBSCRIBE_COV,
36 ServiceSupported::ATOMIC_READ_FILE,
37 ServiceSupported::ATOMIC_WRITE_FILE,
38 ServiceSupported::ADD_LIST_ELEMENT,
39 ServiceSupported::REMOVE_LIST_ELEMENT,
40 ServiceSupported::CREATE_OBJECT,
41 ServiceSupported::DELETE_OBJECT,
42 ServiceSupported::READ_PROPERTY,
43 ServiceSupported::READ_PROPERTY_MULTIPLE,
44 ServiceSupported::WRITE_PROPERTY,
45 ServiceSupported::WRITE_PROPERTY_MULTIPLE,
46 ServiceSupported::DEVICE_COMMUNICATION_CONTROL,
47 ServiceSupported::CONFIRMED_TEXT_MESSAGE,
48 ServiceSupported::REINITIALIZE_DEVICE,
49 ServiceSupported::UNCONFIRMED_TEXT_MESSAGE,
50 ServiceSupported::TIME_SYNCHRONIZATION,
51 ServiceSupported::WHO_HAS,
52 ServiceSupported::WHO_IS,
53 ServiceSupported::READ_RANGE,
54 ServiceSupported::UTC_TIME_SYNCHRONIZATION,
55 ServiceSupported::LIFE_SAFETY_OPERATION,
58 ServiceSupported::SUBSCRIBE_COV_PROPERTY,
59 ServiceSupported::GET_EVENT_INFORMATION,
60 ServiceSupported::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
61 ServiceSupported::CONFIRMED_AUDIT_NOTIFICATION,
62 ServiceSupported::UNCONFIRMED_AUDIT_NOTIFICATION,
63 ServiceSupported::AUDIT_LOG_QUERY,
64];
65
66const SERVICES_SUPPORTED_BITS: usize = 49;
69
70fn compute_services_supported(services: &[ServiceSupported]) -> Vec<u8> {
74 let mut bits = vec![0u8; SERVICES_SUPPORTED_BITS.div_ceil(8)];
75 for service in services {
76 let n = service.to_raw() as usize;
77 if n < SERVICES_SUPPORTED_BITS {
78 bits[n / 8] |= 0x80 >> (n % 8);
79 }
80 }
81 bits
82}
83
84fn compute_object_types_supported(types: &[u32]) -> Vec<u8> {
87 let max_type = types.iter().copied().max().unwrap_or(0) as usize;
88 let num_bytes = (max_type / 8) + 1;
89 let mut bitstring = vec![0u8; num_bytes];
90 for &t in types {
91 let byte_idx = (t as usize) / 8;
92 let bit_pos = 7 - ((t as usize) % 8);
93 if byte_idx < bitstring.len() {
94 bitstring[byte_idx] |= 1 << bit_pos;
95 }
96 }
97 bitstring
98}
99
100pub struct DeviceConfig {
102 pub instance: u32,
104 pub name: String,
106 pub vendor_name: String,
108 pub vendor_id: u16,
110 pub model_name: String,
112 pub firmware_revision: String,
114 pub application_software_version: String,
116 pub max_apdu_length: u32,
118 pub segmentation_supported: Segmentation,
120 pub apdu_timeout: u32,
122 pub apdu_retries: u32,
124}
125
126impl Default for DeviceConfig {
127 fn default() -> Self {
128 Self {
129 instance: 1,
130 name: "BACnet Device".into(),
131 vendor_name: "Rusty BACnet".into(),
132 vendor_id: 0,
133 model_name: "rusty-bacnet".into(),
134 firmware_revision: "0.1.0".into(),
135 application_software_version: "0.1.0".into(),
136 max_apdu_length: 1476,
137 segmentation_supported: Segmentation::NONE,
138 apdu_timeout: 6000,
139 apdu_retries: 3,
140 }
141 }
142}
143
144pub struct DeviceObject {
146 oid: ObjectIdentifier,
147 properties: HashMap<PropertyIdentifier, PropertyValue>,
148 object_list: Vec<ObjectIdentifier>,
150 protocol_object_types_supported: Vec<u8>,
153 configured_services_supported: Vec<ServiceSupported>,
155 clock: Option<Arc<dyn ClockReader>>,
157 active_cov_subscriptions: Vec<BACnetCOVSubscription>,
159}
160
161impl DeviceObject {
162 pub fn new(config: DeviceConfig) -> Result<Self, Error> {
164 let oid = ObjectIdentifier::new(ObjectType::DEVICE, config.instance)?;
165 let mut properties = HashMap::new();
166
167 properties.insert(
168 PropertyIdentifier::OBJECT_IDENTIFIER,
169 PropertyValue::ObjectIdentifier(oid),
170 );
171 properties.insert(
172 PropertyIdentifier::OBJECT_NAME,
173 PropertyValue::CharacterString(config.name),
174 );
175 properties.insert(
176 PropertyIdentifier::OBJECT_TYPE,
177 PropertyValue::Enumerated(ObjectType::DEVICE.to_raw()),
178 );
179 properties.insert(
180 PropertyIdentifier::SYSTEM_STATUS,
181 PropertyValue::Enumerated(0), );
183 properties.insert(
184 PropertyIdentifier::VENDOR_NAME,
185 PropertyValue::CharacterString(config.vendor_name),
186 );
187 properties.insert(
188 PropertyIdentifier::VENDOR_IDENTIFIER,
189 PropertyValue::Unsigned(config.vendor_id as u64),
190 );
191 properties.insert(
192 PropertyIdentifier::MODEL_NAME,
193 PropertyValue::CharacterString(config.model_name),
194 );
195 properties.insert(
196 PropertyIdentifier::FIRMWARE_REVISION,
197 PropertyValue::CharacterString(config.firmware_revision),
198 );
199 properties.insert(
200 PropertyIdentifier::APPLICATION_SOFTWARE_VERSION,
201 PropertyValue::CharacterString(config.application_software_version),
202 );
203 properties.insert(
204 PropertyIdentifier::PROTOCOL_VERSION,
205 PropertyValue::Unsigned(1),
206 );
207 properties.insert(
208 PropertyIdentifier::PROTOCOL_REVISION,
209 PropertyValue::Unsigned(22), );
211 properties.insert(
212 PropertyIdentifier::MAX_APDU_LENGTH_ACCEPTED,
213 PropertyValue::Unsigned(config.max_apdu_length as u64),
214 );
215 properties.insert(
216 PropertyIdentifier::SEGMENTATION_SUPPORTED,
217 PropertyValue::Enumerated(config.segmentation_supported.to_raw() as u32),
218 );
219 properties.insert(
220 PropertyIdentifier::APDU_TIMEOUT,
221 PropertyValue::Unsigned(config.apdu_timeout as u64),
222 );
223 properties.insert(
224 PropertyIdentifier::NUMBER_OF_APDU_RETRIES,
225 PropertyValue::Unsigned(config.apdu_retries as u64),
226 );
227 properties.insert(
228 PropertyIdentifier::DATABASE_REVISION,
229 PropertyValue::Unsigned(0),
230 );
231 properties.insert(
232 PropertyIdentifier::DESCRIPTION,
233 PropertyValue::CharacterString(String::new()),
234 );
235
236 properties.insert(
238 PropertyIdentifier::DEVICE_ADDRESS_BINDING,
239 PropertyValue::List(Vec::new()),
240 );
241
242 properties.insert(
244 PropertyIdentifier::LAST_RESTART_REASON,
245 PropertyValue::Enumerated(0), );
247
248 properties.insert(
250 PropertyIdentifier::DEVICE_UUID,
251 PropertyValue::OctetString(vec![0u8; 16]),
252 );
253
254 if config.segmentation_supported != Segmentation::NONE {
256 let max_segments_accepted = if config.segmentation_supported == Segmentation::TRANSMIT {
257 1
258 } else {
259 65
260 };
261 properties.insert(
262 PropertyIdentifier::MAX_SEGMENTS_ACCEPTED,
263 PropertyValue::Unsigned(max_segments_accepted),
264 );
265 }
266
267 let protocol_object_types_supported = compute_object_types_supported(&[
271 ObjectType::ANALOG_INPUT.to_raw(),
272 ObjectType::ANALOG_OUTPUT.to_raw(),
273 ObjectType::ANALOG_VALUE.to_raw(),
274 ObjectType::BINARY_INPUT.to_raw(),
275 ObjectType::BINARY_OUTPUT.to_raw(),
276 ObjectType::BINARY_VALUE.to_raw(),
277 ObjectType::CALENDAR.to_raw(),
278 ObjectType::COMMAND.to_raw(),
279 ObjectType::DEVICE.to_raw(),
280 ObjectType::EVENT_ENROLLMENT.to_raw(),
281 ObjectType::FILE.to_raw(),
282 ObjectType::GROUP.to_raw(),
283 ObjectType::LOOP.to_raw(),
284 ObjectType::MULTI_STATE_INPUT.to_raw(),
285 ObjectType::MULTI_STATE_OUTPUT.to_raw(),
286 ObjectType::NOTIFICATION_CLASS.to_raw(),
287 ObjectType::PROGRAM.to_raw(),
288 ObjectType::SCHEDULE.to_raw(),
289 ObjectType::AVERAGING.to_raw(),
290 ObjectType::MULTI_STATE_VALUE.to_raw(),
291 ObjectType::TREND_LOG.to_raw(),
292 ObjectType::LIFE_SAFETY_POINT.to_raw(),
293 ObjectType::LIFE_SAFETY_ZONE.to_raw(),
294 ObjectType::ACCUMULATOR.to_raw(),
295 ObjectType::PULSE_CONVERTER.to_raw(),
296 ObjectType::EVENT_LOG.to_raw(),
297 ObjectType::GLOBAL_GROUP.to_raw(),
298 ObjectType::TREND_LOG_MULTIPLE.to_raw(),
299 ObjectType::LOAD_CONTROL.to_raw(),
300 ObjectType::STRUCTURED_VIEW.to_raw(),
301 ObjectType::ACCESS_DOOR.to_raw(),
302 ObjectType::TIMER.to_raw(),
303 ObjectType::ACCESS_CREDENTIAL.to_raw(),
304 ObjectType::ACCESS_POINT.to_raw(),
305 ObjectType::ACCESS_RIGHTS.to_raw(),
306 ObjectType::ACCESS_USER.to_raw(),
307 ObjectType::ACCESS_ZONE.to_raw(),
308 ObjectType::CREDENTIAL_DATA_INPUT.to_raw(),
309 ObjectType::BITSTRING_VALUE.to_raw(),
310 ObjectType::CHARACTERSTRING_VALUE.to_raw(),
311 ObjectType::DATEPATTERN_VALUE.to_raw(),
312 ObjectType::DATE_VALUE.to_raw(),
313 ObjectType::DATETIMEPATTERN_VALUE.to_raw(),
314 ObjectType::DATETIME_VALUE.to_raw(),
315 ObjectType::INTEGER_VALUE.to_raw(),
316 ObjectType::LARGE_ANALOG_VALUE.to_raw(),
317 ObjectType::OCTETSTRING_VALUE.to_raw(),
318 ObjectType::POSITIVE_INTEGER_VALUE.to_raw(),
319 ObjectType::TIMEPATTERN_VALUE.to_raw(),
320 ObjectType::TIME_VALUE.to_raw(),
321 ObjectType::ALERT_ENROLLMENT.to_raw(),
322 ObjectType::LIGHTING_OUTPUT.to_raw(),
323 ObjectType::BINARY_LIGHTING_OUTPUT.to_raw(),
324 ObjectType::NETWORK_PORT.to_raw(),
325 ObjectType::ELEVATOR_GROUP.to_raw(),
326 ObjectType::ESCALATOR.to_raw(),
327 ObjectType::LIFT.to_raw(),
328 ObjectType::STAGING.to_raw(),
329 ObjectType::AUDIT_LOG.to_raw(),
330 ObjectType::AUDIT_REPORTER.to_raw(),
331 ObjectType::COLOR.to_raw(),
332 ObjectType::COLOR_TEMPERATURE.to_raw(),
333 ]);
334
335 Ok(Self {
336 oid,
337 properties,
338 object_list: vec![oid], protocol_object_types_supported,
340 configured_services_supported: EXECUTED_SERVICES.to_vec(),
341 clock: None,
342 active_cov_subscriptions: Vec::new(),
343 })
344 }
345
346 pub fn set_object_list(&mut self, oids: Vec<ObjectIdentifier>) {
348 self.object_list = oids;
349 }
350
351 pub fn set_services_supported(&mut self, services: &[ServiceSupported]) {
358 self.configured_services_supported = services.to_vec();
359 }
360
361 pub fn instance(&self) -> u32 {
363 self.oid.instance_number()
364 }
365
366 pub fn set_description(&mut self, desc: impl Into<String>) {
368 self.properties.insert(
369 PropertyIdentifier::DESCRIPTION,
370 PropertyValue::CharacterString(desc.into()),
371 );
372 }
373
374 pub fn set_active_cov_subscriptions(&mut self, subs: Vec<BACnetCOVSubscription>) {
376 self.active_cov_subscriptions = subs;
377 }
378
379 pub fn add_cov_subscription(&mut self, sub: BACnetCOVSubscription) {
381 self.active_cov_subscriptions.push(sub);
382 }
383
384 fn clock_frame(&self) -> Option<ClockFrame> {
385 self.clock.as_ref()?.read_clock()
386 }
387
388 fn services_supported(&self) -> Vec<u8> {
389 let clock_available = self.clock_frame().is_some();
390 let services = self
391 .configured_services_supported
392 .iter()
393 .copied()
394 .filter(|service| {
395 clock_available
396 || (*service != ServiceSupported::TIME_SYNCHRONIZATION
397 && *service != ServiceSupported::UTC_TIME_SYNCHRONIZATION)
398 })
399 .collect::<Vec<_>>();
400 compute_services_supported(&services)
401 }
402}
403
404impl BACnetObject for DeviceObject {
405 fn object_identifier(&self) -> ObjectIdentifier {
406 self.oid
407 }
408
409 fn object_name(&self) -> &str {
410 match self.properties.get(&PropertyIdentifier::OBJECT_NAME) {
411 Some(PropertyValue::CharacterString(s)) => s,
412 _ => "Unknown",
413 }
414 }
415
416 fn read_property(
417 &self,
418 property: PropertyIdentifier,
419 array_index: Option<u32>,
420 ) -> Result<PropertyValue, Error> {
421 if property == PropertyIdentifier::OBJECT_LIST {
422 return match array_index {
423 None => {
424 let elements = self
425 .object_list
426 .iter()
427 .map(|oid| PropertyValue::ObjectIdentifier(*oid))
428 .collect();
429 Ok(PropertyValue::List(elements))
430 }
431 Some(0) => {
432 Ok(PropertyValue::Unsigned(self.object_list.len() as u64))
434 }
435 Some(idx) => {
436 let i = (idx - 1) as usize; if i < self.object_list.len() {
438 Ok(PropertyValue::ObjectIdentifier(self.object_list[i]))
439 } else {
440 Err(Error::Protocol {
441 class: ErrorClass::PROPERTY.to_raw() as u32,
442 code: ErrorCode::INVALID_ARRAY_INDEX.to_raw() as u32,
443 })
444 }
445 }
446 };
447 }
448
449 if property == PropertyIdentifier::PROPERTY_LIST {
450 return read_property_list_property(&self.property_list(), array_index);
451 }
452
453 if matches!(
454 property,
455 PropertyIdentifier::LOCAL_DATE
456 | PropertyIdentifier::LOCAL_TIME
457 | PropertyIdentifier::UTC_OFFSET
458 | PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS
459 ) {
460 let frame = self.clock_frame().ok_or(Error::Protocol {
461 class: ErrorClass::PROPERTY.to_raw() as u32,
462 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
463 })?;
464 return Ok(match property {
465 PropertyIdentifier::LOCAL_DATE => PropertyValue::Date(frame.local_date),
466 PropertyIdentifier::LOCAL_TIME => PropertyValue::Time(frame.local_time),
467 PropertyIdentifier::UTC_OFFSET => {
468 PropertyValue::Signed(i32::from(frame.utc_offset))
469 }
470 PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS => {
471 PropertyValue::Boolean(frame.daylight_savings_status)
472 }
473 _ => unreachable!(),
474 });
475 }
476
477 if property == PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED {
478 let num_bytes = self.protocol_object_types_supported.len();
479 let total_bits = num_bytes * 8;
480 let mut max_type = 0u32;
482 for (byte_idx, &byte) in self.protocol_object_types_supported.iter().enumerate() {
483 for bit in 0..8 {
484 if byte & (1 << (7 - bit)) != 0 {
485 max_type = (byte_idx * 8 + bit) as u32;
486 }
487 }
488 }
489 let used_bits = max_type as usize + 1;
490 let unused = (total_bits - used_bits) as u8;
491 return Ok(PropertyValue::BitString {
492 unused_bits: unused,
493 data: self.protocol_object_types_supported.clone(),
494 });
495 }
496
497 if property == PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED {
498 let services_supported = self.services_supported();
499 let unused = (services_supported.len() * 8 - SERVICES_SUPPORTED_BITS) as u8;
500 return Ok(PropertyValue::BitString {
501 unused_bits: unused,
502 data: services_supported,
503 });
504 }
505
506 if property == PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS {
507 let mut buf = bytes::BytesMut::new();
508 bacnet_encoding::constructed::encode_cov_subscription_list(
509 &mut buf,
510 &self.active_cov_subscriptions,
511 );
512 return Ok(PropertyValue::ApplicationData(buf.to_vec()));
513 }
514
515 self.properties
516 .get(&property)
517 .cloned()
518 .ok_or(Error::Protocol {
519 class: ErrorClass::PROPERTY.to_raw() as u32,
520 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
521 })
522 }
523
524 fn write_property(
525 &mut self,
526 property: PropertyIdentifier,
527 _array_index: Option<u32>,
528 value: PropertyValue,
529 _priority: Option<u8>,
530 ) -> Result<(), Error> {
531 if property == PropertyIdentifier::DESCRIPTION {
532 if let PropertyValue::CharacterString(_) = &value {
533 self.properties.insert(property, value);
534 return Ok(());
535 }
536 return Err(Error::Protocol {
537 class: ErrorClass::PROPERTY.to_raw() as u32,
538 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
539 });
540 }
541 Err(Error::Protocol {
542 class: ErrorClass::PROPERTY.to_raw() as u32,
543 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
544 })
545 }
546
547 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
548 let mut props: Vec<PropertyIdentifier> = self.properties.keys().copied().collect();
549 props.push(PropertyIdentifier::OBJECT_LIST);
550 props.push(PropertyIdentifier::PROPERTY_LIST);
551 props.push(PropertyIdentifier::PROTOCOL_OBJECT_TYPES_SUPPORTED);
552 props.push(PropertyIdentifier::PROTOCOL_SERVICES_SUPPORTED);
553 props.push(PropertyIdentifier::ACTIVE_COV_SUBSCRIPTIONS);
554 if self.clock_frame().is_some() {
555 props.extend([
556 PropertyIdentifier::LOCAL_DATE,
557 PropertyIdentifier::LOCAL_TIME,
558 PropertyIdentifier::UTC_OFFSET,
559 PropertyIdentifier::DAYLIGHT_SAVINGS_STATUS,
560 ]);
561 }
562 props.sort_by_key(|p| p.to_raw());
563 Cow::Owned(props)
564 }
565
566 fn is_createable(&self) -> bool {
568 false
569 }
570 fn is_deleteable(&self) -> bool {
571 false
572 }
573
574 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
575 self.clock = clock;
576 }
577}
578
579#[cfg(test)]
580mod tests;