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