Skip to main content

bacnet_encoding/constructed/
audit_notification.rs

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
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(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(&notification.source_device)?;
46    check_encoded_recipient(&notification.target_device)?;
47
48    if let Some(timestamp) = &notification.source_timestamp {
49        primitives::encode_timestamp(buf, 0, timestamp)?;
50    }
51    if let Some(timestamp) = &notification.target_timestamp {
52        primitives::encode_timestamp(buf, 1, timestamp)?;
53    }
54
55    encode_wrapped_recipient(buf, 2, &notification.source_device);
56    if let Some(object) = &notification.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) = &notification.source_comment {
61        primitives::encode_ctx_character_string(buf, 5, comment)?;
62    }
63    if let Some(comment) = &notification.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, &notification.target_device);
77    if let Some(object) = &notification.target_object {
78        primitives::encode_ctx_object_id(buf, 11, object);
79    }
80    if let Some(property) = &notification.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) = &notification.target_value {
89        encode_raw_value(buf, 14, value);
90    }
91    if let Some(value) = &notification.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
119/// Check a raw value before encoding it. `what` names it in the error, written
120/// out whole, so a value that passes builds no label (#1645).
121fn 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
128/// Decode one bare `BACnetAuditNotification` starting at `offset`.
129pub 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, &timestamp)?;
369    if canonical.as_ref() != &data[offset..next] {
370        return Err(not_canonical(offset, what));
371    }
372    Ok((timestamp, next))
373}
374
375/// The refusal of a member `what` at `offset` whose encoding isn't the
376/// canonical one.
377#[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}