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