bacnet_types/constructed/
audit.rs1#[cfg(not(feature = "std"))]
4use alloc::{string::String, vec::Vec};
5
6use crate::bitstring::{AuditOperationFlags, LogStatus};
7use crate::enums::{
8 AuditOperation, BACnetSuccessFilter, ErrorClass, ErrorCode, ObjectType, PropertyIdentifier,
9};
10use crate::primitives::{BACnetTimeStamp, Date, ObjectIdentifier, PropertyValue, Time};
11
12use super::{BACnetAddress, BACnetRecipient};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum BACnetObjectSelector {
20 None,
22 Object(ObjectIdentifier),
24 ObjectType(ObjectType),
26}
27
28impl BACnetObjectSelector {
29 pub fn encode_property_value(&self) -> PropertyValue {
31 match self {
32 Self::None => PropertyValue::Null,
33 Self::Object(object) => PropertyValue::ObjectIdentifier(*object),
34 Self::ObjectType(kind) => PropertyValue::Enumerated(kind.to_raw()),
35 }
36 }
37
38 pub fn decode_property_value(value: &PropertyValue) -> Result<Self, crate::error::Error> {
40 match value {
41 PropertyValue::Null => Ok(Self::None),
42 PropertyValue::ObjectIdentifier(object) => Ok(Self::Object(*object)),
43 PropertyValue::Enumerated(kind) => Ok(Self::ObjectType(ObjectType::from_raw(*kind))),
44 _ => Err(crate::error::Error::Encoding(
45 "BACnetObjectSelector requires NULL, ObjectIdentifier, or Enumerated".into(),
46 )),
47 }
48 }
49}
50
51#[cfg(test)]
52mod selector_tests {
53 use super::*;
54
55 #[test]
56 fn audit_object_selector_preserves_choice_and_extensible_type() {
57 let object = ObjectIdentifier::new(ObjectType::BINARY_VALUE, 42).unwrap();
58 for (selector, value) in [
59 (BACnetObjectSelector::None, PropertyValue::Null),
60 (
61 BACnetObjectSelector::Object(object),
62 PropertyValue::ObjectIdentifier(object),
63 ),
64 (
65 BACnetObjectSelector::ObjectType(ObjectType::BINARY_VALUE),
66 PropertyValue::Enumerated(5),
67 ),
68 (
69 BACnetObjectSelector::ObjectType(ObjectType::from_raw(128)),
70 PropertyValue::Enumerated(128),
71 ),
72 ] {
73 assert_eq!(selector.encode_property_value(), value);
74 assert_eq!(
75 BACnetObjectSelector::decode_property_value(&value).unwrap(),
76 selector
77 );
78 }
79 for invalid in [
80 PropertyValue::Unsigned(5),
81 PropertyValue::Boolean(false),
82 PropertyValue::List(vec![]),
83 ] {
84 assert!(matches!(
85 BACnetObjectSelector::decode_property_value(&invalid),
86 Err(crate::error::Error::Encoding(_))
87 ));
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq)]
94pub struct BACnetAuditNotification {
95 pub source_timestamp: Option<BACnetTimeStamp>,
97 pub target_timestamp: Option<BACnetTimeStamp>,
99 pub source_device: BACnetRecipient,
101 pub source_object: Option<ObjectIdentifier>,
103 pub operation: AuditOperation,
105 pub source_comment: Option<String>,
107 pub target_comment: Option<String>,
109 pub invoke_id: Option<u8>,
111 pub source_user_id: Option<u16>,
113 pub source_user_role: Option<u8>,
115 pub target_device: BACnetRecipient,
117 pub target_object: Option<ObjectIdentifier>,
119 pub target_property: Option<AuditPropertyReference>,
121 pub target_priority: Option<u8>,
123 pub target_value: Option<Vec<u8>>,
125 pub current_value: Option<Vec<u8>>,
127 pub result: Option<(ErrorClass, ErrorCode)>,
129}
130
131#[derive(Debug, Clone, PartialEq)]
133pub struct BACnetAuditLogRecordResult {
134 pub sequence_number: u64,
136 pub record: BACnetAuditLogRecord,
138}
139
140#[derive(Debug, Clone, PartialEq)]
142pub struct BACnetAuditLogRecord {
143 pub timestamp: (Date, Time),
145 pub datum: BACnetAuditLogDatum,
147}
148
149#[derive(Debug, Clone, PartialEq)]
151#[allow(clippy::large_enum_variant)] pub enum BACnetAuditLogDatum {
153 LogStatus(LogStatus),
155 AuditNotification(BACnetAuditNotification),
157 TimeChange(f32),
159}
160
161#[derive(Debug, Clone, PartialEq, Eq)]
163pub enum BACnetAuditLogQueryParameters {
164 ByTarget {
166 target_device_identifier: ObjectIdentifier,
168 target_device_address: Option<BACnetAddress>,
170 target_object_identifier: Option<ObjectIdentifier>,
172 target_property_identifier: Option<PropertyIdentifier>,
174 target_array_index: Option<u64>,
176 target_priority: Option<u8>,
178 operations: Option<AuditOperationFlags>,
180 successful_actions_only: BACnetSuccessFilter,
183 },
184 BySource {
186 source_device_identifier: ObjectIdentifier,
188 source_device_address: Option<BACnetAddress>,
190 source_object_identifier: Option<ObjectIdentifier>,
192 operations: Option<AuditOperationFlags>,
194 successful_actions_only: BACnetSuccessFilter,
197 },
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct AuditPropertyReference {
203 pub property_identifier: PropertyIdentifier,
205 pub property_array_index: Option<u64>,
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn audit_record_models_are_dependency_neutral_and_exactly_typed() {
215 let record = BACnetAuditLogRecordResult {
216 sequence_number: u64::MAX,
217 record: BACnetAuditLogRecord {
218 timestamp: (
219 Date {
220 year: 124,
221 month: 2,
222 day: 29,
223 day_of_week: 4,
224 },
225 Time {
226 hour: 12,
227 minute: 0,
228 second: 0,
229 hundredths: 0,
230 },
231 ),
232 datum: BACnetAuditLogDatum::TimeChange(-1.5),
233 },
234 };
235 assert_eq!(record.clone(), record);
236 }
237
238 #[test]
239 #[allow(deprecated)]
240 fn legacy_bool_helper_maps_old_meaning_to_named_filter() {
241 assert_eq!(
242 BACnetSuccessFilter::from_legacy_bool(true),
243 BACnetSuccessFilter::SUCCESSES_ONLY
244 );
245 assert_eq!(
246 BACnetSuccessFilter::from_legacy_bool(false),
247 BACnetSuccessFilter::ALL
248 );
249 }
250}