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