1#[cfg(not(feature = "std"))]
2use alloc::{string::ToString, vec::Vec};
3
4use super::recipient::{check_encoded_recipient, write_recipient};
5use super::tagged::{
6 decode_app_canonical_enumerated, decode_ctx_canonical_unsigned, decode_ctx_character_string,
7 decode_ctx_constructed, decode_ctx_object_id, decode_optional_ctx, expect_end, next_is_opening,
8};
9use super::{decode_recipient, validate_tlv_sequence};
10use crate::{primitives, tags};
11use bacnet_types::constructed::{AuditPropertyReference, BACnetAuditNotification};
12use bacnet_types::enums::{AuditOperation, ErrorClass, ErrorCode};
13use bacnet_types::error::Error;
14use bytes::BytesMut;
15
16pub fn encode_audit_notification(
18 notification: &BACnetAuditNotification,
19 buf: &mut BytesMut,
20) -> Result<(), Error> {
21 let operation = notification.operation.to_raw();
22 if !valid_operation(operation) {
23 return Err(Error::OutOfRange(format!(
24 "AuditNotification operation {operation} is reserved"
25 )));
26 }
27 if notification
28 .target_priority
29 .is_some_and(|priority| !(1..=16).contains(&priority))
30 {
31 return Err(Error::OutOfRange(format!(
32 "AuditNotification target priority {} is outside 1..=16",
33 notification.target_priority.unwrap()
34 )));
35 }
36 validate_raw_value(
37 notification.target_value.as_deref(),
38 "AuditNotification target-value",
39 )?;
40 validate_raw_value(
41 notification.current_value.as_deref(),
42 "AuditNotification current-value",
43 )?;
44 check_encoded_recipient(¬ification.source_device)?;
45 check_encoded_recipient(¬ification.target_device)?;
46
47 if let Some(timestamp) = ¬ification.source_timestamp {
48 primitives::encode_timestamp(buf, 0, timestamp)?;
49 }
50 if let Some(timestamp) = ¬ification.target_timestamp {
51 primitives::encode_timestamp(buf, 1, timestamp)?;
52 }
53
54 encode_wrapped_recipient(buf, 2, ¬ification.source_device);
55 if let Some(object) = ¬ification.source_object {
56 primitives::encode_ctx_object_id(buf, 3, object);
57 }
58 primitives::encode_ctx_enumerated(buf, 4, operation);
59 if let Some(comment) = ¬ification.source_comment {
60 primitives::encode_ctx_character_string(buf, 5, comment)?;
61 }
62 if let Some(comment) = ¬ification.target_comment {
63 primitives::encode_ctx_character_string(buf, 6, comment)?;
64 }
65 if let Some(invoke_id) = notification.invoke_id {
66 primitives::encode_ctx_unsigned(buf, 7, u64::from(invoke_id));
67 }
68 if let Some(user_id) = notification.source_user_id {
69 primitives::encode_ctx_unsigned(buf, 8, u64::from(user_id));
70 }
71 if let Some(user_role) = notification.source_user_role {
72 primitives::encode_ctx_unsigned(buf, 9, u64::from(user_role));
73 }
74
75 encode_wrapped_recipient(buf, 10, ¬ification.target_device);
76 if let Some(object) = ¬ification.target_object {
77 primitives::encode_ctx_object_id(buf, 11, object);
78 }
79 if let Some(property) = ¬ification.target_property {
80 tags::encode_opening_tag(buf, 12);
81 encode_property_reference(buf, property);
82 tags::encode_closing_tag(buf, 12);
83 }
84 if let Some(priority) = notification.target_priority {
85 primitives::encode_ctx_unsigned(buf, 13, u64::from(priority));
86 }
87 if let Some(value) = ¬ification.target_value {
88 encode_raw_value(buf, 14, value);
89 }
90 if let Some(value) = ¬ification.current_value {
91 encode_raw_value(buf, 15, value);
92 }
93 if let Some((class, code)) = notification.result {
94 tags::encode_opening_tag(buf, 16);
95 primitives::encode_app_enumerated(buf, u32::from(class.to_raw()));
96 primitives::encode_app_enumerated(buf, u32::from(code.to_raw()));
97 tags::encode_closing_tag(buf, 16);
98 }
99 Ok(())
100}
101
102fn encode_wrapped_recipient(
103 buf: &mut BytesMut,
104 field_tag: u8,
105 recipient: &bacnet_types::constructed::BACnetRecipient,
106) {
107 tags::encode_opening_tag(buf, field_tag);
108 write_recipient(buf, recipient);
109 tags::encode_closing_tag(buf, field_tag);
110}
111
112fn encode_raw_value(buf: &mut BytesMut, field_tag: u8, value: &[u8]) {
113 tags::encode_opening_tag(buf, field_tag);
114 buf.extend_from_slice(value);
115 tags::encode_closing_tag(buf, field_tag);
116}
117
118fn validate_raw_value(value: Option<&[u8]>, what: &str) -> Result<(), Error> {
121 let Some(value) = value else {
122 return Ok(());
123 };
124 validate_tlv_sequence(value, what).map_err(|error| Error::Encoding(error.to_string()))
125}
126
127pub fn decode_audit_notification_at(
129 data: &[u8],
130 mut offset: usize,
131) -> Result<(BACnetAuditNotification, usize), Error> {
132 let source_timestamp = if next_is_opening(data, offset, 0)? {
133 let (timestamp, next) =
134 decode_canonical_timestamp(data, offset, 0, "AuditNotification source-timestamp")?;
135 offset = next;
136 Some(timestamp)
137 } else {
138 None
139 };
140 let target_timestamp = if next_is_opening(data, offset, 1)? {
141 let (timestamp, next) =
142 decode_canonical_timestamp(data, offset, 1, "AuditNotification target-timestamp")?;
143 offset = next;
144 Some(timestamp)
145 } else {
146 None
147 };
148
149 let (source_device, next) =
150 decode_wrapped_recipient(data, offset, 2, "AuditNotification source-device")?;
151 offset = next;
152
153 let (source_object, next) = decode_optional_ctx(
154 data,
155 offset,
156 3,
157 "AuditNotification source-object",
158 decode_ctx_object_id,
159 )?;
160 offset = next;
161
162 let operation_offset = offset;
163 let (operation_raw, next) =
164 decode_ctx_canonical_unsigned::<u32>(data, offset, 4, "AuditNotification operation")?;
165 if !valid_operation(operation_raw) {
166 return Err(Error::decoding(
167 operation_offset,
168 format!("AuditNotification operation {operation_raw} is reserved"),
169 ));
170 }
171 let operation = AuditOperation::from_raw(operation_raw);
172 offset = next;
173
174 let (source_comment, next) = decode_optional_ctx(
175 data,
176 offset,
177 5,
178 "AuditNotification source-comment",
179 decode_ctx_character_string,
180 )?;
181 let (target_comment, next) = decode_optional_ctx(
182 data,
183 next,
184 6,
185 "AuditNotification target-comment",
186 decode_ctx_character_string,
187 )?;
188 let (invoke_id, next) = decode_optional_ctx(
189 data,
190 next,
191 7,
192 "AuditNotification invoke-id",
193 decode_ctx_canonical_unsigned::<u8>,
194 )?;
195 let (source_user_id, next) = decode_optional_ctx(
196 data,
197 next,
198 8,
199 "AuditNotification source-user-id",
200 decode_ctx_canonical_unsigned::<u16>,
201 )?;
202 let (source_user_role, next) = decode_optional_ctx(
203 data,
204 next,
205 9,
206 "AuditNotification source-user-role",
207 decode_ctx_canonical_unsigned::<u8>,
208 )?;
209 offset = next;
210
211 let (target_device, next) =
212 decode_wrapped_recipient(data, offset, 10, "AuditNotification target-device")?;
213 offset = next;
214
215 let (target_object, next) = decode_optional_ctx(
216 data,
217 offset,
218 11,
219 "AuditNotification target-object",
220 decode_ctx_object_id,
221 )?;
222 offset = next;
223 let target_property = if next_is_opening(data, offset, 12)? {
224 const WHAT: &str = "AuditNotification target-property";
225 let (body, next) = decode_ctx_constructed(data, offset, 12, WHAT)?;
226 let (property, property_end) = decode_property_reference(body)?;
227 expect_end(body, property_end, offset, WHAT)?;
228 let mut canonical = BytesMut::new();
229 encode_property_reference(&mut canonical, &property);
230 if canonical.as_ref() != body {
231 return Err(Error::decoding(
232 offset,
233 "AuditNotification target-property is not canonically encoded",
234 ));
235 }
236 offset = next;
237 Some(property)
238 } else {
239 None
240 };
241 let priority_offset = offset;
242 let (target_priority, next) = decode_optional_ctx(
243 data,
244 offset,
245 13,
246 "AuditNotification target-priority",
247 decode_ctx_canonical_unsigned::<u8>,
248 )?;
249 if let Some(priority) = target_priority.filter(|priority| !(1..=16).contains(priority)) {
250 return Err(Error::out_of_range(
251 priority_offset,
252 format!("AuditNotification target-priority {priority} is outside 1..=16"),
253 ));
254 }
255 offset = next;
256 let target_value = if next_is_opening(data, offset, 14)? {
257 let (value, next) = decode_raw_value(data, offset, 14, "AuditNotification target-value")?;
258 offset = next;
259 Some(value)
260 } else {
261 None
262 };
263 let current_value = if next_is_opening(data, offset, 15)? {
264 let (value, next) = decode_raw_value(data, offset, 15, "AuditNotification current-value")?;
265 offset = next;
266 Some(value)
267 } else {
268 None
269 };
270 let result = if next_is_opening(data, offset, 16)? {
271 let (result, next) = decode_error(data, offset)?;
272 offset = next;
273 Some(result)
274 } else {
275 None
276 };
277
278 Ok((
279 BACnetAuditNotification {
280 source_timestamp,
281 target_timestamp,
282 source_device,
283 source_object,
284 operation,
285 source_comment,
286 target_comment,
287 invoke_id,
288 source_user_id,
289 source_user_role,
290 target_device,
291 target_object,
292 target_property,
293 target_priority,
294 target_value,
295 current_value,
296 result,
297 },
298 offset,
299 ))
300}
301
302fn valid_operation(value: u32) -> bool {
303 (0..=15).contains(&value) || (32..=63).contains(&value)
304}
305
306fn encode_property_reference(buf: &mut BytesMut, property: &AuditPropertyReference) {
307 primitives::encode_ctx_enumerated(buf, 0, property.property_identifier.to_raw());
308 if let Some(index) = property.property_array_index {
309 primitives::encode_ctx_unsigned(buf, 1, index);
310 }
311}
312
313fn decode_property_reference(data: &[u8]) -> Result<(AuditPropertyReference, usize), Error> {
314 let (property, offset) = decode_ctx_canonical_unsigned::<u32>(
315 data,
316 0,
317 0,
318 "AuditNotification target-property identifier",
319 )?;
320 let (property_array_index, offset) = decode_optional_ctx(
321 data,
322 offset,
323 1,
324 "AuditNotification target-property array-index",
325 decode_ctx_canonical_unsigned::<u64>,
326 )?;
327 Ok((
328 AuditPropertyReference {
329 property_identifier: bacnet_types::enums::PropertyIdentifier::from_raw(property),
330 property_array_index,
331 },
332 offset,
333 ))
334}
335
336fn decode_wrapped_recipient(
337 data: &[u8],
338 offset: usize,
339 tag_number: u8,
340 what: &str,
341) -> Result<(bacnet_types::constructed::BACnetRecipient, usize), Error> {
342 let (body, next) = decode_ctx_constructed(data, offset, tag_number, what)?;
343 let (recipient, recipient_end) = decode_recipient(body, 0)?;
344 expect_end(body, recipient_end, offset, what)?;
345 let mut canonical = BytesMut::new();
346 write_recipient(&mut canonical, &recipient);
347 if canonical.as_ref() != body {
348 return Err(Error::decoding(
349 offset,
350 format!("{what} is not canonically encoded"),
351 ));
352 }
353 Ok((recipient, next))
354}
355
356fn decode_canonical_timestamp(
357 data: &[u8],
358 offset: usize,
359 tag_number: u8,
360 what: &str,
361) -> Result<(bacnet_types::primitives::BACnetTimeStamp, usize), Error> {
362 let (timestamp, next) = primitives::decode_timestamp(data, offset, tag_number)?;
363 let mut canonical = BytesMut::new();
364 primitives::encode_timestamp(&mut canonical, tag_number, ×tamp)?;
365 if canonical.as_ref() != &data[offset..next] {
366 return Err(Error::decoding(
367 offset,
368 format!("{what} is not canonically encoded"),
369 ));
370 }
371 Ok((timestamp, next))
372}
373
374fn decode_raw_value(
375 data: &[u8],
376 offset: usize,
377 tag_number: u8,
378 what: &str,
379) -> Result<(Vec<u8>, usize), Error> {
380 let (value, next) = decode_ctx_constructed(data, offset, tag_number, what)?;
381 validate_tlv_sequence(value, what)?;
382 Ok((value.to_vec(), next))
383}
384
385fn decode_error(data: &[u8], offset: usize) -> Result<((ErrorClass, ErrorCode), usize), Error> {
386 const WHAT: &str = "AuditNotification result";
387 let (body, next) = decode_ctx_constructed(data, offset, 16, WHAT)?;
388 let (class, body_offset) =
389 decode_app_canonical_enumerated(body, 0, "AuditNotification result error-class")?;
390 let (code, body_end) =
391 decode_app_canonical_enumerated(body, body_offset, "AuditNotification result error-code")?;
392 expect_end(body, body_end, offset, WHAT)?;
393 Ok((
394 (ErrorClass::from_raw(class), ErrorCode::from_raw(code)),
395 next,
396 ))
397}