Skip to main content

bacnet_encoding/constructed/
staging.rs

1//! Clause 21 codecs used by the Staging object.
2
3#[cfg(not(feature = "std"))]
4use alloc::vec::Vec;
5
6use bacnet_types::constructed::{BACnetDeviceObjectReference, BACnetStageLimitValue};
7use bacnet_types::error::Error;
8use bacnet_types::primitives::PropertyValue;
9use bytes::BytesMut;
10
11use super::tagged::{decode_ctx_object_id, decode_optional_ctx};
12use crate::primitives;
13
14/// Encode one unframed `BACnetStageLimitValue` SEQUENCE.
15pub fn encode_stage_limit_value(buf: &mut BytesMut, value: &BACnetStageLimitValue) {
16    primitives::encode_app_real(buf, value.limit);
17    let (unused_bits, data) = pack_bits(&value.values);
18    primitives::encode_app_bit_string(buf, unused_bits, &data);
19    primitives::encode_app_real(buf, value.deadband);
20}
21
22/// Decode one unframed `BACnetStageLimitValue` SEQUENCE at `offset`.
23pub fn decode_stage_limit_value(
24    data: &[u8],
25    offset: usize,
26) -> Result<(BACnetStageLimitValue, usize), Error> {
27    let (limit, offset) = primitives::decode_application_value(data, offset)?;
28    let PropertyValue::Real(limit) = limit else {
29        return Err(Error::decoding(
30            offset,
31            "stage limit must be application REAL",
32        ));
33    };
34
35    let (values, offset) = primitives::decode_application_value(data, offset)?;
36    let PropertyValue::BitString {
37        unused_bits,
38        data: packed,
39    } = values
40    else {
41        return Err(Error::decoding(
42            offset,
43            "stage values must be application BIT STRING",
44        ));
45    };
46    let values = unpack_bits(unused_bits, &packed, offset)?;
47
48    let (deadband, offset) = primitives::decode_application_value(data, offset)?;
49    let PropertyValue::Real(deadband) = deadband else {
50        return Err(Error::decoding(
51            offset,
52            "stage deadband must be application REAL",
53        ));
54    };
55
56    Ok((
57        BACnetStageLimitValue {
58            limit,
59            values,
60            deadband,
61        },
62        offset,
63    ))
64}
65
66/// Encode one unframed `BACnetDeviceObjectReference` SEQUENCE.
67pub fn encode_device_object_reference(buf: &mut BytesMut, reference: &BACnetDeviceObjectReference) {
68    if let Some(device) = &reference.device_identifier {
69        primitives::encode_ctx_object_id(buf, 0, device);
70    }
71    primitives::encode_ctx_object_id(buf, 1, &reference.object_identifier);
72}
73
74/// Decode one unframed `BACnetDeviceObjectReference` SEQUENCE at `offset`.
75pub fn decode_device_object_reference(
76    data: &[u8],
77    offset: usize,
78) -> Result<(BACnetDeviceObjectReference, usize), Error> {
79    const WHAT: &str = "BACnetDeviceObjectReference";
80    let (device_identifier, offset) =
81        decode_optional_ctx(data, offset, 0, WHAT, decode_ctx_object_id)?;
82    let (object_identifier, end) = decode_ctx_object_id(data, offset, 1, WHAT)?;
83    Ok((
84        BACnetDeviceObjectReference {
85            device_identifier,
86            object_identifier,
87        },
88        end,
89    ))
90}
91
92fn pack_bits(values: &[bool]) -> (u8, Vec<u8>) {
93    if values.is_empty() {
94        return (0, Vec::new());
95    }
96    let mut data = vec![0; values.len().div_ceil(8)];
97    for (index, value) in values.iter().enumerate() {
98        if *value {
99            data[index / 8] |= 0x80 >> (index % 8);
100        }
101    }
102    ((data.len() * 8 - values.len()) as u8, data)
103}
104
105fn unpack_bits(unused_bits: u8, data: &[u8], offset: usize) -> Result<Vec<bool>, Error> {
106    if data.is_empty() {
107        if unused_bits == 0 {
108            return Ok(Vec::new());
109        }
110        return Err(Error::decoding(
111            offset,
112            "empty stage values BIT STRING must declare zero unused bits",
113        ));
114    }
115    let mask = if unused_bits == 0 {
116        0
117    } else {
118        (1_u8 << unused_bits) - 1
119    };
120    if data.last().copied().unwrap_or_default() & mask != 0 {
121        return Err(Error::decoding(
122            offset,
123            "stage values BIT STRING has nonzero padding bits",
124        ));
125    }
126    let bit_len = data
127        .len()
128        .checked_mul(8)
129        .and_then(|bits| bits.checked_sub(unused_bits as usize))
130        .ok_or_else(|| Error::decoding(offset, "invalid stage values BIT STRING length"))?;
131    Ok((0..bit_len)
132        .map(|index| data[index / 8] & (0x80 >> (index % 8)) != 0)
133        .collect())
134}