Skip to main content

bacnet_encoding/constructed/
staging.rs

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