1use 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum AuditPriorityPolicy {
19 Inherit,
21 Filter(BACnetPriorityFilter),
23}
24
25#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
37pub struct ObjectAuditPolicy {
38 pub level: Option<AuditLevel>,
40 pub operations: Option<AuditOperationFlags>,
42 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 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 #[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#[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;