use bacnet_encoding::primitives;
use bacnet_encoding::tags;
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::error::Error;
use bytes::{BufMut, BytesMut};
pub const MAX_DECODED_ITEMS: usize = 10_000;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PropertyReference {
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
}
impl PropertyReference {
pub fn encode(&self, buf: &mut BytesMut) {
primitives::encode_ctx_unsigned(buf, 0, self.property_identifier.to_raw() as u64);
if let Some(idx) = self.property_array_index {
primitives::encode_ctx_unsigned(buf, 1, idx as u64);
}
}
pub fn decode(data: &[u8], offset: usize) -> Result<(Self, usize), Error> {
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"PropertyReference truncated at property-id",
));
}
let prop_id = primitives::decode_unsigned(&data[pos..end])? as u32;
let mut offset = end;
let mut array_index = None;
if offset < data.len() {
let (tag, new_pos) = tags::decode_tag(data, offset)?;
if tag.is_context(1) {
let end = new_pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
new_pos,
"PropertyReference truncated at array-index",
));
}
array_index = Some(primitives::decode_unsigned(&data[new_pos..end])? as u32);
offset = end;
}
}
Ok((
Self {
property_identifier: PropertyIdentifier::from_raw(prop_id),
property_array_index: array_index,
},
offset,
))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BACnetPropertyValue {
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
pub value: Vec<u8>,
pub priority: Option<u8>,
}
impl BACnetPropertyValue {
pub fn encode(&self, buf: &mut BytesMut) {
primitives::encode_ctx_unsigned(buf, 0, self.property_identifier.to_raw() as u64);
if let Some(idx) = self.property_array_index {
primitives::encode_ctx_unsigned(buf, 1, idx as u64);
}
tags::encode_opening_tag(buf, 2);
buf.put_slice(&self.value);
tags::encode_closing_tag(buf, 2);
if let Some(prio) = self.priority {
primitives::encode_ctx_unsigned(buf, 3, prio as u64);
}
}
pub fn decode(data: &[u8], offset: usize) -> Result<(Self, usize), Error> {
let (tag, pos) = tags::decode_tag(data, offset)?;
let end = pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
pos,
"BACnetPropertyValue truncated at property-id",
));
}
let prop_id = primitives::decode_unsigned(&data[pos..end])? as u32;
let mut offset = end;
let mut array_index = None;
if offset < data.len() {
let (tag, new_pos) = tags::decode_tag(data, offset)?;
if tag.is_context(1) {
let end = new_pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
new_pos,
"BACnetPropertyValue truncated at array-index",
));
}
array_index = Some(primitives::decode_unsigned(&data[new_pos..end])? as u32);
offset = end;
}
}
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if !tag.is_opening_tag(2) {
return Err(Error::decoding(
offset,
"BACnetPropertyValue expected opening tag 2",
));
}
let (value_bytes, offset) = tags::extract_context_value(data, tag_end, 2)?;
let value = value_bytes.to_vec();
let mut priority = None;
if offset < data.len() {
let (tag, new_pos) = tags::decode_tag(data, offset)?;
if tag.is_context(3) {
let end = new_pos + tag.length as usize;
if end > data.len() {
return Err(Error::decoding(
new_pos,
"BACnetPropertyValue truncated at priority",
));
}
let prio = primitives::decode_unsigned(&data[new_pos..end])? as u8;
if !(1..=16).contains(&prio) {
return Err(Error::decoding(
new_pos,
format!("BACnetPropertyValue priority {prio} out of range 1-16"),
));
}
priority = Some(prio);
return Ok((
Self {
property_identifier: PropertyIdentifier::from_raw(prop_id),
property_array_index: array_index,
value,
priority,
},
end,
));
}
}
Ok((
Self {
property_identifier: PropertyIdentifier::from_raw(prop_id),
property_array_index: array_index,
value,
priority,
},
offset,
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn property_reference_round_trip() {
let pr = PropertyReference {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
};
let mut buf = BytesMut::new();
pr.encode(&mut buf);
let (decoded, _) = PropertyReference::decode(&buf, 0).unwrap();
assert_eq!(pr, decoded);
}
#[test]
fn property_reference_with_index_round_trip() {
let pr = PropertyReference {
property_identifier: PropertyIdentifier::PRIORITY_ARRAY,
property_array_index: Some(8),
};
let mut buf = BytesMut::new();
pr.encode(&mut buf);
let (decoded, _) = PropertyReference::decode(&buf, 0).unwrap();
assert_eq!(pr, decoded);
}
#[test]
fn bacnet_property_value_round_trip() {
let pv = BACnetPropertyValue {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
value: vec![0x44, 0x42, 0x90, 0x00, 0x00], priority: None,
};
let mut buf = BytesMut::new();
pv.encode(&mut buf);
let (decoded, _) = BACnetPropertyValue::decode(&buf, 0).unwrap();
assert_eq!(pv, decoded);
}
#[test]
fn bacnet_property_value_with_all_fields() {
let pv = BACnetPropertyValue {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: Some(5),
value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
priority: Some(8),
};
let mut buf = BytesMut::new();
pv.encode(&mut buf);
let (decoded, _) = BACnetPropertyValue::decode(&buf, 0).unwrap();
assert_eq!(pv, decoded);
}
#[test]
fn bacnet_property_value_priority_validation() {
let pv = BACnetPropertyValue {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
value: vec![0x10], priority: Some(8),
};
let mut buf = BytesMut::new();
pv.encode(&mut buf);
let data = buf.to_vec();
let mut corrupted = data.clone();
let last = corrupted.len() - 1;
corrupted[last] = 0; assert!(BACnetPropertyValue::decode(&corrupted, 0).is_err());
}
#[test]
fn test_decode_property_reference_empty_input() {
assert!(PropertyReference::decode(&[], 0).is_err());
}
#[test]
fn test_decode_property_reference_truncated_1_byte() {
let pr = PropertyReference {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: Some(8),
};
let mut buf = BytesMut::new();
pr.encode(&mut buf);
assert!(PropertyReference::decode(&buf[..1], 0).is_err());
}
#[test]
fn test_decode_property_reference_invalid_tag() {
assert!(PropertyReference::decode(&[0xFF, 0xFF, 0xFF], 0).is_err());
}
#[test]
fn test_decode_bacnet_property_value_empty_input() {
assert!(BACnetPropertyValue::decode(&[], 0).is_err());
}
#[test]
fn test_decode_bacnet_property_value_truncated_1_byte() {
let pv = BACnetPropertyValue {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
priority: None,
};
let mut buf = BytesMut::new();
pv.encode(&mut buf);
assert!(BACnetPropertyValue::decode(&buf[..1], 0).is_err());
}
#[test]
fn test_decode_bacnet_property_value_truncated_2_bytes() {
let pv = BACnetPropertyValue {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
priority: None,
};
let mut buf = BytesMut::new();
pv.encode(&mut buf);
assert!(BACnetPropertyValue::decode(&buf[..2], 0).is_err());
}
#[test]
fn test_decode_bacnet_property_value_truncated_3_bytes() {
let pv = BACnetPropertyValue {
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
priority: None,
};
let mut buf = BytesMut::new();
pv.encode(&mut buf);
assert!(BACnetPropertyValue::decode(&buf[..3], 0).is_err());
}
#[test]
fn test_decode_bacnet_property_value_invalid_tag() {
assert!(BACnetPropertyValue::decode(&[0xFF, 0xFF, 0xFF], 0).is_err());
}
#[test]
fn test_decode_bacnet_property_value_oversized_length() {
assert!(BACnetPropertyValue::decode(&[0x05, 0xFF], 0).is_err());
}
}