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