1use crate::{
3 common,
4 property_metadata::{
5 PropertyConformance, PropertyMetadata, PropertyPresenceCondition, PropertyWriteCapability,
6 },
7};
8use bacnet_types::{
9 bitstring::{AuditOperationFlags, BACnetPriorityFilter},
10 enums::{AuditLevel, AuditOperation, ErrorClass, ErrorCode, PropertyIdentifier as P},
11 error::Error,
12 primitives::PropertyValue,
13};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub enum AuditPriorityPolicy {
18 Inherit,
20 Filter(BACnetPriorityFilter),
22}
23
24#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
33pub struct ObjectAuditPolicy {
34 pub level: Option<AuditLevel>,
36 pub operations: Option<AuditOperationFlags>,
38 pub priority_filter: Option<AuditPriorityPolicy>,
40}
41
42impl ObjectAuditPolicy {
43 pub(crate) fn read(
44 &self,
45 property: P,
46 index: Option<u32>,
47 ) -> Option<Result<PropertyValue, Error>> {
48 let value = match property {
49 P::AUDIT_LEVEL => self.level.map(|v| PropertyValue::Enumerated(v.to_raw())),
50 P::AUDITABLE_OPERATIONS => self.operations.map(|v| bits(v.to_bacnet())),
51 P::AUDIT_PRIORITY_FILTER => self.priority_filter.map(|v| match v {
52 AuditPriorityPolicy::Inherit => PropertyValue::Null,
53 AuditPriorityPolicy::Filter(filter) => bits(filter.to_bacnet()),
54 }),
55 _ => return None,
56 };
57 Some(
58 value
59 .ok_or_else(common::unknown_property_error)
60 .and_then(|value| {
61 if index.is_some() {
62 Err(common::property_is_not_an_array_error())
63 } else {
64 Ok(value)
65 }
66 }),
67 )
68 }
69
70 pub(crate) fn write(
71 &mut self,
72 property: P,
73 index: Option<u32>,
74 value: &PropertyValue,
75 priority: Option<u8>,
76 ) -> Option<Result<(), Error>> {
77 self.read(property, index).map(|present| {
78 present?;
79 if priority.is_some_and(|v| !(1..=16).contains(&v)) {
80 return Err(Error::Protocol {
81 class: ErrorClass::SERVICES.to_raw() as u32,
82 code: ErrorCode::PARAMETER_OUT_OF_RANGE.to_raw() as u32,
83 });
84 }
85 if matches!(value, PropertyValue::Null) && property != P::AUDIT_PRIORITY_FILTER {
88 return Ok(());
89 }
90 match (property, value) {
91 (P::AUDIT_LEVEL, PropertyValue::Enumerated(v)) => {
92 self.level = Some(AuditLevel::from_raw(*v))
93 }
94 (P::AUDITABLE_OPERATIONS, PropertyValue::BitString { unused_bits, data }) => {
95 self.operations = Some(
96 AuditOperationFlags::from_bacnet(*unused_bits, data)
97 .map_err(|_| common::value_out_of_range_error())?,
98 );
99 }
100 (P::AUDIT_PRIORITY_FILTER, PropertyValue::Null) => {
101 self.priority_filter = Some(AuditPriorityPolicy::Inherit)
102 }
103 (P::AUDIT_PRIORITY_FILTER, PropertyValue::BitString { unused_bits, data }) => {
104 self.priority_filter = Some(AuditPriorityPolicy::Filter(
105 BACnetPriorityFilter::from_bacnet(*unused_bits, data)
106 .map_err(|_| common::value_out_of_range_error())?,
107 ));
108 }
109 _ => return Err(common::invalid_data_type_error()),
110 }
111 Ok(())
112 })
113 }
114
115 pub(crate) fn metadata(&self) -> impl Iterator<Item = PropertyMetadata> {
116 [
117 (P::AUDIT_LEVEL, self.level.is_some()),
118 (P::AUDITABLE_OPERATIONS, self.operations.is_some()),
119 (P::AUDIT_PRIORITY_FILTER, self.priority_filter.is_some()),
120 ]
121 .into_iter()
122 .filter(|(_, present)| *present)
123 .map(|(property, _)| {
124 PropertyMetadata::new(
125 property,
126 PropertyConformance::Optional,
127 Some(if property == P::AUDIT_PRIORITY_FILTER {
128 PropertyPresenceCondition::CommandableAuditReporting
129 } else {
130 PropertyPresenceCondition::ObjectAuditReporting
131 }),
132 PropertyWriteCapability::Always,
133 )
134 })
135 }
136
137 #[doc(hidden)]
139 pub fn effective_internal(
140 self,
141 reporter: &super::AuditReporterConfiguration,
142 ) -> EffectiveAuditPolicy {
143 EffectiveAuditPolicy {
144 reporter_enabled: reporter.audit_level != AuditLevel::NONE,
145 level: self
146 .level
147 .filter(|v| *v != AuditLevel::DEFAULT)
148 .unwrap_or(reporter.audit_level),
149 operations: self.operations.unwrap_or(reporter.auditable_operations),
150 priorities: match self.priority_filter {
151 Some(AuditPriorityPolicy::Filter(value)) => value,
152 _ => reporter.audit_priority_filter,
153 },
154 }
155 }
156}
157
158fn bits((unused_bits, data): (u8, Vec<u8>)) -> PropertyValue {
159 PropertyValue::BitString { unused_bits, data }
160}
161
162#[doc(hidden)]
164#[derive(Clone, Copy)]
165pub struct EffectiveAuditPolicy {
166 pub reporter_enabled: bool,
167 pub level: AuditLevel,
168 pub operations: AuditOperationFlags,
169 pub priorities: BACnetPriorityFilter,
170}
171impl EffectiveAuditPolicy {
172 pub fn reports(
173 self,
174 operation: AuditOperation,
175 property: Option<P>,
176 priority: Option<u8>,
177 ) -> bool {
178 self.reporter_enabled
179 && self.level != AuditLevel::NONE
180 && self.operations.contains(operation)
181 && !(self.level == AuditLevel::AUDIT_CONFIG && property == Some(P::PRESENT_VALUE))
182 && priority.is_none_or(|v| self.priorities.contains(v))
183 }
184}
185
186#[cfg(test)]
187#[path = "object_policy_tests.rs"]
188mod tests;