Skip to main content

bacnet_encoding/constructed/
audit_notification.rs

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
13/// Encode one bare `BACnetAuditNotification` field sequence.
14pub 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(&notification.source_device)?;
36    check_encoded_recipient(&notification.target_device)?;
37
38    if let Some(timestamp) = &notification.source_timestamp {
39        primitives::encode_timestamp(buf, 0, timestamp)?;
40    }
41    if let Some(timestamp) = &notification.target_timestamp {
42        primitives::encode_timestamp(buf, 1, timestamp)?;
43    }
44
45    encode_wrapped_recipient(buf, 2, &notification.source_device);
46    if let Some(object) = &notification.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) = &notification.source_comment {
51        primitives::encode_ctx_character_string(buf, 5, comment)?;
52    }
53    if let Some(comment) = &notification.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, &notification.target_device);
67    if let Some(object) = &notification.target_object {
68        primitives::encode_ctx_object_id(buf, 11, object);
69    }
70    if let Some(property) = &notification.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) = &notification.target_value {
79        encode_raw_value(buf, 14, value);
80    }
81    if let Some(value) = &notification.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
117/// Decode one bare `BACnetAuditNotification` starting at `offset`.
118pub 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, &timestamp)?;
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}