1use std::borrow::Cow;
4use std::collections::VecDeque;
5
6use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
9
10use crate::common::read_property_list_property;
11use crate::traits::BACnetObject;
12
13#[derive(Debug, Clone)]
19pub struct AuditRecord {
20 pub timestamp_secs: u64,
21 pub description: String,
22}
23
24pub struct AuditLogObject {
26 oid: ObjectIdentifier,
27 name: String,
28 description: String,
29 log_enable: bool,
30 buffer_size: u32,
31 buffer: VecDeque<AuditRecord>,
32 total_record_count: u64,
33 status_flags: StatusFlags,
34}
35
36impl AuditLogObject {
37 pub fn new(instance: u32, name: impl Into<String>, buffer_size: u32) -> Result<Self, Error> {
38 let oid = ObjectIdentifier::new(ObjectType::AUDIT_LOG, instance)?;
39 Ok(Self {
40 oid,
41 name: name.into(),
42 description: String::new(),
43 log_enable: true,
44 buffer_size,
45 buffer: VecDeque::new(),
46 total_record_count: 0,
47 status_flags: StatusFlags::empty(),
48 })
49 }
50
51 pub fn add_record(&mut self, record: AuditRecord) {
53 if !self.log_enable {
54 return;
55 }
56 if self.buffer.len() >= self.buffer_size as usize {
57 self.buffer.pop_front();
58 }
59 self.buffer.push_back(record);
60 self.total_record_count += 1;
61 }
62
63 pub fn records(&self) -> &VecDeque<AuditRecord> {
65 &self.buffer
66 }
67
68 pub fn set_description(&mut self, desc: impl Into<String>) {
70 self.description = desc.into();
71 }
72}
73
74impl BACnetObject for AuditLogObject {
75 fn object_identifier(&self) -> ObjectIdentifier {
76 self.oid
77 }
78
79 fn object_name(&self) -> &str {
80 &self.name
81 }
82
83 fn read_property(
84 &self,
85 property: PropertyIdentifier,
86 array_index: Option<u32>,
87 ) -> Result<PropertyValue, Error> {
88 match property {
89 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
90 Ok(PropertyValue::ObjectIdentifier(self.oid))
91 }
92 p if p == PropertyIdentifier::OBJECT_NAME => {
93 Ok(PropertyValue::CharacterString(self.name.clone()))
94 }
95 p if p == PropertyIdentifier::DESCRIPTION => {
96 Ok(PropertyValue::CharacterString(self.description.clone()))
97 }
98 p if p == PropertyIdentifier::OBJECT_TYPE => {
99 Ok(PropertyValue::Enumerated(ObjectType::AUDIT_LOG.to_raw()))
100 }
101 p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
102 p if p == PropertyIdentifier::BUFFER_SIZE => {
103 Ok(PropertyValue::Unsigned(self.buffer_size as u64))
104 }
105 p if p == PropertyIdentifier::RECORD_COUNT => {
106 Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
107 }
108 p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
109 Ok(PropertyValue::Unsigned(self.total_record_count))
110 }
111 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
112 unused_bits: 4,
113 data: vec![self.status_flags.bits() << 4],
114 }),
115 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
116 p if p == PropertyIdentifier::PROPERTY_LIST => {
117 read_property_list_property(&self.property_list(), array_index)
118 }
119 _ => Err(Error::Protocol {
120 class: ErrorClass::PROPERTY.to_raw() as u32,
121 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
122 }),
123 }
124 }
125
126 fn write_property(
127 &mut self,
128 property: PropertyIdentifier,
129 _array_index: Option<u32>,
130 value: PropertyValue,
131 _priority: Option<u8>,
132 ) -> Result<(), Error> {
133 if property == PropertyIdentifier::LOG_ENABLE {
134 if let PropertyValue::Boolean(v) = value {
135 self.log_enable = v;
136 return Ok(());
137 }
138 return Err(Error::Protocol {
139 class: ErrorClass::PROPERTY.to_raw() as u32,
140 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
141 });
142 }
143 if property == PropertyIdentifier::RECORD_COUNT {
144 if let PropertyValue::Unsigned(0) = value {
145 self.buffer.clear();
146 return Ok(());
147 }
148 return Err(Error::Protocol {
149 class: ErrorClass::PROPERTY.to_raw() as u32,
150 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
151 });
152 }
153 if property == PropertyIdentifier::DESCRIPTION {
154 if let PropertyValue::CharacterString(s) = value {
155 self.description = s;
156 return Ok(());
157 }
158 return Err(Error::Protocol {
159 class: ErrorClass::PROPERTY.to_raw() as u32,
160 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
161 });
162 }
163 Err(Error::Protocol {
164 class: ErrorClass::PROPERTY.to_raw() as u32,
165 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
166 })
167 }
168
169 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
170 static PROPS: &[PropertyIdentifier] = &[
171 PropertyIdentifier::OBJECT_IDENTIFIER,
172 PropertyIdentifier::OBJECT_NAME,
173 PropertyIdentifier::DESCRIPTION,
174 PropertyIdentifier::OBJECT_TYPE,
175 PropertyIdentifier::LOG_ENABLE,
176 PropertyIdentifier::BUFFER_SIZE,
177 PropertyIdentifier::RECORD_COUNT,
178 PropertyIdentifier::TOTAL_RECORD_COUNT,
179 PropertyIdentifier::STATUS_FLAGS,
180 PropertyIdentifier::EVENT_STATE,
181 ];
182 Cow::Borrowed(PROPS)
183 }
184}
185
186pub struct AuditReporterObject {
192 oid: ObjectIdentifier,
193 name: String,
194 description: String,
195 status_flags: StatusFlags,
196}
197
198impl AuditReporterObject {
199 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
200 let oid = ObjectIdentifier::new(ObjectType::AUDIT_REPORTER, instance)?;
201 Ok(Self {
202 oid,
203 name: name.into(),
204 description: String::new(),
205 status_flags: StatusFlags::empty(),
206 })
207 }
208
209 pub fn set_description(&mut self, desc: impl Into<String>) {
211 self.description = desc.into();
212 }
213}
214
215impl BACnetObject for AuditReporterObject {
216 fn object_identifier(&self) -> ObjectIdentifier {
217 self.oid
218 }
219
220 fn object_name(&self) -> &str {
221 &self.name
222 }
223
224 fn read_property(
225 &self,
226 property: PropertyIdentifier,
227 array_index: Option<u32>,
228 ) -> Result<PropertyValue, Error> {
229 match property {
230 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
231 Ok(PropertyValue::ObjectIdentifier(self.oid))
232 }
233 p if p == PropertyIdentifier::OBJECT_NAME => {
234 Ok(PropertyValue::CharacterString(self.name.clone()))
235 }
236 p if p == PropertyIdentifier::DESCRIPTION => {
237 Ok(PropertyValue::CharacterString(self.description.clone()))
238 }
239 p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
240 ObjectType::AUDIT_REPORTER.to_raw(),
241 )),
242 p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
243 unused_bits: 4,
244 data: vec![self.status_flags.bits() << 4],
245 }),
246 p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
247 p if p == PropertyIdentifier::PROPERTY_LIST => {
248 read_property_list_property(&self.property_list(), array_index)
249 }
250 _ => Err(Error::Protocol {
251 class: ErrorClass::PROPERTY.to_raw() as u32,
252 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
253 }),
254 }
255 }
256
257 fn write_property(
258 &mut self,
259 property: PropertyIdentifier,
260 _array_index: Option<u32>,
261 value: PropertyValue,
262 _priority: Option<u8>,
263 ) -> Result<(), Error> {
264 if property == PropertyIdentifier::DESCRIPTION {
265 if let PropertyValue::CharacterString(s) = value {
266 self.description = s;
267 return Ok(());
268 }
269 return Err(Error::Protocol {
270 class: ErrorClass::PROPERTY.to_raw() as u32,
271 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
272 });
273 }
274 Err(Error::Protocol {
275 class: ErrorClass::PROPERTY.to_raw() as u32,
276 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
277 })
278 }
279
280 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
281 static PROPS: &[PropertyIdentifier] = &[
282 PropertyIdentifier::OBJECT_IDENTIFIER,
283 PropertyIdentifier::OBJECT_NAME,
284 PropertyIdentifier::DESCRIPTION,
285 PropertyIdentifier::OBJECT_TYPE,
286 PropertyIdentifier::STATUS_FLAGS,
287 PropertyIdentifier::EVENT_STATE,
288 ];
289 Cow::Borrowed(PROPS)
290 }
291}
292
293#[cfg(test)]
294mod tests {
295 use super::*;
296
297 #[test]
300 fn audit_log_add_records() {
301 let mut al = AuditLogObject::new(1, "AL-1", 100).unwrap();
302 al.add_record(AuditRecord {
303 timestamp_secs: 1000,
304 description: "User login".into(),
305 });
306 assert_eq!(al.records().len(), 1);
307 assert_eq!(
308 al.read_property(PropertyIdentifier::RECORD_COUNT, None)
309 .unwrap(),
310 PropertyValue::Unsigned(1)
311 );
312 }
313
314 #[test]
315 fn audit_log_ring_buffer() {
316 let mut al = AuditLogObject::new(1, "AL-1", 2).unwrap();
317 for i in 0..4 {
318 al.add_record(AuditRecord {
319 timestamp_secs: i * 60,
320 description: format!("Event {i}"),
321 });
322 }
323 assert_eq!(al.records().len(), 2);
324 assert_eq!(al.records()[0].description, "Event 2");
325 assert_eq!(
326 al.read_property(PropertyIdentifier::TOTAL_RECORD_COUNT, None)
327 .unwrap(),
328 PropertyValue::Unsigned(4)
329 );
330 }
331
332 #[test]
333 fn audit_log_disable() {
334 let mut al = AuditLogObject::new(1, "AL-1", 100).unwrap();
335 al.write_property(
336 PropertyIdentifier::LOG_ENABLE,
337 None,
338 PropertyValue::Boolean(false),
339 None,
340 )
341 .unwrap();
342 al.add_record(AuditRecord {
343 timestamp_secs: 1000,
344 description: "Should not appear".into(),
345 });
346 assert_eq!(al.records().len(), 0);
347 }
348
349 #[test]
350 fn audit_log_clear() {
351 let mut al = AuditLogObject::new(1, "AL-1", 100).unwrap();
352 al.add_record(AuditRecord {
353 timestamp_secs: 1000,
354 description: "Event".into(),
355 });
356 al.write_property(
357 PropertyIdentifier::RECORD_COUNT,
358 None,
359 PropertyValue::Unsigned(0),
360 None,
361 )
362 .unwrap();
363 assert_eq!(al.records().len(), 0);
364 }
365
366 #[test]
367 fn audit_log_read_object_type() {
368 let al = AuditLogObject::new(1, "AL-1", 100).unwrap();
369 assert_eq!(
370 al.read_property(PropertyIdentifier::OBJECT_TYPE, None)
371 .unwrap(),
372 PropertyValue::Enumerated(ObjectType::AUDIT_LOG.to_raw())
373 );
374 }
375
376 #[test]
379 fn audit_reporter_read_object_type() {
380 let ar = AuditReporterObject::new(1, "AR-1").unwrap();
381 assert_eq!(
382 ar.read_property(PropertyIdentifier::OBJECT_TYPE, None)
383 .unwrap(),
384 PropertyValue::Enumerated(ObjectType::AUDIT_REPORTER.to_raw())
385 );
386 }
387
388 #[test]
389 fn audit_reporter_write_denied() {
390 let mut ar = AuditReporterObject::new(1, "AR-1").unwrap();
391 assert!(ar
392 .write_property(
393 PropertyIdentifier::OBJECT_NAME,
394 None,
395 PropertyValue::CharacterString("new".into()),
396 None,
397 )
398 .is_err());
399 }
400}