use bacnet_types::constructed::BACnetObjectPropertyReference;
use bacnet_types::error::Error;
use bacnet_types::primitives::PropertyValue;
use crate::common;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum ReferenceFrame {
Bare,
Setpoint,
}
pub(crate) fn reference_read_value(
reference: &Option<BACnetObjectPropertyReference>,
) -> PropertyValue {
match reference {
Some(r) => {
let mut items = vec![
PropertyValue::ObjectIdentifier(r.object_identifier),
PropertyValue::Enumerated(r.property_identifier),
];
if let Some(index) = r.property_array_index {
items.push(PropertyValue::Unsigned(index as u64));
}
PropertyValue::List(items)
}
None => PropertyValue::Null,
}
}
pub(crate) fn decode_reference_write(
value: &PropertyValue,
frame: ReferenceFrame,
) -> Result<Option<BACnetObjectPropertyReference>, Error> {
match value {
PropertyValue::Null => Ok(None),
PropertyValue::ApplicationData(bytes) => decode_framed(bytes, frame),
PropertyValue::List(items) => match items.first() {
Some(PropertyValue::ObjectIdentifier(_)) => decode_legacy_list(items).map(Some),
Some(PropertyValue::ApplicationData(_)) => {
if !items
.iter()
.all(|item| matches!(item, PropertyValue::ApplicationData(_)))
{
return Err(common::invalid_data_encoding_error());
}
let mut bytes = Vec::new();
for item in items {
if let PropertyValue::ApplicationData(part) = item {
bytes.extend_from_slice(part);
}
}
decode_framed(&bytes, frame)
}
_ => Err(common::invalid_data_type_error()),
},
_ => Err(common::invalid_data_type_error()),
}
}
fn decode_legacy_list(items: &[PropertyValue]) -> Result<BACnetObjectPropertyReference, Error> {
let Some(PropertyValue::ObjectIdentifier(object_identifier)) = items.first() else {
return Err(common::invalid_data_type_error());
};
let property_identifier = match items.get(1) {
Some(PropertyValue::Enumerated(property)) => *property,
Some(PropertyValue::Unsigned(property)) => {
u32::try_from(*property).map_err(|_| common::invalid_data_type_error())?
}
_ => return Err(common::invalid_data_type_error()),
};
let property_array_index = match items.get(2) {
None => None,
Some(PropertyValue::Unsigned(index)) => {
Some(u32::try_from(*index).map_err(|_| common::invalid_data_type_error())?)
}
Some(_) => return Err(common::invalid_data_type_error()),
};
if items.len() > 3 {
return Err(common::invalid_data_type_error());
}
Ok(BACnetObjectPropertyReference {
object_identifier: *object_identifier,
property_identifier,
property_array_index,
})
}
fn decode_framed(
bytes: &[u8],
frame: ReferenceFrame,
) -> Result<Option<BACnetObjectPropertyReference>, Error> {
let bare = bacnet_encoding::constructed::decode_object_property_reference(bytes);
match (bare, frame) {
(Ok(reference), _) => Ok(Some(reference)),
(Err(_), ReferenceFrame::Setpoint) => {
match bacnet_encoding::constructed::decode_setpoint_reference(bytes) {
Ok(reference) => Ok(reference),
Err(_) => Err(common::invalid_data_encoding_error()),
}
}
(Err(_), ReferenceFrame::Bare) => Err(common::invalid_data_encoding_error()),
}
}
#[cfg(test)]
mod tests;