1use std::collections::HashMap;
4
5use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType};
6use bacnet_types::error::Error;
7use bacnet_types::primitives::ObjectIdentifier;
8
9use crate::traits::BACnetObject;
10
11pub struct ObjectDatabase {
16 objects: HashMap<ObjectIdentifier, Box<dyn BACnetObject>>,
17 name_index: HashMap<String, ObjectIdentifier>,
19 type_index: HashMap<ObjectType, Vec<ObjectIdentifier>>,
21}
22
23impl Default for ObjectDatabase {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl ObjectDatabase {
30 pub fn new() -> Self {
32 Self {
33 objects: HashMap::new(),
34 name_index: HashMap::new(),
35 type_index: HashMap::new(),
36 }
37 }
38
39 pub fn add(&mut self, object: Box<dyn BACnetObject>) -> Result<(), Error> {
45 let oid = object.object_identifier();
46 let name = object.object_name().to_string();
47
48 if let Some(&existing_oid) = self.name_index.get(&name) {
50 if existing_oid != oid {
51 return Err(Error::Protocol {
52 class: ErrorClass::OBJECT.to_raw() as u32,
53 code: ErrorCode::DUPLICATE_NAME.to_raw() as u32,
54 });
55 }
56 }
57
58 if let Some(old) = self.objects.get(&oid) {
60 let old_name = old.object_name().to_string();
61 self.name_index.remove(&old_name);
62 }
63
64 self.name_index.insert(name, oid);
65 let is_new = !self.objects.contains_key(&oid);
66 self.objects.insert(oid, object);
67 if is_new {
68 self.type_index
69 .entry(oid.object_type())
70 .or_default()
71 .push(oid);
72 }
73 Ok(())
74 }
75
76 pub fn find_by_name(&self, name: &str) -> Option<&dyn BACnetObject> {
78 let oid = self.name_index.get(name)?;
79 self.objects.get(oid).map(|o| o.as_ref())
80 }
81
82 pub fn check_name_available(
87 &self,
88 oid: &ObjectIdentifier,
89 new_name: &str,
90 ) -> Result<(), Error> {
91 if let Some(&owner) = self.name_index.get(new_name) {
92 if owner != *oid {
93 return Err(Error::Protocol {
94 class: ErrorClass::OBJECT.to_raw() as u32,
95 code: ErrorCode::DUPLICATE_NAME.to_raw() as u32,
96 });
97 }
98 }
99 Ok(())
100 }
101
102 pub fn update_name_index(&mut self, oid: &ObjectIdentifier) {
106 if let Some(obj) = self.objects.get(oid) {
107 self.name_index.retain(|_, v| v != oid);
109 self.name_index.insert(obj.object_name().to_string(), *oid);
111 }
112 }
113
114 pub fn get(&self, oid: &ObjectIdentifier) -> Option<&dyn BACnetObject> {
116 self.objects.get(oid).map(|o| o.as_ref())
117 }
118
119 pub fn get_mut(&mut self, oid: &ObjectIdentifier) -> Option<&mut Box<dyn BACnetObject>> {
121 self.objects.get_mut(oid)
122 }
123
124 pub fn remove(&mut self, oid: &ObjectIdentifier) -> Option<Box<dyn BACnetObject>> {
126 if let Some(obj) = self.objects.remove(oid) {
127 self.name_index.remove(obj.object_name());
128 if let Some(type_set) = self.type_index.get_mut(&oid.object_type()) {
129 type_set.retain(|o| o != oid);
130 }
131 Some(obj)
132 } else {
133 None
134 }
135 }
136
137 pub fn list_objects(&self) -> Vec<ObjectIdentifier> {
139 self.objects.keys().copied().collect()
140 }
141
142 pub fn find_by_type(&self, object_type: ObjectType) -> Vec<ObjectIdentifier> {
147 self.type_index
148 .get(&object_type)
149 .cloned()
150 .unwrap_or_default()
151 }
152
153 pub fn iter_objects(&self) -> impl Iterator<Item = (ObjectIdentifier, &dyn BACnetObject)> {
157 self.objects.iter().map(|(&oid, obj)| (oid, obj.as_ref()))
158 }
159
160 pub fn len(&self) -> usize {
162 self.objects.len()
163 }
164
165 pub fn is_empty(&self) -> bool {
167 self.objects.is_empty()
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use std::borrow::Cow;
174
175 use super::*;
176 use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
177 use bacnet_types::error::Error;
178 use bacnet_types::primitives::PropertyValue;
179
180 struct TestObject {
182 oid: ObjectIdentifier,
183 name: String,
184 }
185
186 impl BACnetObject for TestObject {
187 fn object_identifier(&self) -> ObjectIdentifier {
188 self.oid
189 }
190
191 fn object_name(&self) -> &str {
192 &self.name
193 }
194
195 fn read_property(
196 &self,
197 property: PropertyIdentifier,
198 _array_index: Option<u32>,
199 ) -> Result<PropertyValue, Error> {
200 if property == PropertyIdentifier::OBJECT_NAME {
201 Ok(PropertyValue::CharacterString(self.name.clone()))
202 } else {
203 Err(Error::Protocol {
204 class: ErrorClass::PROPERTY.to_raw() as u32,
205 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
206 })
207 }
208 }
209
210 fn write_property(
211 &mut self,
212 _property: PropertyIdentifier,
213 _array_index: Option<u32>,
214 _value: PropertyValue,
215 _priority: Option<u8>,
216 ) -> Result<(), Error> {
217 Err(Error::Protocol {
218 class: ErrorClass::PROPERTY.to_raw() as u32,
219 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
220 })
221 }
222
223 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
224 Cow::Borrowed(&[PropertyIdentifier::OBJECT_NAME])
225 }
226 }
227
228 fn make_test_object(instance: u32) -> Box<dyn BACnetObject> {
229 Box::new(TestObject {
230 oid: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, instance).unwrap(),
231 name: format!("AI-{instance}"),
232 })
233 }
234
235 fn make_test_object_typed(
236 object_type: ObjectType,
237 instance: u32,
238 name: &str,
239 ) -> Box<dyn BACnetObject> {
240 Box::new(TestObject {
241 oid: ObjectIdentifier::new(object_type, instance).unwrap(),
242 name: name.to_string(),
243 })
244 }
245
246 #[test]
247 fn add_and_get() {
248 let mut db = ObjectDatabase::new();
249 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
250 db.add(make_test_object(1)).unwrap();
251 assert_eq!(db.len(), 1);
252
253 let obj = db.get(&oid).unwrap();
254 assert_eq!(obj.object_name(), "AI-1");
255 }
256
257 #[test]
258 fn get_nonexistent_returns_none() {
259 let db = ObjectDatabase::new();
260 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 99).unwrap();
261 assert!(db.get(&oid).is_none());
262 }
263
264 #[test]
265 fn read_property_via_database() {
266 let mut db = ObjectDatabase::new();
267 db.add(make_test_object(1)).unwrap();
268 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
269 let obj = db.get(&oid).unwrap();
270 let val = obj
271 .read_property(PropertyIdentifier::OBJECT_NAME, None)
272 .unwrap();
273 assert_eq!(val, PropertyValue::CharacterString("AI-1".into()));
274 }
275
276 #[test]
277 fn remove_object() {
278 let mut db = ObjectDatabase::new();
279 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
280 db.add(make_test_object(1)).unwrap();
281 assert_eq!(db.len(), 1);
282 let removed = db.remove(&oid);
283 assert!(removed.is_some());
284 assert_eq!(db.len(), 0);
285 }
286
287 #[test]
288 fn list_objects() {
289 let mut db = ObjectDatabase::new();
290 db.add(make_test_object(1)).unwrap();
291 db.add(make_test_object(2)).unwrap();
292 let oids = db.list_objects();
293 assert_eq!(oids.len(), 2);
294 }
295
296 #[test]
297 fn find_by_type_returns_matching_objects() {
298 let mut db = ObjectDatabase::new();
299 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "AI-1"))
300 .unwrap();
301 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 2, "AI-2"))
302 .unwrap();
303 db.add(make_test_object_typed(ObjectType::BINARY_INPUT, 1, "BI-1"))
304 .unwrap();
305 db.add(make_test_object_typed(ObjectType::ANALOG_OUTPUT, 1, "AO-1"))
306 .unwrap();
307
308 let ai_oids = db.find_by_type(ObjectType::ANALOG_INPUT);
309 assert_eq!(ai_oids.len(), 2);
310 for oid in &ai_oids {
311 assert_eq!(oid.object_type(), ObjectType::ANALOG_INPUT);
312 }
313
314 let bi_oids = db.find_by_type(ObjectType::BINARY_INPUT);
315 assert_eq!(bi_oids.len(), 1);
316 assert_eq!(bi_oids[0].object_type(), ObjectType::BINARY_INPUT);
317 assert_eq!(bi_oids[0].instance_number(), 1);
318
319 let ao_oids = db.find_by_type(ObjectType::ANALOG_OUTPUT);
320 assert_eq!(ao_oids.len(), 1);
321 }
322
323 #[test]
324 fn find_by_type_returns_empty_for_no_matches() {
325 let mut db = ObjectDatabase::new();
326 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "AI-1"))
327 .unwrap();
328
329 let results = db.find_by_type(ObjectType::BINARY_VALUE);
330 assert!(results.is_empty());
331 }
332
333 #[test]
334 fn find_by_type_on_empty_database() {
335 let db = ObjectDatabase::new();
336 let results = db.find_by_type(ObjectType::ANALOG_INPUT);
337 assert!(results.is_empty());
338 }
339
340 #[test]
341 fn iter_objects_yields_all_entries() {
342 let mut db = ObjectDatabase::new();
343 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "AI-1"))
344 .unwrap();
345 db.add(make_test_object_typed(ObjectType::BINARY_INPUT, 1, "BI-1"))
346 .unwrap();
347
348 let items: Vec<_> = db.iter_objects().collect();
349 assert_eq!(items.len(), 2);
350
351 for (oid, obj) in &items {
353 assert_eq!(oid.object_type(), obj.object_identifier().object_type());
354 assert!(!obj.object_name().is_empty());
355 }
356 }
357
358 #[test]
359 fn iter_objects_on_empty_database() {
360 let db = ObjectDatabase::new();
361 assert_eq!(db.iter_objects().count(), 0);
362 }
363
364 #[test]
365 fn duplicate_name_rejected() {
366 let mut db = ObjectDatabase::new();
367 db.add(make_test_object_typed(
368 ObjectType::ANALOG_INPUT,
369 1,
370 "Sensor",
371 ))
372 .unwrap();
373 let result = db.add(make_test_object_typed(
375 ObjectType::ANALOG_INPUT,
376 2,
377 "Sensor",
378 ));
379 assert!(result.is_err());
380 assert_eq!(db.len(), 1); }
382
383 #[test]
384 fn replace_same_oid_allowed() {
385 let mut db = ObjectDatabase::new();
386 db.add(make_test_object_typed(
387 ObjectType::ANALOG_INPUT,
388 1,
389 "Sensor",
390 ))
391 .unwrap();
392 db.add(make_test_object_typed(
394 ObjectType::ANALOG_INPUT,
395 1,
396 "Sensor-v2",
397 ))
398 .unwrap();
399 assert_eq!(db.len(), 1);
400 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
401 assert_eq!(db.get(&oid).unwrap().object_name(), "Sensor-v2");
402 }
403
404 #[test]
405 fn find_by_name_works() {
406 let mut db = ObjectDatabase::new();
407 db.add(make_test_object_typed(ObjectType::ANALOG_INPUT, 1, "Temp"))
408 .unwrap();
409 db.add(make_test_object_typed(ObjectType::BINARY_INPUT, 1, "Alarm"))
410 .unwrap();
411
412 let obj = db.find_by_name("Temp").unwrap();
413 assert_eq!(obj.object_identifier().instance_number(), 1);
414 assert_eq!(
415 obj.object_identifier().object_type(),
416 ObjectType::ANALOG_INPUT
417 );
418
419 assert!(db.find_by_name("NonExistent").is_none());
420 }
421
422 #[test]
423 fn remove_frees_name() {
424 let mut db = ObjectDatabase::new();
425 let oid = ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap();
426 db.add(make_test_object_typed(
427 ObjectType::ANALOG_INPUT,
428 1,
429 "Sensor",
430 ))
431 .unwrap();
432 db.remove(&oid);
433 db.add(make_test_object_typed(
435 ObjectType::ANALOG_INPUT,
436 2,
437 "Sensor",
438 ))
439 .unwrap();
440 assert_eq!(db.len(), 1);
441 }
442}