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