Skip to main content

bacnet_objects/
audit.rs

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