1use std::borrow::Cow;
4use std::collections::VecDeque;
5use std::sync::Arc;
6
7use bacnet_types::bitstring::{AuditOperationFlags, BACnetPriorityFilter, LogStatus};
8use bacnet_types::constructed::{
9 BACnetAuditLogDatum, BACnetAuditLogQueryParameters, BACnetAuditLogRecord,
10 BACnetAuditLogRecordResult, BACnetAuditNotification, BACnetObjectSelector, BACnetRecipient,
11};
12use bacnet_types::enums::{
13 AuditLevel, BACnetSuccessFilter, ErrorClass, ErrorCode, EventState, ObjectType,
14 PropertyIdentifier, Reliability,
15};
16use bacnet_types::error::Error;
17use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
18
19use crate::clock::ClockReader;
20use crate::common::read_property_list_property;
21use crate::property_metadata::PropertyMetadata;
22use crate::traits::BACnetObject;
23
24mod association;
25pub use association::{SelectedAuditReporter, TargetAuditAssociation};
26mod object_policy;
27mod policy_authority;
28pub use object_policy::{AuditPriorityPolicy, EffectiveAuditPolicy, ObjectAuditPolicy};
29pub use policy_authority::{AuditPolicyAuthority, PreparedAuditPolicyWrite};
30
31mod buffer;
32mod forwarding;
33mod log_metadata;
34pub use forwarding::AuditLogForwarding;
35mod notification;
36mod persistence;
37mod receipt;
38mod reporter_change;
39mod reporter_delay;
40pub use reporter_delay::AuditSendDelay;
41mod reporter_metadata;
42mod reporter_object;
43mod reporter_status;
44mod staging;
45pub use notification::AuditLogNotificationSink;
46use persistence::{validate_record, validate_snapshot};
47pub use persistence::{
48 AuditLogPersistence, AuditLogSnapshot, FileAuditLogPersistence, MAX_AUDIT_RECORDS,
49};
50pub use receipt::{
51 CompletedAuditReceipt, ConfirmedAuditNotificationOutcome, MAX_AUDIT_RECEIPT_KEY_BYTES,
52 MAX_COMPLETED_AUDIT_RECEIPTS,
53};
54pub use reporter_change::{
55 AuditReporterAuthority, AuditReporterChangeSink, AuditReporterConfiguration,
56};
57pub use reporter_status::{AuditDeliveryToken, AuditReporterStatus};
58pub use staging::{AuditBatchStage, StagedAuditBatch};
59
60#[derive(Debug, Clone, PartialEq)]
62pub struct AuditLogQueryPage {
63 pub records: Vec<BACnetAuditLogRecordResult>,
65 pub no_more_items: bool,
67}
68
69pub trait AuditLogStorage: Send + Sync {
75 fn query(
82 &self,
83 parameters: &BACnetAuditLogQueryParameters,
84 start_at_sequence_number: Option<u64>,
85 requested_count: u16,
86 ) -> AuditLogQueryPage;
87
88 fn retained_records(&self) -> &VecDeque<BACnetAuditLogRecordResult>;
95}
96
97pub struct AuditLogObject {
113 oid: ObjectIdentifier,
114 name: String,
115 description: String,
116 log_enable: bool,
117 buffer_size: u32,
118 buffer: VecDeque<BACnetAuditLogRecordResult>,
119 completed_receipts: Vec<CompletedAuditReceipt>,
120 total_record_count: u64,
121 status_flags: StatusFlags,
122 forwarding: Option<Arc<AuditLogForwarding>>,
123 generation: u64,
124 writer: crate::durable::SaveWriter<Arc<AuditLogSnapshot>>,
126 staged: Option<staging::StagedCommit>,
128 settled: VecDeque<staging::Settled>,
130 next_token: u64,
131 clock: Option<Arc<dyn ClockReader>>,
132}
133
134impl AuditLogObject {
135 pub fn new(
144 instance: u32,
145 name: impl Into<String>,
146 buffer_size: u32,
147 persistence: Arc<dyn AuditLogPersistence>,
148 ) -> Result<Self, Error> {
149 let oid = ObjectIdentifier::new(ObjectType::AUDIT_LOG, instance)?;
150 if buffer_size > MAX_AUDIT_RECORDS {
151 return Err(Error::OutOfRange(format!(
152 "AuditLog capacity {buffer_size} exceeds {MAX_AUDIT_RECORDS}"
153 )));
154 }
155 let snapshot = match persistence.load(oid)? {
156 Some(snapshot) => {
157 if snapshot.object_identifier != oid {
158 return Err(Error::Encoding(
159 "AuditLog persisted identity does not match configuration".into(),
160 ));
161 }
162 validate_snapshot(&snapshot)?;
163 if snapshot.capacity != buffer_size {
164 tracing::warn!(
165 audit_log = %oid,
166 configured = buffer_size,
167 stored = snapshot.capacity,
168 "Audit Log keeps the Buffer_Size it stored, not the one configured"
169 );
170 }
171 snapshot
172 }
173 None => {
174 let snapshot = AuditLogSnapshot {
175 object_identifier: oid,
176 generation: 1,
177 capacity: buffer_size,
178 log_enable: true,
179 total_record_count: 0,
180 records: Vec::new(),
181 completed_receipts: Vec::new(),
182 };
183 validate_snapshot(&snapshot)?;
184 persistence.commit(&snapshot)?;
185 snapshot
186 }
187 };
188 Ok(Self {
189 oid,
190 name: name.into(),
191 description: String::new(),
192 log_enable: snapshot.log_enable,
193 buffer_size: snapshot.capacity,
194 buffer: snapshot.records.into(),
195 completed_receipts: snapshot.completed_receipts,
196 total_record_count: snapshot.total_record_count,
197 status_flags: StatusFlags::empty(),
198 forwarding: None,
199 generation: snapshot.generation,
200 writer: staging::writer(oid, persistence),
201 staged: None,
202 settled: VecDeque::new(),
203 next_token: 0,
204 clock: None,
205 })
206 }
207
208 pub fn add_record(&mut self, record: BACnetAuditLogRecord) -> Result<Option<u64>, Error> {
210 if !self.log_enable {
211 return Ok(None);
212 }
213 validate_record(&record)?;
214 let mut prospective = self.snapshot_for_next_generation()?;
215 let sequence_number = append_record(&mut prospective, record);
216 self.commit_and_apply(prospective)?;
217 Ok(Some(sequence_number))
218 }
219
220 pub fn records(&self) -> &VecDeque<BACnetAuditLogRecordResult> {
222 &self.buffer
223 }
224
225 pub fn buffer_size(&self) -> u32 {
227 self.buffer_size
228 }
229
230 pub fn log_enable(&self) -> bool {
232 self.log_enable
233 }
234
235 pub fn total_record_count(&self) -> u64 {
237 self.total_record_count
238 }
239
240 pub fn generation(&self) -> u64 {
242 self.generation
243 }
244
245 pub fn set_description(&mut self, desc: impl Into<String>) {
247 self.description = desc.into();
248 }
249
250 fn valid_timestamp(
251 &self,
252 ) -> Result<
253 (
254 bacnet_types::primitives::Date,
255 bacnet_types::primitives::Time,
256 ),
257 Error,
258 > {
259 let frame = self
260 .clock
261 .as_ref()
262 .and_then(|clock| clock.read_clock())
263 .filter(|frame| frame.is_valid_actual_datetime())
264 .ok_or(Error::Protocol {
265 class: ErrorClass::DEVICE.to_raw() as u32,
266 code: ErrorCode::OPERATIONAL_PROBLEM.to_raw() as u32,
267 })?;
268 Ok((frame.local_date, frame.local_time))
269 }
270
271 fn snapshot_for_next_generation(&mut self) -> Result<AuditLogSnapshot, Error> {
274 self.settle_staged();
275 let generation = self
276 .generation
277 .checked_add(1)
278 .ok_or_else(|| Error::OutOfRange("AuditLog persistence generation exhausted".into()))?;
279 let mut snapshot = self.current_snapshot();
280 snapshot.generation = generation;
281 Ok(snapshot)
282 }
283
284 fn current_snapshot(&self) -> AuditLogSnapshot {
285 AuditLogSnapshot {
286 object_identifier: self.oid,
287 generation: self.generation,
288 capacity: self.buffer_size,
289 log_enable: self.log_enable,
290 total_record_count: self.total_record_count,
291 records: self.buffer.iter().cloned().collect(),
292 completed_receipts: self.completed_receipts.clone(),
293 }
294 }
295}
296
297fn log_enable_record(
299 timestamp: (
300 bacnet_types::primitives::Date,
301 bacnet_types::primitives::Time,
302 ),
303 log_enable: bool,
304) -> BACnetAuditLogRecord {
305 BACnetAuditLogRecord {
306 timestamp,
307 datum: BACnetAuditLogDatum::LogStatus(if log_enable {
308 LogStatus::empty()
309 } else {
310 LogStatus::LOG_DISABLED
311 }),
312 }
313}
314
315fn append_record(snapshot: &mut AuditLogSnapshot, record: BACnetAuditLogRecord) -> u64 {
316 let sequence_number = if snapshot.total_record_count == u64::MAX {
317 1
318 } else {
319 snapshot.total_record_count + 1
320 };
321 snapshot.total_record_count = sequence_number;
322 if snapshot.capacity != 0 {
323 if snapshot.records.len() >= snapshot.capacity as usize {
324 snapshot.records.remove(0);
325 }
326 snapshot.records.push(BACnetAuditLogRecordResult {
327 sequence_number,
328 record,
329 });
330 }
331 sequence_number
332}
333
334fn recipient_matches(
335 actual: &BACnetRecipient,
336 required_identifier: ObjectIdentifier,
337 optional_address: Option<&bacnet_types::constructed::BACnetAddress>,
338) -> bool {
339 match actual {
340 BACnetRecipient::Device(identifier) => *identifier == required_identifier,
341 BACnetRecipient::Address(address) => {
342 optional_address.is_some_and(|filter| address == filter)
343 }
344 }
345}
346
347fn operation_matches(
348 notification: &BACnetAuditNotification,
349 operations: Option<bacnet_types::bitstring::AuditOperationFlags>,
350 success_filter: BACnetSuccessFilter,
351) -> bool {
352 operations.is_none_or(|flags| flags.contains(notification.operation))
353 && match success_filter.to_raw() {
358 0 => true,
359 1 => notification.result.is_none(),
360 2 => notification.result.is_some(),
361 _ => false,
362 }
363}
364
365fn query_matches(
366 notification: &BACnetAuditNotification,
367 parameters: &BACnetAuditLogQueryParameters,
368) -> bool {
369 match parameters {
370 BACnetAuditLogQueryParameters::ByTarget {
371 target_device_identifier,
372 target_device_address,
373 target_object_identifier,
374 target_property_identifier,
375 target_array_index,
376 target_priority,
377 operations,
378 successful_actions_only,
379 } => {
380 recipient_matches(
381 ¬ification.target_device,
382 *target_device_identifier,
383 target_device_address.as_ref(),
384 ) && target_object_identifier
385 .is_none_or(|filter| notification.target_object == Some(filter))
386 && target_property_identifier.is_none_or(|filter| {
387 notification.target_property.as_ref().is_some_and(|property| {
388 property.property_identifier == filter
389 })
390 })
391 && target_array_index.is_none_or(|filter| {
392 notification.target_property.as_ref().is_some_and(|property| {
393 property.property_array_index == Some(filter)
394 })
395 })
396 && target_priority.is_none_or(|filter| {
399 notification
400 .target_priority
401 .is_none_or(|priority| priority == filter)
402 })
403 && operation_matches(notification, *operations, *successful_actions_only)
404 }
405 BACnetAuditLogQueryParameters::BySource {
406 source_device_identifier,
407 source_device_address,
408 source_object_identifier,
409 operations,
410 successful_actions_only,
411 } => {
412 recipient_matches(
413 ¬ification.source_device,
414 *source_device_identifier,
415 source_device_address.as_ref(),
416 ) && source_object_identifier
417 .is_none_or(|filter| notification.source_object == Some(filter))
418 && operation_matches(notification, *operations, *successful_actions_only)
419 }
420 }
421}
422
423impl AuditLogStorage for AuditLogObject {
424 fn query(
425 &self,
426 parameters: &BACnetAuditLogQueryParameters,
427 start_at_sequence_number: Option<u64>,
428 requested_count: u16,
429 ) -> AuditLogQueryPage {
430 let limit = usize::from(requested_count).min(MAX_AUDIT_RECORDS as usize);
431 let mut records = Vec::with_capacity(limit.min(self.buffer.len()));
432 let mut unreturned_match = false;
433
434 for result in self.buffer.iter().rev() {
438 if start_at_sequence_number.is_some_and(|start| result.sequence_number >= start) {
439 continue;
440 }
441 let BACnetAuditLogDatum::AuditNotification(notification) = &result.record.datum else {
442 continue;
443 };
444 if !query_matches(notification, parameters) {
445 continue;
446 }
447 if records.len() < limit {
448 records.push(result.clone());
449 } else {
450 unreturned_match = true;
454 }
455 }
456
457 AuditLogQueryPage {
458 records,
459 no_more_items: !unreturned_match,
460 }
461 }
462
463 fn retained_records(&self) -> &VecDeque<BACnetAuditLogRecordResult> {
464 &self.buffer
465 }
466}
467
468impl BACnetObject for AuditLogObject {
469 fn object_identifier(&self) -> ObjectIdentifier {
470 self.oid
471 }
472
473 fn object_name(&self) -> &str {
474 &self.name
475 }
476
477 fn read_property(
478 &self,
479 property: PropertyIdentifier,
480 array_index: Option<u32>,
481 ) -> Result<PropertyValue, Error> {
482 match property {
483 p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
484 Ok(PropertyValue::ObjectIdentifier(self.oid))
485 }
486 p if p == PropertyIdentifier::OBJECT_NAME => {
487 Ok(PropertyValue::CharacterString(self.name.clone()))
488 }
489 p if p == PropertyIdentifier::DESCRIPTION => {
490 Ok(PropertyValue::CharacterString(self.description.clone()))
491 }
492 p if p == PropertyIdentifier::OBJECT_TYPE => {
493 Ok(PropertyValue::Enumerated(ObjectType::AUDIT_LOG.to_raw()))
494 }
495 p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
496 p if p == PropertyIdentifier::BUFFER_SIZE => {
497 Ok(PropertyValue::Unsigned(self.buffer_size as u64))
498 }
499 p if p == PropertyIdentifier::RECORD_COUNT => {
500 Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
501 }
502 p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
503 Ok(PropertyValue::Unsigned(self.total_record_count))
504 }
505 p if p == PropertyIdentifier::STATUS_FLAGS => {
506 let mut flags = self.status_flags;
507 flags.set(
508 StatusFlags::FAULT,
509 self.forwarding.as_ref().is_some_and(|forwarding| {
510 forwarding.status().reliability() != Reliability::NO_FAULT_DETECTED
511 }),
512 );
513 Ok(PropertyValue::BitString {
514 unused_bits: 4,
515 data: vec![flags.bits() << 4],
516 })
517 }
518 p if self.forwarding.is_some() && p == PropertyIdentifier::MEMBER_OF => {
519 Ok(crate::device_reference::reference_value(
520 self.forwarding.as_ref().unwrap().parent(),
521 ))
522 }
523 p if self.forwarding.is_some() && p == PropertyIdentifier::DELETE_ON_FORWARD => {
524 Ok(PropertyValue::Boolean(false))
525 }
526 p if self.forwarding.is_some()
527 && p == PropertyIdentifier::ISSUE_CONFIRMED_NOTIFICATIONS =>
528 {
529 Ok(PropertyValue::Boolean(true))
530 }
531 p if self.forwarding.is_some() && p == PropertyIdentifier::RELIABILITY => {
532 Ok(PropertyValue::Enumerated(
533 self.forwarding
534 .as_ref()
535 .unwrap()
536 .status()
537 .reliability()
538 .to_raw(),
539 ))
540 }
541 p if p == PropertyIdentifier::EVENT_STATE => {
542 Ok(PropertyValue::Enumerated(EventState::NORMAL.to_raw()))
543 }
544 p if p == PropertyIdentifier::PROPERTY_LIST => {
545 read_property_list_property(&self.property_list(), array_index)
546 }
547 p if p == PropertyIdentifier::LOG_BUFFER => {
550 Err(crate::log_buffer::log_buffer_read_denied())
551 }
552 _ => Err(Error::Protocol {
553 class: ErrorClass::PROPERTY.to_raw() as u32,
554 code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
555 }),
556 }
557 }
558
559 fn write_property(
560 &mut self,
561 property: PropertyIdentifier,
562 _array_index: Option<u32>,
563 value: PropertyValue,
564 _priority: Option<u8>,
565 ) -> Result<(), Error> {
566 if property == PropertyIdentifier::LOG_ENABLE {
567 if let PropertyValue::Boolean(v) = value {
568 if v == self.log_enable {
569 return Ok(());
570 }
571 return self.commit_change(staging::StagedChange::LogEnable(v));
572 }
573 return Err(Error::Protocol {
574 class: ErrorClass::PROPERTY.to_raw() as u32,
575 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
576 });
577 }
578 if property == PropertyIdentifier::BUFFER_SIZE {
579 return self.write_buffer_size(&value);
580 }
581 if property == PropertyIdentifier::RECORD_COUNT {
584 return Err(Error::Protocol {
585 class: ErrorClass::PROPERTY.to_raw() as u32,
586 code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
587 });
588 }
589 if property == PropertyIdentifier::DESCRIPTION {
590 if let PropertyValue::CharacterString(s) = value {
591 self.description = s;
592 return Ok(());
593 }
594 return Err(Error::Protocol {
595 class: ErrorClass::PROPERTY.to_raw() as u32,
596 code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
597 });
598 }
599 Err(crate::common::unhandled_write_error(
600 self.property_metadata().as_ref(),
601 property,
602 _array_index,
603 ))
604 }
605
606 fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
607 log_metadata::for_object(self)
608 }
609
610 fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
611 crate::property_metadata::property_list_from_metadata(
612 log_metadata::for_object(self).as_ref(),
613 )
614 }
615
616 fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
617 self.clock = clock;
618 }
619
620 fn audit_log_storage_internal(&self) -> Option<&dyn AuditLogStorage> {
621 Some(self)
622 }
623
624 fn audit_log_forwarding_internal(&self) -> Option<Arc<AuditLogForwarding>> {
625 self.forwarding.clone()
626 }
627
628 fn set_audit_log_parent_internal(
629 &mut self,
630 parent: bacnet_types::constructed::BACnetDeviceObjectReference,
631 ) -> Result<(), Error> {
632 self.set_member_of(Some(parent));
633 Ok(())
634 }
635
636 fn audit_log_notification_sink_internal(
637 &mut self,
638 ) -> Option<&mut dyn AuditLogNotificationSink> {
639 Some(self)
640 }
641
642 fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
643 Some(self)
644 }
645
646 fn advance_monotonic_time_internal(&mut self, _now: std::time::Duration) -> bool {
647 self.settle_finished();
648 false
649 }
650}
651
652pub struct AuditReporterObject {
674 oid: ObjectIdentifier,
675 name: String,
676 status: Arc<AuditReporterStatus>,
677 change_sink: Option<std::sync::Weak<dyn AuditReporterChangeSink>>,
678 change_owner: std::sync::Weak<crate::database::AuditOwnership>,
679 clock: Option<Arc<dyn ClockReader>>,
680}
681
682impl AuditReporterObject {
683 pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
685 Ok(Self {
686 oid: ObjectIdentifier::new(ObjectType::AUDIT_REPORTER, instance)?,
687 name: name.into(),
688 status: Arc::new(AuditReporterStatus::default()),
689 change_sink: None,
690 change_owner: std::sync::Weak::new(),
691 clock: None,
692 })
693 }
694 pub fn set_description(&mut self, desc: impl Into<String>) -> Result<(), Error> {
696 AuditReporterAuthority(self).write_description(
697 PropertyValue::CharacterString(desc.into()),
698 None,
699 None,
700 )
701 }
702 #[doc(hidden)]
704 pub fn monitors_object_internal(&self, target: ObjectIdentifier) -> bool {
705 self.configuration_internal().monitors(target)
706 }
707 #[doc(hidden)]
708 pub fn monitors_unassigned_create_internal(&self, kind: ObjectType) -> bool {
709 self.configuration_internal().monitors_unassigned(kind)
710 }
711 #[doc(hidden)]
713 pub fn reports_write_internal(
714 &self,
715 property: PropertyIdentifier,
716 command_priority: Option<u8>,
717 ) -> bool {
718 ObjectAuditPolicy::default()
719 .effective_internal(&self.configuration_internal())
720 .reports(
721 bacnet_types::enums::AuditOperation::WRITE,
722 Some(property),
723 command_priority,
724 )
725 }
726 #[doc(hidden)]
728 pub fn status_internal(&self) -> Arc<AuditReporterStatus> {
729 Arc::clone(&self.status)
730 }
731 #[doc(hidden)]
732 pub fn confirmed_internal(&self) -> bool {
733 self.status.confirmed()
734 }
735 fn reliability(&self) -> Reliability {
736 if !self.configuration_internal().enabled() {
737 Reliability::NO_FAULT_DETECTED
738 } else {
739 self.status.reliability()
740 }
741 }
742}
743
744#[cfg(test)]
745#[path = "audit/tests.rs"]
746mod tests;
747
748#[cfg(test)]
749#[path = "audit/query_tests.rs"]
750mod query_tests;
751
752#[cfg(test)]
753#[path = "audit/notification_tests.rs"]
754mod notification_tests;
755
756#[cfg(test)]
757#[path = "audit/receipt_tests.rs"]
758mod receipt_tests;
759
760#[cfg(test)]
761#[path = "audit/persistence_receipt_tests.rs"]
762mod persistence_receipt_tests;
763
764#[cfg(test)]
765#[path = "audit/persistence_schema_tests.rs"]
766mod persistence_schema_tests;
767
768#[path = "audit/reporter_configuration.rs"]
769mod reporter_configuration;
770
771#[cfg(test)]
772#[path = "audit/reporter_delay_tests.rs"]
773mod reporter_delay_tests;
774
775#[cfg(test)]
776#[path = "audit/staging_tests.rs"]
777mod staging_tests;
778
779#[cfg(test)]
780#[path = "audit/buffer_tests.rs"]
781mod buffer_tests;