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};
8use bacnet_types::constructed::{
9    BACnetAuditLogDatum, BACnetAuditLogQueryParameters, BACnetAuditLogRecord,
10    BACnetAuditLogRecordResult, BACnetAuditNotification, BACnetRecipient,
11};
12use bacnet_types::enums::{
13    AuditLevel, ErrorClass, ErrorCode, EventState, ObjectType, PropertyIdentifier, Reliability,
14};
15use bacnet_types::error::Error;
16use bacnet_types::primitives::{ObjectIdentifier, PropertyValue, StatusFlags};
17
18use crate::clock::ClockReader;
19use crate::common::read_property_list_property;
20use crate::property_metadata::PropertyMetadata;
21use crate::traits::{BACnetObject, WritePropertyRollback};
22
23mod notification;
24mod persistence;
25mod receipt;
26mod reporter_metadata;
27pub use notification::AuditLogNotificationSink;
28use persistence::{validate_record, validate_snapshot};
29pub use persistence::{
30    AuditLogPersistence, AuditLogSnapshot, FileAuditLogPersistence, MAX_AUDIT_RECORDS,
31};
32pub use receipt::{
33    CompletedAuditReceipt, ConfirmedAuditNotificationOutcome, MAX_AUDIT_RECEIPT_KEY_BYTES,
34    MAX_COMPLETED_AUDIT_RECEIPTS,
35};
36
37/// One owned page returned by an object-level AuditLogQuery capability.
38#[derive(Debug, Clone, PartialEq)]
39pub struct AuditLogQueryPage {
40    /// Matching records in newest-first retained insertion order.
41    pub records: Vec<BACnetAuditLogRecordResult>,
42    /// Whether a complete retained-buffer scan found no unreturned match.
43    pub no_more_items: bool,
44}
45
46/// Read-only query capability for an object's retained Audit Log records.
47///
48/// Implementations return owned pages so the server can release its object
49/// database read guard before building and encoding the ComplexACK. This
50/// interface never performs persistence I/O or mutates the log.
51pub trait AuditLogStorage: Send + Sync {
52    /// Filter and page the currently retained in-memory records.
53    ///
54    /// A present start is the existing Clause-21 `Unsigned32` model and admits
55    /// only literal sequence identities below it. This intentionally does not
56    /// add a modular cursor across `u64::MAX -> 1`.
57    fn query(
58        &self,
59        parameters: &BACnetAuditLogQueryParameters,
60        start_at_sequence_number: Option<u32>,
61        requested_count: u16,
62    ) -> AuditLogQueryPage;
63}
64
65// ---------------------------------------------------------------------------
66// AuditLog (type 61)
67// ---------------------------------------------------------------------------
68
69/// BACnet AuditLog object with explicit application-owned durable storage.
70pub struct AuditLogObject {
71    oid: ObjectIdentifier,
72    name: String,
73    description: String,
74    log_enable: bool,
75    buffer_size: u32,
76    buffer: VecDeque<BACnetAuditLogRecordResult>,
77    completed_receipts: Vec<CompletedAuditReceipt>,
78    total_record_count: u64,
79    status_flags: StatusFlags,
80    generation: u64,
81    persistence: Arc<dyn AuditLogPersistence>,
82    clock: Option<Arc<dyn ClockReader>>,
83}
84
85struct AuditLogWriteRollback {
86    snapshot: AuditLogSnapshot,
87}
88
89const LOG_DISABLED_STATUS: u8 = 0b001;
90const BUFFER_PURGED_STATUS: u8 = 0b010;
91
92impl AuditLogObject {
93    /// Open or initialize one AuditLog using the explicitly supplied storage.
94    pub fn new(
95        instance: u32,
96        name: impl Into<String>,
97        buffer_size: u32,
98        persistence: Arc<dyn AuditLogPersistence>,
99    ) -> Result<Self, Error> {
100        let oid = ObjectIdentifier::new(ObjectType::AUDIT_LOG, instance)?;
101        if buffer_size > MAX_AUDIT_RECORDS {
102            return Err(Error::OutOfRange(format!(
103                "AuditLog capacity {buffer_size} exceeds {MAX_AUDIT_RECORDS}"
104            )));
105        }
106        let snapshot = match persistence.load(oid)? {
107            Some(snapshot) => {
108                if snapshot.object_identifier != oid || snapshot.capacity != buffer_size {
109                    return Err(Error::Encoding(
110                        "AuditLog persisted identity or capacity does not match configuration"
111                            .into(),
112                    ));
113                }
114                validate_snapshot(&snapshot)?;
115                snapshot
116            }
117            None => {
118                let snapshot = AuditLogSnapshot {
119                    object_identifier: oid,
120                    generation: 1,
121                    capacity: buffer_size,
122                    log_enable: true,
123                    total_record_count: 0,
124                    records: Vec::new(),
125                    completed_receipts: Vec::new(),
126                };
127                validate_snapshot(&snapshot)?;
128                persistence.commit(&snapshot)?;
129                snapshot
130            }
131        };
132        Ok(Self {
133            oid,
134            name: name.into(),
135            description: String::new(),
136            log_enable: snapshot.log_enable,
137            buffer_size: snapshot.capacity,
138            buffer: snapshot.records.into(),
139            completed_receipts: snapshot.completed_receipts,
140            total_record_count: snapshot.total_record_count,
141            status_flags: StatusFlags::empty(),
142            generation: snapshot.generation,
143            persistence,
144            clock: None,
145        })
146    }
147
148    /// Append one application-supplied record when logging is enabled.
149    pub fn add_record(&mut self, record: BACnetAuditLogRecord) -> Result<Option<u64>, Error> {
150        if !self.log_enable {
151            return Ok(None);
152        }
153        validate_record(&record)?;
154        let mut prospective = self.snapshot_for_next_generation()?;
155        let sequence_number = append_record(&mut prospective, record);
156        self.commit_and_apply(prospective)?;
157        Ok(Some(sequence_number))
158    }
159
160    /// Get the current buffer contents.
161    pub fn records(&self) -> &VecDeque<BACnetAuditLogRecordResult> {
162        &self.buffer
163    }
164
165    /// Configured and persisted ring capacity.
166    pub fn buffer_size(&self) -> u32 {
167        self.buffer_size
168    }
169
170    /// Persisted logging enable policy.
171    pub fn log_enable(&self) -> bool {
172        self.log_enable
173    }
174
175    /// Monotonic record identity counter with BACnet MAX-to-one wrap.
176    pub fn total_record_count(&self) -> u64 {
177        self.total_record_count
178    }
179
180    /// Current durable snapshot generation.
181    pub fn generation(&self) -> u64 {
182        self.generation
183    }
184
185    /// Clear buffered records and append the internal BUFFER_PURGED status.
186    fn purge(&mut self) -> Result<u64, Error> {
187        let timestamp = self.valid_timestamp()?;
188        let mut prospective = self.snapshot_for_next_generation()?;
189        prospective.records.clear();
190        let sequence_number = append_record(
191            &mut prospective,
192            BACnetAuditLogRecord {
193                timestamp,
194                datum: BACnetAuditLogDatum::LogStatus(BUFFER_PURGED_STATUS),
195            },
196        );
197        self.commit_and_apply(prospective)?;
198        Ok(sequence_number)
199    }
200
201    /// Set the description string.
202    pub fn set_description(&mut self, desc: impl Into<String>) {
203        self.description = desc.into();
204    }
205
206    fn valid_timestamp(
207        &self,
208    ) -> Result<
209        (
210            bacnet_types::primitives::Date,
211            bacnet_types::primitives::Time,
212        ),
213        Error,
214    > {
215        let frame = self
216            .clock
217            .as_ref()
218            .and_then(|clock| clock.read_clock())
219            .filter(|frame| frame.is_valid_actual_datetime())
220            .ok_or(Error::Protocol {
221                class: ErrorClass::DEVICE.to_raw() as u32,
222                code: ErrorCode::OPERATIONAL_PROBLEM.to_raw() as u32,
223            })?;
224        Ok((frame.local_date, frame.local_time))
225    }
226
227    fn snapshot_for_next_generation(&self) -> Result<AuditLogSnapshot, Error> {
228        let generation = self
229            .generation
230            .checked_add(1)
231            .ok_or_else(|| Error::OutOfRange("AuditLog persistence generation exhausted".into()))?;
232        let mut snapshot = self.current_snapshot();
233        snapshot.generation = generation;
234        Ok(snapshot)
235    }
236
237    fn current_snapshot(&self) -> AuditLogSnapshot {
238        AuditLogSnapshot {
239            object_identifier: self.oid,
240            generation: self.generation,
241            capacity: self.buffer_size,
242            log_enable: self.log_enable,
243            total_record_count: self.total_record_count,
244            records: self.buffer.iter().cloned().collect(),
245            completed_receipts: self.completed_receipts.clone(),
246        }
247    }
248
249    fn commit_and_apply(&mut self, snapshot: AuditLogSnapshot) -> Result<(), Error> {
250        validate_snapshot(&snapshot)?;
251        self.persistence.commit(&snapshot)?;
252        self.generation = snapshot.generation;
253        self.log_enable = snapshot.log_enable;
254        self.total_record_count = snapshot.total_record_count;
255        self.buffer = snapshot.records.into();
256        self.completed_receipts = snapshot.completed_receipts;
257        Ok(())
258    }
259}
260
261fn append_record(snapshot: &mut AuditLogSnapshot, record: BACnetAuditLogRecord) -> u64 {
262    let sequence_number = if snapshot.total_record_count == u64::MAX {
263        1
264    } else {
265        snapshot.total_record_count + 1
266    };
267    snapshot.total_record_count = sequence_number;
268    if snapshot.capacity != 0 {
269        if snapshot.records.len() >= snapshot.capacity as usize {
270            snapshot.records.remove(0);
271        }
272        snapshot.records.push(BACnetAuditLogRecordResult {
273            sequence_number,
274            record,
275        });
276    }
277    sequence_number
278}
279
280fn recipient_matches(
281    actual: &BACnetRecipient,
282    required_identifier: ObjectIdentifier,
283    optional_address: Option<&bacnet_types::constructed::BACnetAddress>,
284) -> bool {
285    match actual {
286        BACnetRecipient::Device(identifier) => *identifier == required_identifier,
287        BACnetRecipient::Address(address) => {
288            optional_address.is_some_and(|filter| address == filter)
289        }
290    }
291}
292
293fn operation_matches(
294    notification: &BACnetAuditNotification,
295    operations: Option<bacnet_types::bitstring::AuditOperationFlags>,
296    successful_actions_only: bool,
297) -> bool {
298    operations.is_none_or(|flags| flags.contains(notification.operation))
299        && (!successful_actions_only || notification.result.is_none())
300}
301
302fn query_matches(
303    notification: &BACnetAuditNotification,
304    parameters: &BACnetAuditLogQueryParameters,
305) -> bool {
306    match parameters {
307        BACnetAuditLogQueryParameters::ByTarget {
308            target_device_identifier,
309            target_device_address,
310            target_object_identifier,
311            target_property_identifier,
312            target_array_index,
313            target_priority,
314            operations,
315            successful_actions_only,
316        } => {
317            recipient_matches(
318                &notification.target_device,
319                *target_device_identifier,
320                target_device_address.as_ref(),
321            ) && target_object_identifier
322                .is_none_or(|filter| notification.target_object == Some(filter))
323                && target_property_identifier.is_none_or(|filter| {
324                    notification.target_property.as_ref().is_some_and(|property| {
325                        property.property_identifier == filter
326                    })
327                })
328                && target_array_index.is_none_or(|filter| {
329                    notification.target_property.as_ref().is_some_and(|property| {
330                        property.property_array_index == Some(filter)
331                    })
332                })
333                // Clause 13.19 says a record without Priority matches any
334                // requested Target Priority.
335                && target_priority.is_none_or(|filter| {
336                    notification
337                        .target_priority
338                        .is_none_or(|priority| priority == filter)
339                })
340                && operation_matches(notification, *operations, *successful_actions_only)
341        }
342        BACnetAuditLogQueryParameters::BySource {
343            source_device_identifier,
344            source_device_address,
345            source_object_identifier,
346            operations,
347            successful_actions_only,
348        } => {
349            recipient_matches(
350                &notification.source_device,
351                *source_device_identifier,
352                source_device_address.as_ref(),
353            ) && source_object_identifier
354                .is_none_or(|filter| notification.source_object == Some(filter))
355                && operation_matches(notification, *operations, *successful_actions_only)
356        }
357    }
358}
359
360impl AuditLogStorage for AuditLogObject {
361    fn query(
362        &self,
363        parameters: &BACnetAuditLogQueryParameters,
364        start_at_sequence_number: Option<u32>,
365        requested_count: u16,
366    ) -> AuditLogQueryPage {
367        let limit = usize::from(requested_count).min(MAX_AUDIT_RECORDS as usize);
368        let mut records = Vec::with_capacity(limit.min(self.buffer.len()));
369        let mut unreturned_match = false;
370
371        // The ring is stored oldest-to-newest. Reverse insertion order is the
372        // query order even across sequence wrap; numeric sorting would turn
373        // retained [MAX, 1] into the wrong chronology.
374        for result in self.buffer.iter().rev() {
375            if start_at_sequence_number
376                .is_some_and(|start| result.sequence_number >= u64::from(start))
377            {
378                continue;
379            }
380            let BACnetAuditLogDatum::AuditNotification(notification) = &result.record.datum else {
381                continue;
382            };
383            if !query_matches(notification, parameters) {
384                continue;
385            }
386            if records.len() < limit {
387                records.push(result.clone());
388            } else {
389                // Keep scanning the complete retained snapshot so a full page
390                // can still truthfully distinguish exhaustion from a later
391                // eligible match. This also defines count=0.
392                unreturned_match = true;
393            }
394        }
395
396        AuditLogQueryPage {
397            records,
398            no_more_items: !unreturned_match,
399        }
400    }
401}
402
403impl BACnetObject for AuditLogObject {
404    fn object_identifier(&self) -> ObjectIdentifier {
405        self.oid
406    }
407
408    fn object_name(&self) -> &str {
409        &self.name
410    }
411
412    fn read_property(
413        &self,
414        property: PropertyIdentifier,
415        array_index: Option<u32>,
416    ) -> Result<PropertyValue, Error> {
417        match property {
418            p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
419                Ok(PropertyValue::ObjectIdentifier(self.oid))
420            }
421            p if p == PropertyIdentifier::OBJECT_NAME => {
422                Ok(PropertyValue::CharacterString(self.name.clone()))
423            }
424            p if p == PropertyIdentifier::DESCRIPTION => {
425                Ok(PropertyValue::CharacterString(self.description.clone()))
426            }
427            p if p == PropertyIdentifier::OBJECT_TYPE => {
428                Ok(PropertyValue::Enumerated(ObjectType::AUDIT_LOG.to_raw()))
429            }
430            p if p == PropertyIdentifier::LOG_ENABLE => Ok(PropertyValue::Boolean(self.log_enable)),
431            p if p == PropertyIdentifier::BUFFER_SIZE => {
432                Ok(PropertyValue::Unsigned(self.buffer_size as u64))
433            }
434            p if p == PropertyIdentifier::RECORD_COUNT => {
435                Ok(PropertyValue::Unsigned(self.buffer.len() as u64))
436            }
437            p if p == PropertyIdentifier::TOTAL_RECORD_COUNT => {
438                Ok(PropertyValue::Unsigned(self.total_record_count))
439            }
440            p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
441                unused_bits: 4,
442                data: vec![self.status_flags.bits() << 4],
443            }),
444            p if p == PropertyIdentifier::EVENT_STATE => Ok(PropertyValue::Enumerated(0)),
445            p if p == PropertyIdentifier::PROPERTY_LIST => {
446                read_property_list_property(&self.property_list(), array_index)
447            }
448            _ => Err(Error::Protocol {
449                class: ErrorClass::PROPERTY.to_raw() as u32,
450                code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
451            }),
452        }
453    }
454
455    fn write_property(
456        &mut self,
457        property: PropertyIdentifier,
458        _array_index: Option<u32>,
459        value: PropertyValue,
460        _priority: Option<u8>,
461    ) -> Result<(), Error> {
462        if property == PropertyIdentifier::LOG_ENABLE {
463            if let PropertyValue::Boolean(v) = value {
464                if v == self.log_enable {
465                    return Ok(());
466                }
467                let timestamp = self.valid_timestamp()?;
468                let mut prospective = self.snapshot_for_next_generation()?;
469                prospective.log_enable = v;
470                append_record(
471                    &mut prospective,
472                    BACnetAuditLogRecord {
473                        timestamp,
474                        datum: BACnetAuditLogDatum::LogStatus(if v {
475                            0
476                        } else {
477                            LOG_DISABLED_STATUS
478                        }),
479                    },
480                );
481                self.commit_and_apply(prospective)?;
482                return Ok(());
483            }
484            return Err(Error::Protocol {
485                class: ErrorClass::PROPERTY.to_raw() as u32,
486                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
487            });
488        }
489        if property == PropertyIdentifier::RECORD_COUNT {
490            return Err(Error::Protocol {
491                class: ErrorClass::PROPERTY.to_raw() as u32,
492                code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
493            });
494        }
495        if property == PropertyIdentifier::DESCRIPTION {
496            if let PropertyValue::CharacterString(s) = value {
497                self.description = s;
498                return Ok(());
499            }
500            return Err(Error::Protocol {
501                class: ErrorClass::PROPERTY.to_raw() as u32,
502                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
503            });
504        }
505        Err(Error::Protocol {
506            class: ErrorClass::PROPERTY.to_raw() as u32,
507            code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
508        })
509    }
510
511    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
512        static PROPS: &[PropertyIdentifier] = &[
513            PropertyIdentifier::OBJECT_IDENTIFIER,
514            PropertyIdentifier::OBJECT_NAME,
515            PropertyIdentifier::DESCRIPTION,
516            PropertyIdentifier::OBJECT_TYPE,
517            PropertyIdentifier::LOG_ENABLE,
518            PropertyIdentifier::BUFFER_SIZE,
519            PropertyIdentifier::RECORD_COUNT,
520            PropertyIdentifier::TOTAL_RECORD_COUNT,
521            PropertyIdentifier::STATUS_FLAGS,
522            PropertyIdentifier::EVENT_STATE,
523        ];
524        Cow::Borrowed(PROPS)
525    }
526
527    fn bind_clock_internal(&mut self, clock: Option<Arc<dyn ClockReader>>) {
528        self.clock = clock;
529    }
530
531    fn audit_log_storage_internal(&self) -> Option<&dyn AuditLogStorage> {
532        Some(self)
533    }
534
535    fn audit_log_notification_sink_internal(
536        &mut self,
537    ) -> Option<&mut dyn AuditLogNotificationSink> {
538        Some(self)
539    }
540
541    fn capture_write_property_rollback(
542        &mut self,
543        property: PropertyIdentifier,
544        value: &PropertyValue,
545    ) -> Option<WritePropertyRollback> {
546        let PropertyValue::Boolean(requested) = value else {
547            return None;
548        };
549        (property == PropertyIdentifier::LOG_ENABLE && *requested != self.log_enable).then(|| {
550            WritePropertyRollback::new(AuditLogWriteRollback {
551                snapshot: self.current_snapshot(),
552            })
553        })
554    }
555
556    fn restore_write_property_rollback(
557        &mut self,
558        rollback: WritePropertyRollback,
559    ) -> Result<(), Error> {
560        let mut snapshot = rollback.downcast::<AuditLogWriteRollback>()?.snapshot;
561        if snapshot.object_identifier != self.oid || snapshot.capacity != self.buffer_size {
562            return Err(Error::Encoding(
563                "AuditLog rollback snapshot does not belong to this object".into(),
564            ));
565        }
566        snapshot.generation = self
567            .generation
568            .checked_add(1)
569            .ok_or_else(|| Error::OutOfRange("AuditLog persistence generation exhausted".into()))?;
570        self.commit_and_apply(snapshot)
571    }
572}
573
574// ---------------------------------------------------------------------------
575// AuditReporter (type 62)
576// ---------------------------------------------------------------------------
577
578/// BACnet AuditReporter object — configures which audit notifications to send.
579pub struct AuditReporterObject {
580    oid: ObjectIdentifier,
581    name: String,
582    description: String,
583    status_flags: StatusFlags,
584    audit_level: AuditLevel,
585    auditable_operations: AuditOperationFlags,
586    audit_priority_filter: BACnetPriorityFilter,
587    issue_confirmed_notifications: bool,
588}
589
590impl AuditReporterObject {
591    pub fn new(instance: u32, name: impl Into<String>) -> Result<Self, Error> {
592        let oid = ObjectIdentifier::new(ObjectType::AUDIT_REPORTER, instance)?;
593        Ok(Self {
594            oid,
595            name: name.into(),
596            description: String::new(),
597            status_flags: StatusFlags::empty(),
598            audit_level: AuditLevel::NONE,
599            auditable_operations: AuditOperationFlags::empty(),
600            audit_priority_filter: BACnetPriorityFilter::all(),
601            issue_confirmed_notifications: false,
602        })
603    }
604
605    /// Set the description string.
606    pub fn set_description(&mut self, desc: impl Into<String>) {
607        self.description = desc.into();
608    }
609
610    /// Set the locally managed audit level.
611    pub fn set_audit_level(&mut self, level: AuditLevel) -> Result<(), Error> {
612        if level == AuditLevel::DEFAULT {
613            return Err(Error::OutOfRange(
614                "Audit Reporter audit level must not be DEFAULT".into(),
615            ));
616        }
617        self.audit_level = level;
618        Ok(())
619    }
620
621    /// Set the locally managed operation filter.
622    pub fn set_auditable_operations(&mut self, operations: AuditOperationFlags) {
623        self.auditable_operations = operations;
624    }
625
626    /// Set the locally managed command-priority filter.
627    pub fn set_audit_priority_filter(&mut self, filter: BACnetPriorityFilter) {
628        self.audit_priority_filter = filter;
629    }
630
631    /// Select confirmed or unconfirmed audit notifications for future producers.
632    pub fn set_issue_confirmed_notifications(&mut self, confirmed: bool) {
633        self.issue_confirmed_notifications = confirmed;
634    }
635}
636
637impl BACnetObject for AuditReporterObject {
638    fn object_identifier(&self) -> ObjectIdentifier {
639        self.oid
640    }
641
642    fn object_name(&self) -> &str {
643        &self.name
644    }
645
646    fn property_metadata(&self) -> Cow<'_, [PropertyMetadata]> {
647        Cow::Borrowed(reporter_metadata::AUDIT_REPORTER_PROPERTIES)
648    }
649
650    fn read_property(
651        &self,
652        property: PropertyIdentifier,
653        array_index: Option<u32>,
654    ) -> Result<PropertyValue, Error> {
655        match property {
656            p if p == PropertyIdentifier::OBJECT_IDENTIFIER => {
657                Ok(PropertyValue::ObjectIdentifier(self.oid))
658            }
659            p if p == PropertyIdentifier::OBJECT_NAME => {
660                Ok(PropertyValue::CharacterString(self.name.clone()))
661            }
662            p if p == PropertyIdentifier::DESCRIPTION => {
663                Ok(PropertyValue::CharacterString(self.description.clone()))
664            }
665            p if p == PropertyIdentifier::OBJECT_TYPE => Ok(PropertyValue::Enumerated(
666                ObjectType::AUDIT_REPORTER.to_raw(),
667            )),
668            p if p == PropertyIdentifier::STATUS_FLAGS => Ok(PropertyValue::BitString {
669                unused_bits: 4,
670                data: vec![self.status_flags.bits() << 4],
671            }),
672            p if p == PropertyIdentifier::RELIABILITY => Ok(PropertyValue::Enumerated(
673                Reliability::NO_FAULT_DETECTED.to_raw(),
674            )),
675            p if p == PropertyIdentifier::EVENT_STATE => {
676                Ok(PropertyValue::Enumerated(EventState::NORMAL.to_raw()))
677            }
678            p if p == PropertyIdentifier::AUDIT_LEVEL => {
679                Ok(PropertyValue::Enumerated(self.audit_level.to_raw()))
680            }
681            p if p == PropertyIdentifier::AUDIT_SOURCE_REPORTER => {
682                Ok(PropertyValue::Boolean(false))
683            }
684            p if p == PropertyIdentifier::AUDITABLE_OPERATIONS => {
685                let (unused_bits, data) = self.auditable_operations.to_bacnet();
686                Ok(PropertyValue::BitString { unused_bits, data })
687            }
688            p if p == PropertyIdentifier::AUDIT_PRIORITY_FILTER => {
689                let (unused_bits, data) = self.audit_priority_filter.to_bacnet();
690                Ok(PropertyValue::BitString { unused_bits, data })
691            }
692            p if p == PropertyIdentifier::ISSUE_CONFIRMED_NOTIFICATIONS => {
693                Ok(PropertyValue::Boolean(self.issue_confirmed_notifications))
694            }
695            p if p == PropertyIdentifier::PROPERTY_LIST => {
696                read_property_list_property(&self.property_list(), array_index)
697            }
698            _ => Err(Error::Protocol {
699                class: ErrorClass::PROPERTY.to_raw() as u32,
700                code: ErrorCode::UNKNOWN_PROPERTY.to_raw() as u32,
701            }),
702        }
703    }
704
705    fn write_property(
706        &mut self,
707        property: PropertyIdentifier,
708        _array_index: Option<u32>,
709        value: PropertyValue,
710        _priority: Option<u8>,
711    ) -> Result<(), Error> {
712        if property == PropertyIdentifier::DESCRIPTION {
713            if let PropertyValue::CharacterString(s) = value {
714                self.description = s;
715                return Ok(());
716            }
717            return Err(Error::Protocol {
718                class: ErrorClass::PROPERTY.to_raw() as u32,
719                code: ErrorCode::INVALID_DATA_TYPE.to_raw() as u32,
720            });
721        }
722        Err(Error::Protocol {
723            class: ErrorClass::PROPERTY.to_raw() as u32,
724            code: ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32,
725        })
726    }
727
728    fn property_list(&self) -> Cow<'static, [PropertyIdentifier]> {
729        crate::property_metadata::property_list_from_metadata(
730            reporter_metadata::AUDIT_REPORTER_PROPERTIES,
731        )
732    }
733}
734
735#[cfg(test)]
736#[path = "audit/tests.rs"]
737mod tests;
738
739#[cfg(test)]
740#[path = "audit/query_tests.rs"]
741mod query_tests;
742
743#[cfg(test)]
744#[path = "audit/notification_tests.rs"]
745mod notification_tests;
746
747#[cfg(test)]
748#[path = "audit/receipt_tests.rs"]
749mod receipt_tests;
750
751#[cfg(test)]
752#[path = "audit/persistence_receipt_tests.rs"]
753mod persistence_receipt_tests;