1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum AuditPriorityPolicy {
20 Inherit,
22 Filter(BACnetPriorityFilter),
24}
25
26#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
38pub struct ObjectAuditPolicy {
39 pub level: Option<AuditLevel>,
41 pub operations: Option<AuditOperationFlags>,
43 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 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 #[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#[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;