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