bacnet_client/tags.rs
1//! Typed interpretation of the value bytes returned by a Tags read.
2//!
3//! Raw ReadProperty and ReadPropertyMultiple results retain their original
4//! APIs. Pass a successful result's echoed array index and value bytes here.
5
6use bacnet_encoding::constructed::decode_name_value;
7use bacnet_encoding::primitives::decode_application_value;
8use bacnet_types::constructed::BACnetNameValue;
9use bacnet_types::error::Error;
10use bacnet_types::primitives::PropertyValue;
11
12/// The three forms of a Tags array read (Clause 12.1.5.1).
13#[derive(Debug, Clone, PartialEq)]
14pub enum TagsRead {
15 /// All tags, in wire order; an empty array is valid.
16 Whole(Vec<BACnetNameValue>),
17 /// One element selected by a one-based index.
18 Element(BACnetNameValue),
19 /// The array length read at index zero.
20 Size(u32),
21}
22
23/// Decode the entire payload of a successful Tags RP or RPM result.
24///
25/// `None` selects the whole array, `Some(0)` its Unsigned size and any
26/// positive index one element. Invalid or trailing bytes return an error;
27/// no successfully decoded prefix is returned. A semantic tag has no value,
28/// while a valued NULL has `Some(PropertyValue::Null)`. Date and Time are
29/// separate primitive choices; a combined pair is invalid (2024-04-29
30/// errata, item 37). This decoder does not validate application tag naming
31/// policies or impose the bundled server's provisioning limits.
32///
33/// ```
34/// use bacnet_client::tags::{decode_tags_read, TagsRead};
35/// assert_eq!(decode_tags_read(Some(0), &[0x21, 2]).unwrap(), TagsRead::Size(2));
36/// assert_eq!(decode_tags_read(None, &[]).unwrap(), TagsRead::Whole(vec![]));
37/// ```
38pub fn decode_tags_read(array_index: Option<u32>, octets: &[u8]) -> Result<TagsRead, Error> {
39 if array_index == Some(0) {
40 let (value, end) = decode_application_value(octets, 0)?;
41 if let PropertyValue::Unsigned(size) = value
42 && end == octets.len()
43 {
44 return u32::try_from(size)
45 .map(TagsRead::Size)
46 .map_err(|_| Error::decoding(0, "Tags array size exceeds u32"));
47 }
48 return Err(Error::decoding(0, "Tags array size must be one Unsigned"));
49 }
50 if array_index.is_some() {
51 let (tag, end) = decode_name_value(octets, 0)?;
52 if end != octets.len() {
53 return Err(Error::decoding(end, "trailing bytes after Tags element"));
54 }
55 return Ok(TagsRead::Element(tag));
56 }
57 let mut tags = Vec::new();
58 let mut offset = 0;
59 while offset < octets.len() {
60 let (tag, end) = decode_name_value(octets, offset)?;
61 if end <= offset || end > octets.len() {
62 return Err(Error::decoding(offset, "invalid Tags element boundary"));
63 }
64 tags.push(tag);
65 offset = end;
66 }
67 Ok(TagsRead::Whole(tags))
68}
69
70#[cfg(test)]
71mod tests;