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::{ObjectIdentifier, PropertyValue};
6use bytes::BytesMut;
7
8use crate::primitives;
9use crate::tags;
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    let mut offset = offset;
77    let mut device_identifier = None;
78    let (first, first_content) = tags::decode_tag(data, offset)?;
79    if first.is_context(0) {
80        let end = object_id_end(data, offset, first_content, first.length, 0)?;
81        device_identifier = Some(ObjectIdentifier::decode(&data[first_content..end])?);
82        offset = end;
83    }
84
85    let (object_tag, object_content) = tags::decode_tag(data, offset)?;
86    if !object_tag.is_context(1) || object_tag.length != 4 {
87        return Err(Error::decoding(
88            offset,
89            "device-object reference requires object-identifier [1]",
90        ));
91    }
92    let end = object_id_end(data, offset, object_content, object_tag.length, 1)?;
93    let object_identifier = ObjectIdentifier::decode(&data[object_content..end])?;
94
95    Ok((
96        BACnetDeviceObjectReference {
97            device_identifier,
98            object_identifier,
99        },
100        end,
101    ))
102}
103
104fn object_id_end(
105    data: &[u8],
106    tag_offset: usize,
107    content: usize,
108    length: u32,
109    tag: u8,
110) -> Result<usize, Error> {
111    if length != 4 {
112        return Err(Error::decoding(
113            tag_offset,
114            format!("device-object reference [{tag}] must contain a 4-octet object identifier"),
115        ));
116    }
117    let end = content
118        .checked_add(4)
119        .ok_or_else(|| Error::decoding(content, "device-object reference length overflow"))?;
120    if end > data.len() {
121        return Err(Error::buffer_too_short(end, data.len()));
122    }
123    Ok(end)
124}
125
126fn pack_bits(values: &[bool]) -> (u8, Vec<u8>) {
127    if values.is_empty() {
128        return (0, Vec::new());
129    }
130    let mut data = vec![0; values.len().div_ceil(8)];
131    for (index, value) in values.iter().enumerate() {
132        if *value {
133            data[index / 8] |= 0x80 >> (index % 8);
134        }
135    }
136    ((data.len() * 8 - values.len()) as u8, data)
137}
138
139fn unpack_bits(unused_bits: u8, data: &[u8], offset: usize) -> Result<Vec<bool>, Error> {
140    if data.is_empty() {
141        if unused_bits == 0 {
142            return Ok(Vec::new());
143        }
144        return Err(Error::decoding(
145            offset,
146            "empty stage values BIT STRING must declare zero unused bits",
147        ));
148    }
149    let mask = if unused_bits == 0 {
150        0
151    } else {
152        (1_u8 << unused_bits) - 1
153    };
154    if data.last().copied().unwrap_or_default() & mask != 0 {
155        return Err(Error::decoding(
156            offset,
157            "stage values BIT STRING has nonzero padding bits",
158        ));
159    }
160    let bit_len = data
161        .len()
162        .checked_mul(8)
163        .and_then(|bits| bits.checked_sub(unused_bits as usize))
164        .ok_or_else(|| Error::decoding(offset, "invalid stage values BIT STRING length"))?;
165    Ok((0..bit_len)
166        .map(|index| data[index / 8] & (0x80 >> (index % 8)) != 0)
167        .collect())
168}