Skip to main content

bacnet_encoding/constructed/
audit_notification.rs

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
16/// Encode one bare `BACnetAuditNotification` field sequence.
17pub 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(&notification.source_device)?;
45    check_encoded_recipient(&notification.target_device)?;
46
47    if let Some(timestamp) = &notification.source_timestamp {
48        primitives::encode_timestamp(buf, 0, timestamp)?;
49    }
50    if let Some(timestamp) = &notification.target_timestamp {
51        primitives::encode_timestamp(buf, 1, timestamp)?;
52    }
53
54    encode_wrapped_recipient(buf, 2, &notification.source_device);
55    if let Some(object) = &notification.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) = &notification.source_comment {
60        primitives::encode_ctx_character_string(buf, 5, comment)?;
61    }
62    if let Some(comment) = &notification.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, &notification.target_device);
76    if let Some(object) = &notification.target_object {
77        primitives::encode_ctx_object_id(buf, 11, object);
78    }
79    if let Some(property) = &notification.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) = &notification.target_value {
88        encode_raw_value(buf, 14, value);
89    }
90    if let Some(value) = &notification.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
118/// Check a raw value before encoding it. `what` names it in the error, written
119/// out whole, so a value that passes builds no label (#1645).
120fn 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
127/// Decode one bare `BACnetAuditNotification` starting at `offset`.
128pub 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, &timestamp)?;
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}