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    fn retained_records(&self) -> &VecDeque<BACnetAuditLogRecordResult>;
95}
96
97// ---------------------------------------------------------------------------
98// AuditLog (type 61)
99// ---------------------------------------------------------------------------
100
101/// BACnet AuditLog object with explicit application-owned durable storage.
102///
103/// # Unwind safety
104///
105/// The log is neither `UnwindSafe` nor `RefUnwindSafe`, by decision (#1452).
106/// It holds the [`ClockReader`] the application supplies, which runs on the
107/// caller's thread with nothing to catch its panics, so asserting the traits
108/// for it would claim more than the type can promise. It also keeps the
109/// result of each staged batch that settled before its request collected it,
110/// and that result's [`Error`] can wrap a [`std::io::Error`], which has
111/// neither trait. Caller clocks get no unwind-safety bound.
112pub struct AuditLogObject {
113    oid: ObjectIdentifier,
114    name: String,
115    description: String,
116    log_enable: bool,
117    buffer_size: u32,
118    buffer: VecDeque<BACnetAuditLogRecordResult>,
119    completed_receipts: Vec<CompletedAuditReceipt>,
120    total_record_count: u64,
121    status_flags: StatusFlags,
122    forwarding: Option<Arc<AuditLogForwarding>>,
123    generation: u64,
124    /// Commits each snapshot to the application's storage on its own thread.
125    writer: crate::durable::SaveWriter<Arc<AuditLogSnapshot>>,
126    /// The next snapshot, while its staged commit runs.
127    staged: Option<staging::StagedCommit>,
128    /// Staged batches settled before their requests finished them.
129    settled: VecDeque<staging::Settled>,
130    next_token: u64,
131    clock: Option<Arc<dyn ClockReader>>,
132}
133
134impl AuditLogObject {
135    /// Open or initialize one AuditLog using the explicitly supplied storage.
136    ///
137    /// `buffer_size` sizes a log that storage does not hold yet. A log
138    /// reopened from storage keeps the Buffer_Size it stored, which a write
139    /// may have changed since (#1238), and logs a warning when that differs
140    /// from `buffer_size`. To give a stored log a new size, turn Log_Enable
141    /// off, write Buffer_Size and turn it on again, or start from empty
142    /// storage.
143    pub fn new(
144        instance: u32,
145        name: impl Into<String>,
146        buffer_size: u32,
147        persistence: Arc<dyn AuditLogPersistence>,
148    ) -> Result<Self, Error> {
149        let oid = ObjectIdentifier::new(ObjectType::AUDIT_LOG, instance)?;
150        if buffer_size > MAX_AUDIT_RECORDS {
151            return Err(Error::OutOfRange(format!(
152                "AuditLog capacity {buffer_size} exceeds {MAX_AUDIT_RECORDS}"
153            )));
154        }
155        let snapshot = match persistence.load(oid)? {
156            Some(snapshot) => {
157                if snapshot.object_identifier != oid {
158                    return Err(Error::Encoding(
159                        "AuditLog persisted identity does not match configuration".into(),
160                    ));
161                }
162                validate_snapshot(&snapshot)?;
163                if snapshot.capacity != buffer_size {
164                    tracing::warn!(
165                        audit_log = %oid,
166                        configured = buffer_size,
167                        stored = snapshot.capacity,
168                        "Audit Log keeps the Buffer_Size it stored, not the one configured"
169                    );
170                }
171                snapshot
172            }
173            None => {
174                let snapshot = AuditLogSnapshot {
175                    object_identifier: oid,
176                    generation: 1,
177                    capacity: buffer_size,
178                    log_enable: true,
179                    total_record_count: 0,
180                    records: Vec::new(),
181                    completed_receipts: Vec::new(),
182                };
183                validate_snapshot(&snapshot)?;
184                persistence.commit(&snapshot)?;
185                snapshot
186            }
187        };
188        Ok(Self {
189            oid,
190            name: name.into(),
191            description: String::new(),
192            log_enable: snapshot.log_enable,
193            buffer_size: snapshot.capacity,
194            buffer: snapshot.records.into(),
195            completed_receipts: snapshot.completed_receipts,
196            total_record_count: snapshot.total_record_count,
197            status_flags: StatusFlags::empty(),
198            forwarding: None,
199            generation: snapshot.generation,
200            writer: staging::writer(oid, persistence),
201            staged: None,
202            settled: VecDeque::new(),
203            next_token: 0,
204            clock: None,
205        })
206    }
207
208    /// Append one application-supplied record when logging is enabled.
209    pub fn add_record(&mut self, record: BACnetAuditLogRecord) -> Result<Option<u64>, Error> {
210        if !self.log_enable {
211            return Ok(None);
212        }
213        validate_record(&record)?;
214        let mut prospective = self.snapshot_for_next_generation()?;
215        let sequence_number = append_record(&mut prospective, record);
216        self.commit_and_apply(prospective)?;
217        Ok(Some(sequence_number))
218    }
219
220    /// Get the current buffer contents.
221    pub fn records(&self) -> &VecDeque<BACnetAuditLogRecordResult> {
222        &self.buffer
223    }
224
225    /// Buffer_Size: the ring's capacity, as configured or last written.
226    pub fn buffer_size(&self) -> u32 {
227        self.buffer_size
228    }
229
230    /// Persisted logging enable policy.
231    pub fn log_enable(&self) -> bool {
232        self.log_enable
233    }
234
235    /// Monotonic record identity counter with BACnet MAX-to-one wrap.
236    pub fn total_record_count(&self) -> u64 {
237        self.total_record_count
238    }
239
240    /// Current durable snapshot generation.
241    pub fn generation(&self) -> u64 {
242        self.generation
243    }
244
245    /// Set the description string.
246    pub fn set_description(&mut self, desc: impl Into<String>) {
247        self.description = desc.into();
248    }
249
250    fn valid_timestamp(
251        &self,
252    ) -> Result<
253        (
254            bacnet_types::primitives::Date,
255            bacnet_types::primitives::Time,
256        ),
257        Error,
258    > {
259        let frame = self
260            .clock
261            .as_ref()
262            .and_then(|clock| clock.read_clock())
263            .filter(|frame| frame.is_valid_actual_datetime())
264            .ok_or(Error::Protocol {
265                class: ErrorClass::DEVICE.to_raw() as u32,
266                code: ErrorCode::OPERATIONAL_PROBLEM.to_raw() as u32,
267            })?;
268        Ok((frame.local_date, frame.local_time))
269    }
270
271    /// The current state as the next generation's snapshot, once whatever
272    /// is staged has landed.
273    fn snapshot_for_next_generation(&mut self) -> Result<AuditLogSnapshot, Error> {
274        self.settle_staged();
275        let generation = self
276            .generation
277            .checked_add(1)
278            .ok_or_else(|| Error::OutOfRange("AuditLog persistence generation exhausted".into()))?;
279        let mut snapshot = self.current_snapshot();
280        snapshot.generation = generation;
281        Ok(snapshot)
282    }
283
284    fn current_snapshot(&self) -> AuditLogSnapshot {
285        AuditLogSnapshot {
286            object_identifier: self.oid,
287            generation: self.generation,
288            capacity: self.buffer_size,
289            log_enable: self.log_enable,
290            total_record_count: self.total_record_count,
291            records: self.buffer.iter().cloned().collect(),
292            completed_receipts: self.completed_receipts.clone(),
293        }
294    }
295}
296
297/// The log-status record a Log_Enable change appends.
298fn log_enable_record(
299    timestamp: (
300        bacnet_types::primitives::Date,
301        bacnet_types::primitives::Time,
302    ),
303    log_enable: bool,
304) -> BACnetAuditLogRecord {
305    BACnetAuditLogRecord {
306        timestamp,
307        datum: BACnetAuditLogDatum::LogStatus(if log_enable {
308            LogStatus::empty()
309        } else {
310            LogStatus::LOG_DISABLED
311        }),
312    }
313}
314
315fn append_record(snapshot: &mut AuditLogSnapshot, record: BACnetAuditLogRecord) -> u64 {
316    let sequence_number = if snapshot.total_record_count == u64::MAX {
317        1
318    } else {
319        snapshot.total_record_count + 1
320    };
321    snapshot.total_record_count = sequence_number;
322    if snapshot.capacity != 0 {
323        if snapshot.records.len() >= snapshot.capacity as usize {
324            snapshot.records.remove(0);
325        }
326        snapshot.records.push(BACnetAuditLogRecordResult {
327            sequence_number,
328            record,
329        });
330    }
331    sequence_number
332}
333
334fn recipient_matches(
335    actual: &BACnetRecipient,
336    required_identifier: ObjectIdentifier,
337    optional_address: Option<&bacnet_types::constructed::BACnetAddress>,
338) -> bool {
339    match actual {
340        BACnetRecipient::Device(identifier) => *identifier == required_identifier,
341        BACnetRecipient::Address(address) => {
342            optional_address.is_some_and(|filter| address == filter)
343        }
344    }
345}
346
347fn operation_matches(
348    notification: &BACnetAuditNotification,
349    operations: Option<bacnet_types::bitstring::AuditOperationFlags>,
350    success_filter: BACnetSuccessFilter,
351) -> bool {
352    operations.is_none_or(|flags| flags.contains(notification.operation))
353        // Corrected-baseline three-state filtering (RB-20, Errata 2024-04-29
354        // item 7): ALL matches every outcome, SUCCESSES_ONLY matches records
355        // without a result, and FAILURES_ONLY matches records with one. A
356        // reserved raw value matches nothing rather than widening the query.
357        && match success_filter.to_raw() {
358            0 => true,
359            1 => notification.result.is_none(),
360            2 => notification.result.is_some(),
361            _ => false,
362        }
363}
364
365fn query_matches(
366    notification: &BACnetAuditNotification,
367    parameters: &BACnetAuditLogQueryParameters,
368) -> bool {
369    match parameters {
370        BACnetAuditLogQueryParameters::ByTarget {
371            target_device_identifier,
372            target_device_address,
373            target_object_identifier,
374            target_property_identifier,
375            target_array_index,
376            target_priority,
377            operations,
378            successful_actions_only,
379        } => {
380            recipient_matches(
381                &notification.target_device,
382                *target_device_identifier,
383                target_device_address.as_ref(),
384            ) && target_object_identifier
385                .is_none_or(|filter| notification.target_object == Some(filter))
386                && target_property_identifier.is_none_or(|filter| {
387                    notification.target_property.as_ref().is_some_and(|property| {
388                        property.property_identifier == filter
389                    })
390                })
391                && target_array_index.is_none_or(|filter| {
392                    notification.target_property.as_ref().is_some_and(|property| {
393                        property.property_array_index == Some(filter)
394                    })
395                })
396                // Clause 13.19 says a record without Priority matches any
397                // requested Target Priority.
398                && target_priority.is_none_or(|filter| {
399                    notification
400                        .target_priority
401                        .is_none_or(|priority| priority == filter)
402                })
403                && operation_matches(notification, *operations, *successful_actions_only)
404        }
405        BACnetAuditLogQueryParameters::BySource {
406            source_device_identifier,
407            source_device_address,
408            source_object_identifier,
409            operations,
410            successful_actions_only,
411        } => {
412            recipient_matches(
413                &notification.source_device,
414                *source_device_identifier,
415                source_device_address.as_ref(),
416            ) && source_object_identifier
417                .is_none_or(|filter| notification.source_object == Some(filter))
418                && operation_matches(notification, *operations, *successful_actions_only)
419        }
420    }
421}
422
423impl AuditLogStorage for AuditLogObject {
424    fn query(
425        &self,
426        parameters: &BACnetAuditLogQueryParameters,
427        start_at_sequence_number: Option<u64>,
428        requested_count: u16,
429    ) -> AuditLogQueryPage {
430        let limit = usize::from(requested_count).min(MAX_AUDIT_RECORDS as usize);
431        let mut records = Vec::with_capacity(limit.min(self.buffer.len()));
432        let mut unreturned_match = false;
433
434        // The ring is stored oldest-to-newest. Reverse insertion order is the
435        // query order even across sequence wrap; numeric sorting would turn
436        // retained [MAX, 1] into the wrong chronology.
437        for result in self.buffer.iter().rev() {
438            if start_at_sequence_number.is_some_and(|start| result.sequence_number >= start) {
439                continue;
440            }
441            let BACnetAuditLogDatum::AuditNotification(notification) = &result.record.datum else {
442                continue;
443            };
444            if !query_matches(notification, parameters) {
445                continue;
446            }
447            if records.len() < limit {
448                records.push(result.clone());
449            } else {
450                // Keep scanning the complete retained snapshot so a full page
451                // can still truthfully distinguish exhaustion from a later
452                // eligible match. This also defines count=0.
453                unreturned_match = true;
454            }
455        }
456
457        AuditLogQueryPage {
458            records,
459            no_more_items: !unreturned_match,
460        }
461    }
462
463    fn retained_records(&self) -> &VecDeque<BACnetAuditLogRecordResult> {
464        &self.buffer
465    }
466}
467
468impl BACnetObject for AuditLogObject {
469    fn object_identifier(&self) -> ObjectIdentifier {
470        self.oid
471    }
472
473    fn object_name(&self) -> &str {
474        &self.name
475    }
476
477    fn read_property(
478        &self,
479        property: PropertyIdentifier,
480        array_index: Option<u32>,
481    ) -> Result<PropertyValue, Error> {
482        match property {
483            p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
484                Ok(PropertyValue::ObjectIdentifier(self.oid))
485            }
486            p if p == PropertyIdentifier::OBJECT_NAME => {
487                Ok(PropertyValue::CharacterString(self.name.clone()))
488            }
489            p if p == PropertyIdentifier::DESCRIPTION => {
490                Ok(PropertyValue::CharacterString(self.description.clone()))
491            }
492            p if p == PropertyIdentifier::OBJECT_TYPE => {
493                Ok(PropertyValue::Enumerated(ObjectType::AUDIT_LOG.to_raw()))
494            }
495            p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
496            p if p == PropertyIdentifier::BUFFER_SIZE => {
497                Ok(PropertyValue::Unsigned(self.buffer_size as u64))
498            }
499            p if p == PropertyIdentifier::RECORD_COUNT => {
500                Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
501            }
502            p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
503                Ok(PropertyValue::Unsigned(self.total_record_count))
504            }
505            p if p == PropertyIdentifier::STATUS_FLAGS => {
506                let mut flags = self.status_flags;
507                flags.set(
508                    StatusFlags::FAULT,
509                    self.forwarding.as_ref().is_some_and(|forwarding| {
510                        forwarding.status().reliability() != Reliability::NO_FAULT_DETECTED
511                    }),
512                );
513                Ok(PropertyValue::BitString {
514                    unused_bits: 4,
515                    data: vec![flags.bits() << 4],
516                })
517            }
518            p if self.forwarding.is_some() && p == PropertyIdentifier::MEMBER_OF => {
519                Ok(crate::device_reference::reference_value(
520                    self.forwarding.as_ref().unwrap().parent(),
521                ))
522            }
523            p if self.forwarding.is_some() && p == PropertyIdentifier::DELETE_ON_FORWARD => {
524                Ok(PropertyValue::Boolean(false))
525            }
526            p if self.forwarding.is_some()
527                && p == PropertyIdentifier::ISSUE_CONFIRMED_NOTIFICATIONS =>
528            {
529                Ok(PropertyValue::Boolean(true))
530            }
531            p if self.forwarding.is_some() && p == PropertyIdentifier::RELIABILITY => {
532                Ok(PropertyValue::Enumerated(
533                    self.forwarding
534                        .as_ref()
535                        .unwrap()
536                        .status()
537                        .reliability()
538                        .to_raw(),
539                ))
540            }
541            p if p == PropertyIdentifier::EVENT_STATE => {
542                Ok(PropertyValue::Enumerated(EventState::NORMAL.to_raw()))
543            }
544            p if p == PropertyIdentifier::PROPERTY_LIST => {
545                read_property_list_property(&self.property_list(), array_index)
546            }
547            // ReadRange pages the ring through
548            // `AuditLogStorage::retained_records`.
549            p if p == PropertyIdentifier::LOG_BUFFER => {
550                Err(crate::log_buffer::log_buffer_read_denied())
551            }
552            _ => Err(Error::Protocol {
553                class: ErrorClass::PROPERTY.to_raw() as u32,
554                code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
555            }),
556        }
557    }
558
559    fn write_property(
560        &mut self,
561        property: PropertyIdentifier,
562        _array_index: Option<u32>,
563        value: PropertyValue,
564        _priority: Option<u8>,
565    ) -> Result<(), Error> {
566        if property == PropertyIdentifier::LOG_ENABLE {
567            if let PropertyValue::Boolean(v) = value {
568                if v == self.log_enable {
569                    return Ok(());
570                }
571                return self.commit_change(staging::StagedChange::LogEnable(v));
572            }
573            return Err(Error::Protocol {
574                class: ErrorClass::PROPERTY.to_raw() as u32,
575                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
576            });
577        }
578        if property == PropertyIdentifier::BUFFER_SIZE {
579            return self.write_buffer_size(&value);
580        }
581        // Clause 12.64.11 keeps an Audit Log's Record_Count read-only, so no
582        // peer can purge the log; the application uses `purge`.
583        if property == PropertyIdentifier::RECORD_COUNT {
584            return Err(Error::Protocol {
585                class: ErrorClass::PROPERTY.to_raw() as u32,
586                code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
587            });
588        }
589        if property == PropertyIdentifier::DESCRIPTION {
590            if let PropertyValue::CharacterString(s) = value {
591                self.description = s;
592                return Ok(());
593            }
594            return Err(Error::Protocol {
595                class: ErrorClass::PROPERTY.to_raw() as u32,
596                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
597            });
598        }
599        Err(crate::common::unhandled_write_error(
600            self.property_metadata().as_ref(),
601            property,
602            _array_index,
603        ))
604    }
605
606    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
607        log_metadata::for_object(self)
608    }
609
610    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
611        crate::property_metadata::property_list_from_metadata(
612            log_metadata::for_object(self).as_ref(),
613        )
614    }
615
616    fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
617        self.clock = clock;
618    }
619
620    fn audit_log_storage_internal(&self) -> Option<&dyn AuditLogStorage> {
621        Some(self)
622    }
623
624    fn audit_log_forwarding_internal(&self) -> Option<Arc<AuditLogForwarding>> {
625        self.forwarding.clone()
626    }
627
628    fn set_audit_log_parent_internal(
629        &mut self,
630        parent: bacnet_types::constructed::BACnetDeviceObjectReference,
631    ) -> Result<(), Error> {
632        self.set_member_of(Some(parent));
633        Ok(())
634    }
635
636    fn audit_log_notification_sink_internal(
637        &mut self,
638    ) -> Option<&mut dyn AuditLogNotificationSink> {
639        Some(self)
640    }
641
642    fn durable_writes_internal(&mut self) -> Option<&mut dyn crate::durable::DurableWrites> {
643        Some(self)
644    }
645
646    fn advance_monotonic_time_internal(&mut self, _now: std::time::Duration) -> bool {
647        self.settle_finished();
648        false
649    }
650}
651
652// ---------------------------------------------------------------------------
653// AuditReporter (type 62)
654// ---------------------------------------------------------------------------
655
656/// BACnet AuditReporter object — configures which audit notifications to send.
657///
658/// Ordinary Reporters are always targets. Source ownership is an endpoint-private
659/// adapter, not a mutable flag or capability available from this crate.
660///
661/// ```compile_fail,E0599
662/// use bacnet_objects::{audit::AuditReporterObject, database::ObjectDatabase, traits::BACnetObject};
663/// let reporter = AuditReporterObject::new(1, "Reporter").unwrap();
664/// let oid = reporter.object_identifier();
665/// let mut db = ObjectDatabase::new();
666/// db.add(Box::new(reporter)).unwrap();
667/// AuditReporterObject::designate_source_internal(&mut db, oid).unwrap();
668/// ```
669///
670/// ```compile_fail,E0432
671/// use bacnet_objects::audit::SourceReporterBinding;
672/// ```
673pub struct AuditReporterObject {
674    oid: ObjectIdentifier,
675    name: String,
676    status: Arc<AuditReporterStatus>,
677    change_sink: Option<std::sync::Weak<dyn AuditReporterChangeSink>>,
678    change_owner: std::sync::Weak<crate::database::AuditOwnership>,
679    clock: Option<Arc<dyn ClockReader>>,
680}
681
682impl AuditReporterObject {
683    /// Construct a disabled target Reporter, not yet bound to a server destination.
684    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
685        Ok(Self {
686            oid: ObjectIdentifier::new(ObjectType::AUDIT_REPORTER, instance)?,
687            name: name.into(),
688            status: Arc::new(AuditReporterStatus::default()),
689            change_sink: None,
690            change_owner: std::sync::Weak::new(),
691            clock: None,
692        })
693    }
694    /// Change Description through the installed target owner, if any.
695    pub fn set_description(&mut self, desc: impl Into<String>) -> Result<(), Error> {
696        AuditReporterAuthority(self).write_description(
697            PropertyValue::CharacterString(desc.into()),
698            None,
699            None,
700        )
701    }
702    /// Nominal selector membership; mandatory self-reporting is a separate election.
703    #[doc(hidden)]
704    pub fn monitors_object_internal(&self, target: ObjectIdentifier) -> bool {
705        self.configuration_internal().monitors(target)
706    }
707    #[doc(hidden)]
708    pub fn monitors_unassigned_create_internal(&self, kind: ObjectType) -> bool {
709        self.configuration_internal().monitors_unassigned(kind)
710    }
711    /// Evaluate an already associated Reporter's WRITE filter.
712    #[doc(hidden)]
713    pub fn reports_write_internal(
714        &self,
715        property: PropertyIdentifier,
716        command_priority: Option<u8>,
717    ) -> bool {
718        ObjectAuditPolicy::default()
719            .effective_internal(&self.configuration_internal())
720            .reports(
721                bacnet_types::enums::AuditOperation::WRITE,
722                Some(property),
723                command_priority,
724            )
725    }
726    /// Instance-owned configuration and delivery authority.
727    #[doc(hidden)]
728    pub fn status_internal(&self) -> Arc<AuditReporterStatus> {
729        Arc::clone(&self.status)
730    }
731    #[doc(hidden)]
732    pub fn confirmed_internal(&self) -> bool {
733        self.status.confirmed()
734    }
735    fn reliability(&self) -> Reliability {
736        if !self.configuration_internal().enabled() {
737            Reliability::NO_FAULT_DETECTED
738        } else {
739            self.status.reliability()
740        }
741    }
742}
743
744#[cfg(test)]
745#[path = "audit/tests.rs"]
746mod tests;
747
748#[cfg(test)]
749#[path = "audit/query_tests.rs"]
750mod query_tests;
751
752#[cfg(test)]
753#[path = "audit/notification_tests.rs"]
754mod notification_tests;
755
756#[cfg(test)]
757#[path = "audit/receipt_tests.rs"]
758mod receipt_tests;
759
760#[cfg(test)]
761#[path = "audit/persistence_receipt_tests.rs"]
762mod persistence_receipt_tests;
763
764#[cfg(test)]
765#[path = "audit/persistence_schema_tests.rs"]
766mod persistence_schema_tests;
767
768#[path = "audit/reporter_configuration.rs"]
769mod reporter_configuration;
770
771#[cfg(test)]
772#[path = "audit/reporter_delay_tests.rs"]
773mod reporter_delay_tests;
774
775#[cfg(test)]
776#[path = "audit/staging_tests.rs"]
777mod staging_tests;
778
779#[cfg(test)]
780#[path = "audit/buffer_tests.rs"]
781mod buffer_tests;