Skip to main content

bacnet_objects/
audit.rs

1//! AuditLog (type 61) and AuditReporter (type 62) objects per Addendum 135-2016bj.
2
3use 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/// One owned page returned by an object-level AuditLogQuery capability.
62#[derive(Debug, Clone, PartialEq)]
63pub struct AuditLogQueryPage {
64    /// Matching records in newest-first retained insertion order.
65    pub records: Vec<BACnetAuditLogRecordResult>,
66    /// Whether a complete retained-buffer scan found no unreturned match.
67    pub no_more_items: bool,
68}
69
70/// Read-only query capability for an object's retained Audit Log records.
71///
72/// Implementations return owned pages so the server can release its object
73/// database read guard before building and encoding the ComplexACK. This
74/// interface never performs persistence I/O or mutates the log.
75pub trait AuditLogStorage: Send + Sync {
76    /// Filter and page the currently retained in-memory records.
77    ///
78    /// A present start is the corrected-baseline `Unsigned64` cursor
79    /// (Errata 2024-04-29 item 8) and admits only literal sequence identities
80    /// below it. This intentionally does not add a modular cursor across
81    /// `u64::MAX -> 1`.
82    fn query(
83        &self,
84        parameters: &BACnetAuditLogQueryParameters,
85        start_at_sequence_number: Option<u64>,
86        requested_count: u16,
87    ) -> AuditLogQueryPage;
88
89    /// The retained records oldest-to-newest, each with its sequence number.
90    ///
91    /// This is the Log_Buffer list that ReadRange pages (Clause 12.64.10), the
92    /// same ring `query` scans, so the two services never see different
93    /// copies. The view is borrowed: the caller reads it under its object
94    /// database guard, and nothing is copied or persisted.
95    ///
96    /// The numbers are nonzero and run on by one from the oldest record,
97    /// 2^64 - 1 wrapping to 1, as the built-in log keeps them. ReadRange
98    /// relies on that to compute where a sequence number sits: it checks
99    /// the record it computes and searches the ring only when that record
100    /// has another number (#1536). A store that breaks this still gets
101    /// records carrying the number asked for, but not always the ones a
102    /// search from the oldest would find: a number held twice may be served
103    /// from its later record, and a zero elsewhere in the ring goes
104    /// unnoticed when the computed record matches.
105    fn retained_records(&self) -> &VecDeque<BACnetAuditLogRecordResult>;
106}
107
108// ---------------------------------------------------------------------------
109// AuditLog (type 61)
110// ---------------------------------------------------------------------------
111
112/// BACnet AuditLog object with explicit application-owned durable storage.
113///
114/// # Unwind safety
115///
116/// The log is neither `UnwindSafe` nor `RefUnwindSafe`, by decision (#1452).
117/// It holds the [`ClockReader`] the application supplies, which runs on the
118/// caller's thread with nothing to catch its panics, so asserting the traits
119/// for it would claim more than the type can promise. It also keeps the
120/// result of each staged batch that settled before its request collected it,
121/// and that result's [`Error`] can wrap a `std::io::Error`, which has
122/// neither trait. Caller clocks get no unwind-safety bound.
123pub 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    /// Commits each snapshot to the application's storage on its own thread.
136    writer: crate::durable::SaveWriter<Arc<AuditLogSnapshot>>,
137    /// The next snapshot, while its staged commit runs.
138    staged: Option<staging::StagedCommit>,
139    /// Staged batches settled before their requests finished them.
140    settled: VecDeque<staging::Settled>,
141    next_token: u64,
142    clock: Option<Arc<dyn ClockReader>>,
143}
144
145impl AuditLogObject {
146    /// Open or initialize one AuditLog using the explicitly supplied storage.
147    ///
148    /// `buffer_size` sizes a log that storage does not hold yet. A log
149    /// reopened from storage keeps the Buffer_Size it stored, which a write
150    /// may have changed since (#1238), and logs a warning when that differs
151    /// from `buffer_size`. To give a stored log a new size, turn Log_Enable
152    /// off, write Buffer_Size and turn it on again, or start from empty
153    /// storage.
154    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    /// Append one application-supplied record when logging is enabled.
220    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    /// Get the current buffer contents.
232    pub fn records(&self) -> &VecDeque<BACnetAuditLogRecordResult> {
233        &self.buffer
234    }
235
236    /// Buffer_Size: the ring's capacity, as configured or last written.
237    pub fn buffer_size(&self) -> u32 {
238        self.buffer_size
239    }
240
241    /// Persisted logging enable policy.
242    pub fn log_enable(&self) -> bool {
243        self.log_enable
244    }
245
246    /// Monotonic record identity counter with BACnet MAX-to-one wrap.
247    pub fn total_record_count(&self) -> u64 {
248        self.total_record_count
249    }
250
251    /// Current durable snapshot generation.
252    pub fn generation(&self) -> u64 {
253        self.generation
254    }
255
256    /// Set the description string.
257    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    /// The current state as the next generation's snapshot, once whatever
283    /// is staged has landed.
284    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
308/// The log-status record a Log_Enable change appends.
309fn 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        // Corrected-baseline three-state filtering (RB-20, Errata 2024-04-29
365        // item 7): ALL matches every outcome, SUCCESSES_ONLY matches records
366        // without a result, and FAILURES_ONLY matches records with one. A
367        // reserved raw value matches nothing rather than widening the query.
368        && 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                &notification.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                // Clause 13.19 says a record without Priority matches any
408                // requested Target Priority.
409                && 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                &notification.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        // The ring is stored oldest-to-newest. Reverse insertion order is the
446        // query order even across sequence wrap; numeric sorting would turn
447        // retained [MAX, 1] into the wrong chronology.
448        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                // Keep scanning the complete retained snapshot so a full page
462                // can still truthfully distinguish exhaustion from a later
463                // eligible match. This also defines count=0.
464                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            // ReadRange pages the ring through
559            // `AuditLogStorage::retained_records`.
560            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        // Clause 12.64.11 keeps an Audit Log's Record_Count read-only, so no
593        // peer can purge the log; the application uses `purge`.
594        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
663// ---------------------------------------------------------------------------
664// AuditReporter (type 62)
665// ---------------------------------------------------------------------------
666
667/// BACnet AuditReporter object — configures which audit notifications to send.
668///
669/// Ordinary Reporters are always targets. Source ownership is an endpoint-private
670/// adapter, not a mutable flag or capability available from this crate.
671///
672/// ```compile_fail,E0599
673/// use bacnet_objects::{audit::AuditReporterObject, database::ObjectDatabase, traits::BACnetObject};
674/// let reporter = AuditReporterObject::new(1, "Reporter").unwrap();
675/// let oid = reporter.object_identifier();
676/// let mut db = ObjectDatabase::new();
677/// db.add(Box::new(reporter)).unwrap();
678/// AuditReporterObject::designate_source_internal(&mut db, oid).unwrap();
679/// ```
680///
681/// ```compile_fail,E0432
682/// use bacnet_objects::audit::SourceReporterBinding;
683/// ```
684pub 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    /// Construct a disabled target Reporter, not yet bound to a server destination.
695    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    /// Change Description through the installed target owner, if any.
706    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    /// Nominal selector membership; mandatory self-reporting is a separate election.
714    #[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    /// Evaluate an already associated Reporter's WRITE filter.
723    #[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    /// Instance-owned configuration and delivery authority.
738    #[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;