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