Skip to main content

bacnet_objects/audit/
object_policy.rs

1//! Optional instance-owned Audit overrides: Analog and Binary Values, and
2//! the Color and Color Temperature objects (#1525).
3use crate::{
4    common,
5    property_metadata::{
6        PropertyConformance, PropertyMetadata, PropertyPresenceCondition, PropertyWriteCapability,
7    },
8};
9use bacnet_types::{
10    bitstring::{AuditOperationFlags, BACnetPriorityFilter},
11    enums::{AuditLevel, AuditOperation, ErrorClass, ErrorCode, PropertyIdentifier as P},
12    error::Error,
13    primitives::PropertyValue,
14};
15
16/// Value of a present Audit_Priority_Filter property.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum AuditPriorityPolicy {
19    /// BACnet NULL: inherit the associated Reporter's command-priority filter.
20    Inherit,
21    /// An explicit sixteen-bit filter, including the empty filter.
22    Filter(BACnetPriorityFilter),
23}
24
25/// Independently optional Audit properties owned by an Analog Value, a Binary
26/// Value, a Color object or a Color Temperature object.
27///
28/// Provisioning any field opts that instance into the implemented Audit property
29/// subset. Absent fields inherit the associated Reporter. DEFAULT level and a
30/// present NULL priority filter also inherit. AV/BV follow their object-specific
31/// clauses for absent priority inheritance; generic ยง19.6.3 has conflicting wording.
32/// The priority filter applies to a commandable Present_Value: a noncommandable
33/// AV/BV leaves its row out, and the colour objects, which have no commandable
34/// property, drop the field when provisioned (#1525). These properties do not
35/// install a Reporter or enable reporting by themselves.
36#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
37pub struct ObjectAuditPolicy {
38    /// Absent row or the instance level; DEFAULT inherits.
39    pub level: Option<AuditLevel>,
40    /// Absent row or the instance operation selection, including an empty set.
41    pub operations: Option<AuditOperationFlags>,
42    /// Absent row, present NULL inheritance, or an explicit filter.
43    pub priority_filter: Option<AuditPriorityPolicy>,
44}
45
46impl ObjectAuditPolicy {
47    pub(crate) fn read(
48        &self,
49        property: P,
50        index: Option<u32>,
51    ) -> Option<Result<PropertyValue, Error>> {
52        let value = match property {
53            P::AUDIT_LEVEL => self.level.map(|v| PropertyValue::Enumerated(v.to_raw())),
54            P::AUDITABLE_OPERATIONS => self.operations.map(|v| bits(v.to_bacnet())),
55            P::AUDIT_PRIORITY_FILTER => self.priority_filter.map(|v| match v {
56                AuditPriorityPolicy::Inherit => PropertyValue::Null,
57                AuditPriorityPolicy::Filter(filter) => bits(filter.to_bacnet()),
58            }),
59            _ => return None,
60        };
61        Some(
62            value
63                .ok_or_else(common::unknown_property_error)
64                .and_then(|value| {
65                    if index.is_some() {
66                        Err(common::property_is_not_an_array_error())
67                    } else {
68                        Ok(value)
69                    }
70                }),
71        )
72    }
73
74    pub(crate) fn write(
75        &mut self,
76        property: P,
77        index: Option<u32>,
78        value: &PropertyValue,
79        priority: Option<u8>,
80    ) -> Option<Result<(), Error>> {
81        self.read(property, index).map(|present| {
82            present?;
83            if priority.is_some_and(|v| !(1..=16).contains(&v)) {
84                return Err(Error::Protocol {
85                    class: ErrorClass::SERVICES.to_raw() as u32,
86                    code: ErrorCode::PARAMETER_OUT_OF_RANGE.to_raw() as u32,
87                });
88            }
89            // NULL relinquishes ordinary noncommandable properties without a
90            // change. For the nullable priority filter it is the actual value.
91            if matches!(value, PropertyValue::Null) && property != P::AUDIT_PRIORITY_FILTER {
92                return Ok(());
93            }
94            match (property, value) {
95                (P::AUDIT_LEVEL, PropertyValue::Enumerated(v)) => {
96                    self.level = Some(AuditLevel::from_raw(*v))
97                }
98                (P::AUDITABLE_OPERATIONS, PropertyValue::BitString { unused_bits, data }) => {
99                    self.operations = Some(
100                        AuditOperationFlags::from_bacnet(*unused_bits, data)
101                            .map_err(|_| common::value_out_of_range_error())?,
102                    );
103                }
104                (P::AUDIT_PRIORITY_FILTER, PropertyValue::Null) => {
105                    self.priority_filter = Some(AuditPriorityPolicy::Inherit)
106                }
107                (P::AUDIT_PRIORITY_FILTER, PropertyValue::BitString { unused_bits, data }) => {
108                    self.priority_filter = Some(AuditPriorityPolicy::Filter(
109                        BACnetPriorityFilter::from_bacnet(*unused_bits, data)
110                            .map_err(|_| common::value_out_of_range_error())?,
111                    ));
112                }
113                _ => return Err(common::invalid_data_type_error()),
114            }
115            Ok(())
116        })
117    }
118
119    pub(crate) fn metadata(&self) -> impl Iterator<Item = PropertyMetadata> {
120        [
121            (P::AUDIT_LEVEL, self.level.is_some()),
122            (P::AUDITABLE_OPERATIONS, self.operations.is_some()),
123            (P::AUDIT_PRIORITY_FILTER, self.priority_filter.is_some()),
124        ]
125        .into_iter()
126        .filter(|(_, present)| *present)
127        .map(|(property, _)| {
128            PropertyMetadata::new(
129                property,
130                PropertyConformance::Optional,
131                Some(if property == P::AUDIT_PRIORITY_FILTER {
132                    PropertyPresenceCondition::CommandableAuditReporting
133                } else {
134                    PropertyPresenceCondition::ObjectAuditReporting
135                }),
136                PropertyWriteCapability::Always,
137            )
138        })
139    }
140
141    /// Resolve this AV/BV instance's settings against its associated Reporter.
142    #[doc(hidden)]
143    pub fn effective_internal(
144        self,
145        reporter: &super::AuditReporterConfiguration,
146    ) -> EffectiveAuditPolicy {
147        EffectiveAuditPolicy {
148            reporter_enabled: reporter.audit_level != AuditLevel::NONE,
149            level: self
150                .level
151                .filter(|v| *v != AuditLevel::DEFAULT)
152                .unwrap_or(reporter.audit_level),
153            operations: self.operations.unwrap_or(reporter.auditable_operations),
154            priorities: match self.priority_filter {
155                Some(AuditPriorityPolicy::Filter(value)) => value,
156                _ => reporter.audit_priority_filter,
157            },
158        }
159    }
160}
161
162fn bits((unused_bits, data): (u8, Vec<u8>)) -> PropertyValue {
163    PropertyValue::BitString { unused_bits, data }
164}
165
166/// Immutable per-operation policy snapshot; source reporting does not use it.
167#[doc(hidden)]
168#[derive(Clone, Copy)]
169pub struct EffectiveAuditPolicy {
170    pub reporter_enabled: bool,
171    pub level: AuditLevel,
172    pub operations: AuditOperationFlags,
173    pub priorities: BACnetPriorityFilter,
174}
175impl EffectiveAuditPolicy {
176    pub fn reports(
177        self,
178        operation: AuditOperation,
179        property: Option<P>,
180        priority: Option<u8>,
181    ) -> bool {
182        self.reporter_enabled
183            && self.level != AuditLevel::NONE
184            && self.operations.contains(operation)
185            && !(self.level == AuditLevel::AUDIT_CONFIG && property == Some(P::PRESENT_VALUE))
186            && priority.is_none_or(|v| self.priorities.contains(v))
187    }
188}
189
190#[cfg(test)]
191#[path = "object_policy_tests.rs"]
192mod tests;