Skip to main content

bacnet_encoding/constructed/
property_access_result.rs

1//! `BACnetPropertyAccessResult` (ASHRAE 135-2020 Clause 21), the element of a
2//! Global Group's Present_Value array (Clause 12.50.7).
3//!
4//! An element opens with the members of a `BACnetDeviceObjectPropertyReference`
5//! under the same context tags: the object `[0]`, the property `[1]`, then the
6//! optional array index `[2]` and device `[3]`. The result follows as one of
7//! two constructed alternatives: the value read inside an opening/closing
8//! context tag `[4]` pair, or the error inside a `[5]` pair, carried as an
9//! application-tagged ENUMERATED class and then code. An array of these
10//! elements concatenates them with no frame.
11
12use bacnet_types::constructed::{AccessResult, BACnetPropertyAccessResult};
13use bacnet_types::enums::{ErrorClass, ErrorCode};
14use bacnet_types::error::Error;
15use bytes::BytesMut;
16
17use super::tagged::{decode_app_enumerated, decode_framed_value, expect_closing};
18use super::{decode_dopr_body, encode_dopr_body};
19use crate::{primitives, tags};
20
21const WHAT: &str = "BACnetPropertyAccessResult";
22
23/// Encode one bare `BACnetPropertyAccessResult`, appending to `buf`.
24///
25/// Fails only when the value itself can't be encoded (see
26/// [`primitives::encode_property_value`]), and then leaves `buf` as it was;
27/// the reference and an error result always encode.
28pub fn encode_property_access_result(
29    buf: &mut BytesMut,
30    result: &BACnetPropertyAccessResult,
31) -> Result<(), Error> {
32    let start = buf.len();
33    encode_dopr_body(buf, &result.reference);
34    match &result.access_result {
35        AccessResult::Value(value) => {
36            tags::encode_opening_tag(buf, 4);
37            if let Err(error) = primitives::encode_property_value(buf, value) {
38                buf.truncate(start);
39                return Err(error);
40            }
41            tags::encode_closing_tag(buf, 4);
42        }
43        AccessResult::Error { class, code } => {
44            tags::encode_opening_tag(buf, 5);
45            primitives::encode_app_enumerated(buf, u32::from(class.to_raw()));
46            primitives::encode_app_enumerated(buf, u32::from(code.to_raw()));
47            tags::encode_closing_tag(buf, 5);
48        }
49    }
50    Ok(())
51}
52
53/// Decode one bare `BACnetPropertyAccessResult` at `offset`; returns it and
54/// the offset past its closing `[4]` or `[5]` tag, so an array is walked by
55/// calling this at each element's start.
56///
57/// The value inside `[4]` decodes as one application value when it holds
58/// one element, and as a [`PropertyValue::List`] otherwise (an array read
59/// whole, empty included). A context-tagged element in it decodes to
60/// [`PropertyValue::ApplicationData`], so the value re-encodes to the same
61/// octets.
62///
63/// [`PropertyValue::List`]: bacnet_types::primitives::PropertyValue::List
64/// [`PropertyValue::ApplicationData`]: bacnet_types::primitives::PropertyValue::ApplicationData
65pub fn decode_property_access_result(
66    data: &[u8],
67    offset: usize,
68) -> Result<(BACnetPropertyAccessResult, usize), Error> {
69    let (reference, offset) = decode_dopr_body(data, offset, WHAT)?;
70    let (tag, content) = tags::decode_tag(data, offset)?;
71    let (access_result, end) = if tag.is_opening_tag(4) {
72        let (value, end) = decode_framed_value(data, content, 4, WHAT)?;
73        (AccessResult::Value(value), end)
74    } else if tag.is_opening_tag(5) {
75        let (class, next) = decode_app_enumerated::<u32>(data, content, WHAT)?;
76        let (code, next) = decode_app_enumerated::<u32>(data, next, WHAT)?;
77        let class = u16::try_from(class).map_err(|_| {
78            Error::out_of_range(content, format!("{WHAT}: error class exceeds u16"))
79        })?;
80        let code = u16::try_from(code)
81            .map_err(|_| Error::out_of_range(content, format!("{WHAT}: error code exceeds u16")))?;
82        let end = expect_closing(data, next, 5, WHAT)?;
83        let error = AccessResult::Error {
84            class: ErrorClass::from_raw(class),
85            code: ErrorCode::from_raw(code),
86        };
87        (error, end)
88    } else {
89        return Err(Error::decoding(
90            offset,
91            format!("{WHAT}: expected property-value [4] or property-access-error [5]"),
92        ));
93    };
94    Ok((
95        BACnetPropertyAccessResult {
96            reference,
97            access_result,
98        },
99        end,
100    ))
101}