Skip to main content

bacnet_encoding/constructed/
value_source.rs

1//! BACnetValueSource CHOICE framing (ASHRAE 135-2020 Clause 21).
2use bacnet_types::constructed::{BACnetAddress, BACnetValueSource};
3use bacnet_types::error::Error;
4use bytes::BytesMut;
5
6use super::recipient::{check_encoded_mac_len, decode_app_mac_address};
7use super::tagged::{decode_app_unsigned, expect_closing};
8use super::{decode_device_object_reference, encode_device_object_reference};
9use crate::{primitives, tags};
10
11const WHAT: &str = "BACnetValueSource";
12
13/// Encode one ValueSource CHOICE, appending to `buf`.
14///
15/// The generic codec preserves legal wire object types, wildcard instances,
16/// local network zero and empty (broadcast) MAC addresses. Source identity and
17/// authorization are mechanism-level concerns, not datatype restrictions.
18///
19/// A MAC longer than [`BACnetAddress::MAX_MAC_LEN`] octets, which the decoder
20/// refuses, returns an error before modifying the output buffer (#1156).
21pub fn encode_value_source(buf: &mut BytesMut, source: &BACnetValueSource) -> Result<(), Error> {
22    if let BACnetValueSource::Address(address) = source {
23        check_encoded_mac_len(&address.mac_address, WHAT)?;
24    }
25    match source {
26        BACnetValueSource::None => tags::encode_tag(buf, 0, tags::TagClass::Context, 0),
27        BACnetValueSource::Object(reference) => {
28            tags::encode_opening_tag(buf, 1);
29            encode_device_object_reference(buf, reference);
30            tags::encode_closing_tag(buf, 1);
31        }
32        BACnetValueSource::Address(address) => {
33            tags::encode_opening_tag(buf, 2);
34            primitives::encode_app_unsigned(buf, u64::from(address.network_number));
35            primitives::encode_app_octet_string(buf, &address.mac_address);
36            tags::encode_closing_tag(buf, 2);
37        }
38    }
39    Ok(())
40}
41
42/// Decode one ValueSource CHOICE at `offset`, returning its value and next offset.
43///
44/// Bytes after that CHOICE are left for the caller, allowing concatenated array
45/// elements. A full-property consumer must check that the returned offset equals
46/// the payload length. Structural acceptance does not establish a valid or
47/// authorized command source. An address MAC longer than
48/// [`BACnetAddress::MAX_MAC_LEN`] octets is refused before it is copied (#1156).
49pub fn decode_value_source(
50    data: &[u8],
51    offset: usize,
52) -> Result<(BACnetValueSource, usize), Error> {
53    let (tag, pos) = tags::decode_tag(data, offset)?;
54    if tag.is_context(0) && tag.length == 0 {
55        return Ok((BACnetValueSource::None, pos));
56    }
57    if tag.is_opening_tag(1) {
58        let (reference, end) = decode_device_object_reference(data, pos)?;
59        let end = expect_closing(data, end, 1, WHAT)?;
60        return Ok((BACnetValueSource::Object(reference), end));
61    }
62    if tag.is_opening_tag(2) {
63        let (network, pos) = decode_app_unsigned::<u64>(data, pos, WHAT)?;
64        let network_number = u16::try_from(network).map_err(|_| {
65            Error::out_of_range(pos, format!("{WHAT}: network number exceeds Unsigned16"))
66        })?;
67        let (mac_address, pos) = decode_app_mac_address(data, pos, WHAT)?;
68        let end = expect_closing(data, pos, 2, WHAT)?;
69        return Ok((
70            BACnetValueSource::Address(BACnetAddress {
71                network_number,
72                mac_address,
73            }),
74            end,
75        ));
76    }
77    Err(Error::decoding(
78        offset,
79        format!("{WHAT}: expected NULL [0], object [1], or address [2]"),
80    ))
81}