1use std::collections::{HashMap, HashSet};
4use std::sync::Arc;
5
6use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::ObjectIdentifier;
9
10use crate::clock::{ClockFrame, ClockReader};
11use crate::event_enrollment::EventEnrollmentMonitoredSource;
12use crate::traits::{BACnetObject, MonotonicClock};
13
14pub struct ObjectDatabase {
19 objects: HashMap<ObjectIdentifier, Box<dyn BACnetObject>>,
20 clock: Option<Arc<dyn ClockReader>>,
22 monotonic_clock: Option<Arc<MonotonicClock>>,
23 event_sequence_number: u16,
25 name_index: HashMap<String, ObjectIdentifier>,
27 type_index: HashMap<ObjectType, Vec<ObjectIdentifier>>,
29 invalid_enrollment_eval_state: HashSet<ObjectIdentifier>,
32 enrollment_eval_sources: HashMap<ObjectIdentifier, EventEnrollmentMonitoredSource>,
35}
36
37#[doc(hidden)]
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub struct EventSequenceReservation {
41 number: u16,
42}
43
44impl EventSequenceReservation {
45 #[doc(hidden)]
47 pub fn number(self) -> u16 {
48 self.number
49 }
50}
51
52impl Default for ObjectDatabase {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl ObjectDatabase {
59 pub fn new() -> Self {
61 Self {
62 objects: HashMap::new(),
63 clock: None,
64 monotonic_clock: None,
65 event_sequence_number: 0,
66 name_index: HashMap::new(),
67 type_index: HashMap::new(),
68 invalid_enrollment_eval_state: HashSet::new(),
69 enrollment_eval_sources: HashMap::new(),
70 }
71 }
72
73 pub fn add(&mut self, mut object: Box<dyn BACnetObject>) -> Result<(), Error> {
78 object.bind_clock_internal(self.clock.clone());
79 object.bind_monotonic_clock_internal(self.monotonic_clock.clone());
80 let oid = object.object_identifier();
81 let name = object.object_name().to_string();
82
83 if let Some(&existing_oid) = self.name_index.get(&name) {
85 if existing_oid != oid {
86 return Err(Error::Protocol {
87 class: ErrorClass::OBJECT.to_raw() as u32,
88 code: ErrorCode::DUPLICATE_NAME.to_raw() as u32,
89 });
90 }
91 }
92
93 if let Some(old) = self.objects.get(&oid) {
96 let old_name = old.object_name().to_string();
97 self.name_index.remove(&old_name);
98 self.invalidate_enrollments_monitoring(&oid);
99 }
100
101 self.name_index.insert(name, oid);
102 let is_new = !self.objects.contains_key(&oid);
103 self.enrollment_eval_sources.remove(&oid);
104 self.objects.insert(oid, object);
105 if is_new {
106 self.type_index
107 .entry(oid.object_type())
108 .or_default()
109 .push(oid);
110 }
111 Ok(())
112 }
113
114 pub fn find_by_name(&self, name: &str) -> Option<&dyn BACnetObject> {
116 let oid = self.name_index.get(name)?;
117 self.objects.get(oid).map(|o| o.as_ref())
118 }
119
120 pub fn check_name_available(
125 &self,
126 oid: &ObjectIdentifier,
127 new_name: &str,
128 ) -> Result<(), Error> {
129 if let Some(&owner) = self.name_index.get(new_name) {
130 if owner != *oid {
131 return Err(Error::Protocol {
132 class: ErrorClass::OBJECT.to_raw() as u32,
133 code: ErrorCode::DUPLICATE_NAME.to_raw() as u32,
134 });
135 }
136 }
137 Ok(())
138 }
139
140 pub fn update_name_index(&mut self, oid: &ObjectIdentifier) {
144 if let Some(obj) = self.objects.get(oid) {
145 self.name_index.retain(|_, v| v != oid);
147 self.name_index.insert(obj.object_name().to_string(), *oid);
149 }
150 }
151
152 pub fn get(&self, oid: &ObjectIdentifier) -> Option<&dyn BACnetObject> {
154 self.objects.get(oid).map(|o| o.as_ref())
155 }
156
157 pub fn get_mut(&mut self, oid: &ObjectIdentifier) -> Option<&mut Box<dyn BACnetObject>> {
159 self.objects.get_mut(oid)
160 }
161
162 pub fn enrollment_eval_state_invalidated(&self, oid: &ObjectIdentifier) -> bool {
165 self.invalid_enrollment_eval_state.contains(oid)
166 }
167
168 pub fn set_enrollment_eval_state_invalidated(
170 &mut self,
171 oid: ObjectIdentifier,
172 invalidated: bool,
173 ) {
174 if invalidated {
175 self.invalid_enrollment_eval_state.insert(oid);
176 } else {
177 self.invalid_enrollment_eval_state.remove(&oid);
178 }
179 }
180
181 pub fn enrollment_eval_source(
184 &self,
185 oid: &ObjectIdentifier,
186 ) -> Option<EventEnrollmentMonitoredSource> {
187 self.enrollment_eval_sources.get(oid).copied()
188 }
189
190 pub fn set_enrollment_eval_source(
193 &mut self,
194 oid: ObjectIdentifier,
195 source: Option<EventEnrollmentMonitoredSource>,
196 ) {
197 if let Some(source) = source {
198 self.enrollment_eval_sources.insert(oid, source);
199 } else {
200 self.enrollment_eval_sources.remove(&oid);
201 }
202 }
203
204 fn invalidate_enrollments_monitoring(&mut self, monitored_oid: &ObjectIdentifier) {
205 let affected = self
206 .objects
207 .iter()
208 .filter_map(|(oid, object)| {
209 let source = object
210 .enrollment_eval_source_internal()
211 .flatten()
212 .or_else(|| self.enrollment_eval_sources.get(oid).copied());
213 source
214 .is_some_and(|source| source.0 == *monitored_oid)
215 .then_some(*oid)
216 })
217 .collect::<Vec<_>>();
218 self.invalid_enrollment_eval_state.extend(affected);
219 }
220
221 pub fn remove(&mut self, oid: &ObjectIdentifier) -> Option<Box<dyn BACnetObject>> {
223 if self.objects.contains_key(oid) {
224 self.invalidate_enrollments_monitoring(oid);
225 }
226 if let Some(mut obj) = self.objects.remove(oid) {
227 self.enrollment_eval_sources.remove(oid);
228 if obj.enrollment_eval_state_internal().is_some() {
229 if obj
230 .set_enrollment_eval_state_internal(Default::default())
231 .is_err()
232 {
233 self.invalid_enrollment_eval_state.insert(*oid);
234 } else {
235 self.invalid_enrollment_eval_state.remove(oid);
236 }
237 let _ = obj.set_enrollment_eval_source_internal(None);
238 } else {
239 self.invalid_enrollment_eval_state.remove(oid);
240 }
241 self.name_index.remove(obj.object_name());
242 if let Some(type_set) = self.type_index.get_mut(&oid.object_type()) {
243 type_set.retain(|o| o != oid);
244 }
245 Some(obj)
246 } else {
247 None
248 }
249 }
250
251 pub fn list_objects(&self) -> Vec<ObjectIdentifier> {
253 self.objects.keys().copied().collect()
254 }
255
256 pub fn find_by_type(&self, object_type: ObjectType) -> Vec<ObjectIdentifier> {
261 self.type_index
262 .get(&object_type)
263 .cloned()
264 .unwrap_or_default()
265 }
266
267 pub fn iter_objects(&self) -> impl Iterator<Item = (ObjectIdentifier, &dyn BACnetObject)> {
271 self.objects.iter().map(|(&oid, obj)| (oid, obj.as_ref()))
272 }
273
274 pub fn set_clock_reader(&mut self, clock: Option<Arc<dyn ClockReader>>) {
278 self.clock = clock;
279 for object in self.objects.values_mut() {
280 object.bind_clock_internal(self.clock.clone());
281 }
282 }
283
284 #[doc(hidden)]
286 pub fn set_monotonic_clock_internal(&mut self, clock: Option<Arc<MonotonicClock>>) {
287 self.monotonic_clock = clock;
288 for object in self.objects.values_mut() {
289 object.bind_monotonic_clock_internal(self.monotonic_clock.clone());
290 }
291 }
292
293 pub fn clock_frame(&self) -> Option<ClockFrame> {
295 self.clock.as_ref()?.read_clock()
296 }
297
298 #[doc(hidden)]
302 pub fn next_event_sequence_number(&mut self) -> u16 {
303 let reservation = self.reserve_event_sequence_number();
304 let number = reservation.number();
305 let confirmed = self.confirm_event_sequence_number(reservation);
306 debug_assert!(
307 confirmed,
308 "a reservation cannot become stale without mutation"
309 );
310 number
311 }
312
313 #[doc(hidden)]
315 pub fn reserve_event_sequence_number(&self) -> EventSequenceReservation {
316 EventSequenceReservation {
317 number: self.event_sequence_number,
318 }
319 }
320
321 #[doc(hidden)]
323 pub fn confirm_event_sequence_number(&mut self, reservation: EventSequenceReservation) -> bool {
324 if reservation.number != self.event_sequence_number {
325 return false;
326 }
327 self.event_sequence_number = self.event_sequence_number.wrapping_add(1);
328 true
329 }
330
331 pub fn for_each_object_mut<F>(&mut self, mut f: F)
338 where
339 F: FnMut(ObjectIdentifier, &mut dyn BACnetObject),
340 {
341 for (&oid, obj) in self.objects.iter_mut() {
342 f(oid, obj.as_mut());
343 }
344 }
345
346 pub fn len(&self) -> usize {
348 self.objects.len()
349 }
350
351 pub fn is_empty(&self) -> bool {
353 self.objects.is_empty()
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use std::borrow::Cow;
360
361 use super::*;
362 use crate::analog::{AnalogInputObject, AnalogOutputObject, AnalogValueObject};
363 use crate::binary::{BinaryInputObject, BinaryOutputObject, BinaryValueObject};
364 use crate::event::{EventStateChange, EventTransition, EventTransitionCommit};
365 use crate::multistate::{MultiStateInputObject, MultiStateOutputObject, MultiStateValueObject};
366 use bacnet_types::enums::{ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier};
367 use bacnet_types::error::Error;
368 use bacnet_types::primitives::{BACnetTimeStamp, PropertyValue};
369
370 #[test]
371 fn all_nine_builtin_intrinsic_families_implement_atomic_commit() {
372 let mut objects: Vec<Box<dyn BACnetObject>> = vec![
373 Box::new(AnalogInputObject::new(1, "AI", 0).unwrap()),
374 Box::new(AnalogOutputObject::new(1, "AO", 0).unwrap()),
375 Box::new(AnalogValueObject::new(1, "AV", 0).unwrap()),
376 Box::new(BinaryInputObject::new(1, "BI").unwrap()),
377 Box::new(BinaryOutputObject::new(1, "BO").unwrap()),
378 Box::new(BinaryValueObject::new(1, "BV").unwrap()),
379 Box::new(MultiStateInputObject::new(1, "MSI", 3).unwrap()),
380 Box::new(MultiStateOutputObject::new(1, "MSO", 3).unwrap()),
381 Box::new(MultiStateValueObject::new(1, "MSV", 3).unwrap()),
382 ];
383
384 for object in &mut objects {
385 assert!(
386 object.intrinsic_reporting_requires_atomic_commit(),
387 "{} must opt into the atomic server path",
388 object.object_name()
389 );
390 object
391 .commit_event_transition_internal(EventTransitionCommit {
392 change: EventStateChange {
393 from: EventState::NORMAL,
394 to: EventState::OFFNORMAL,
395 },
396 coordinate: EventTransition::ToOffnormal,
397 ack_required: true,
398 timestamp: BACnetTimeStamp::SequenceNumber(73),
399 message_text: None,
400 })
401 .unwrap_or_else(|error| {
402 panic!(
403 "{} must implement the shared commit hook: {error:?}",
404 object.object_name()
405 )
406 });
407
408 assert_eq!(
409 object
410 .read_property(PropertyIdentifier::EVENT_STATE, None)
411 .unwrap(),
412 PropertyValue::Enumerated(EventState::OFFNORMAL.to_raw()),
413 "{} Event_State",
414 object.object_name()
415 );
416 assert_eq!(
417 object
418 .read_property(PropertyIdentifier::ACKED_TRANSITIONS, None)
419 .unwrap(),
420 PropertyValue::BitString {
421 unused_bits: 5,
422 data: vec![0x60],
423 },
424 "{} Acked_Transitions",
425 object.object_name()
426 );
427 }
428 }
429
430 #[test]
431 fn event_sequence_wraps_and_is_database_local() {
432 let mut first = ObjectDatabase::new();
433 let mut second = ObjectDatabase::new();
434
435 assert_eq!(first.next_event_sequence_number(), 0);
436 assert_eq!(first.next_event_sequence_number(), 1);
437 assert_eq!(second.next_event_sequence_number(), 0);
438
439 first.event_sequence_number = u16::MAX;
440 assert_eq!(first.next_event_sequence_number(), u16::MAX);
441 assert_eq!(first.next_event_sequence_number(), 0);
442 assert_eq!(second.next_event_sequence_number(), 1);
443 }
444
445 #[test]
446 fn event_sequence_reservation_is_non_consuming_until_confirmed() {
447 let mut db = ObjectDatabase::new();
448
449 let reservation = db.reserve_event_sequence_number();
450 assert_eq!(reservation.number(), 0);
451 assert_eq!(db.reserve_event_sequence_number().number(), 0);
452 assert!(db.confirm_event_sequence_number(reservation));
453 assert_eq!(db.reserve_event_sequence_number().number(), 1);
454 }
455
456 struct TestObject {
458 oid: ObjectIdentifier,
459 name: String,
460 }
461
462 impl BACnetObject for TestObject {
463 fn object_identifier(&self) -> ObjectIdentifier {
464 self.oid
465 }
466
467 fn object_name(&self) -> &str {
468 &self.name
469 }
470
471 fn read_property(
472 &self,
473 property: PropertyIdentifier,
474 _array_index: Option<u32>,
475 ) -> Result<PropertyValue, Error> {
476 if property == PropertyIdentifier::OBJECT_NAME {
477 Ok(PropertyValue::CharacterString(self.name.clone()))
478 } else {
479 Err(Error::Protocol {
480 class: ErrorClass::PROPERTY.to_raw() as u32,
481 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
482 })
483 }
484 }
485
486 fn write_property(
487 &mut self,
488 _property: PropertyIdentifier,
489 _array_index: Option<u32>,
490 _value: PropertyValue,
491 _priority: Option<u8>,
492 ) -> Result<(), Error> {
493 Err(Error::Protocol {
494 class: ErrorClass::PROPERTY.to_raw() as u32,
495 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
496 })
497 }
498
499 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
500 Cow::Borrowed(&[PropertyIdentifier::OBJECT_NAME])
501 }
502 }
503
504 fn make_test_object(instance: u32) -> Box<dyn BACnetObject> {
505 Box::new(TestObject {
506 oid: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, instance).unwrap(),
507 name: format!("AI-{instance}"),
508 })
509 }
510
511 fn make_test_object_typed(
512 object_type: ObjectType,
513 instance: u32,
514 name: &str,
515 ) -> Box<dyn BACnetObject> {
516 Box::new(TestObject {
517 oid: ObjectIdentifier::new(object_type, instance).unwrap(),
518 name: name.to_string(),
519 })
520 }
521
522 #[test]
523 fn add_and_get() {
524 let mut db = ObjectDatabase::new();
525 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
526 db.add(make_test_object(1)).unwrap();
527 assert_eq!(db.len(), 1);
528
529 let obj = db.get(&oid).unwrap();
530 assert_eq!(obj.object_name(), "AI-1");
531 }
532
533 #[test]
534 fn get_nonexistent_returns_none() {
535 let db = ObjectDatabase::new();
536 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 99).unwrap();
537 assert!(db.get(&oid).is_none());
538 }
539
540 #[test]
541 fn read_property_via_database() {
542 let mut db = ObjectDatabase::new();
543 db.add(make_test_object(1)).unwrap();
544 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
545 let obj = db.get(&oid).unwrap();
546 let val = obj
547 .read_property(PropertyIdentifier::OBJECT_NAME, None)
548 .unwrap();
549 assert_eq!(val, PropertyValue::CharacterString("AI-1".into()));
550 }
551
552 #[test]
553 fn remove_object() {
554 let mut db = ObjectDatabase::new();
555 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
556 db.add(make_test_object(1)).unwrap();
557 assert_eq!(db.len(), 1);
558 let removed = db.remove(&oid);
559 assert!(removed.is_some());
560 assert_eq!(db.len(), 0);
561 }
562
563 #[test]
564 fn list_objects() {
565 let mut db = ObjectDatabase::new();
566 db.add(make_test_object(1)).unwrap();
567 db.add(make_test_object(2)).unwrap();
568 let oids = db.list_objects();
569 assert_eq!(oids.len(), 2);
570 }
571
572 #[test]
573 fn find_by_type_returns_matching_objects() {
574 let mut db = ObjectDatabase::new();
575 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "AI-1"))
576 .unwrap();
577 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 2, "AI-2"))
578 .unwrap();
579 db.add(make_test_object_typed(ObjectType::BINARY_INPUT, 1, "BI-1"))
580 .unwrap();
581 db.add(make_test_object_typed(ObjectType::ANALOG_OUTPUT, 1, "AO-1"))
582 .unwrap();
583
584 let ai_oids = db.find_by_type(ObjectType::ANALOG_INPUT);
585 assert_eq!(ai_oids.len(), 2);
586 for oid in &ai_oids {
587 assert_eq!(oid.object_type(), ObjectType::ANALOG_INPUT);
588 }
589
590 let bi_oids = db.find_by_type(ObjectType::BINARY_INPUT);
591 assert_eq!(bi_oids.len(), 1);
592 assert_eq!(bi_oids[0].object_type(), ObjectType::BINARY_INPUT);
593 assert_eq!(bi_oids[0].instance_number(), 1);
594
595 let ao_oids = db.find_by_type(ObjectType::ANALOG_OUTPUT);
596 assert_eq!(ao_oids.len(), 1);
597 }
598
599 #[test]
600 fn find_by_type_returns_empty_for_no_matches() {
601 let mut db = ObjectDatabase::new();
602 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "AI-1"))
603 .unwrap();
604
605 let results = db.find_by_type(ObjectType::BINARY_VALUE);
606 assert!(results.is_empty());
607 }
608
609 #[test]
610 fn find_by_type_on_empty_database() {
611 let db = ObjectDatabase::new();
612 let results = db.find_by_type(ObjectType::ANALOG_INPUT);
613 assert!(results.is_empty());
614 }
615
616 #[test]
617 fn iter_objects_yields_all_entries() {
618 let mut db = ObjectDatabase::new();
619 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "AI-1"))
620 .unwrap();
621 db.add(make_test_object_typed(ObjectType::BINARY_INPUT, 1, "BI-1"))
622 .unwrap();
623
624 let items: Vec<_> = db.iter_objects().collect();
625 assert_eq!(items.len(), 2);
626
627 for (oid, obj) in &items {
629 assert_eq!(oid.object_type(), obj.object_identifier().object_type());
630 assert!(!obj.object_name().is_empty());
631 }
632 }
633
634 #[test]
635 fn iter_objects_on_empty_database() {
636 let db = ObjectDatabase::new();
637 assert_eq!(db.iter_objects().count(), 0);
638 }
639
640 #[test]
641 fn duplicate_name_rejected() {
642 let mut db = ObjectDatabase::new();
643 db.add(make_test_object_typed(
644 ObjectType::ANALOG_INPUT,
645 1,
646 "Sensor",
647 ))
648 .unwrap();
649 let result = db.add(make_test_object_typed(
651 ObjectType::ANALOG_INPUT,
652 2,
653 "Sensor",
654 ));
655 assert!(result.is_err());
656 assert_eq!(db.len(), 1); }
658
659 #[test]
660 fn replace_same_oid_allowed() {
661 let mut db = ObjectDatabase::new();
662 db.add(make_test_object_typed(
663 ObjectType::ANALOG_INPUT,
664 1,
665 "Sensor",
666 ))
667 .unwrap();
668 db.add(make_test_object_typed(
670 ObjectType::ANALOG_INPUT,
671 1,
672 "Sensor-v2",
673 ))
674 .unwrap();
675 assert_eq!(db.len(), 1);
676 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
677 assert_eq!(db.get(&oid).unwrap().object_name(), "Sensor-v2");
678 }
679
680 #[test]
681 fn find_by_name_works() {
682 let mut db = ObjectDatabase::new();
683 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "Temp"))
684 .unwrap();
685 db.add(make_test_object_typed(ObjectType::BINARY_INPUT, 1, "Alarm"))
686 .unwrap();
687
688 let obj = db.find_by_name("Temp").unwrap();
689 assert_eq!(obj.object_identifier().instance_number(), 1);
690 assert_eq!(
691 obj.object_identifier().object_type(),
692 ObjectType::ANALOG_INPUT
693 );
694
695 assert!(db.find_by_name("NonExistent").is_none());
696 }
697
698 #[test]
699 fn remove_frees_name() {
700 let mut db = ObjectDatabase::new();
701 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
702 db.add(make_test_object_typed(
703 ObjectType::ANALOG_INPUT,
704 1,
705 "Sensor",
706 ))
707 .unwrap();
708 db.remove(&oid);
709 db.add(make_test_object_typed(
711 ObjectType::ANALOG_INPUT,
712 2,
713 "Sensor",
714 ))
715 .unwrap();
716 assert_eq!(db.len(), 1);
717 }
718}