use bacnet_types::constructed::BACnetPropertyValue;
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::error::Error;
use bytes::{BufMut, BytesMut};
use crate::{primitives, tags};
mod decode;
pub use decode::{
decode_bacnet_property_value, decode_bacnet_property_value_in_list,
decode_bacnet_property_value_in_list_detailed, PropertyValueDecodeError,
PropertyValueDecodeFailure, PropertyValueDecodeStage,
};
pub fn encode_bacnet_property_value(value: &BACnetPropertyValue, buf: &mut BytesMut) {
primitives::encode_ctx_unsigned(buf, 0, value.property_identifier.to_raw() as u64);
if let Some(idx) = value.property_array_index {
primitives::encode_ctx_unsigned(buf, 1, idx as u64);
}
tags::encode_opening_tag(buf, 2);
buf.put_slice(&value.value);
tags::encode_closing_tag(buf, 2);
if let Some(prio) = value.priority {
primitives::encode_ctx_unsigned(buf, 3, prio as u64);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PropertyValueBoundary {
End,
Context(u8),
ContextToEnd(u8),
Closing(u8),
}
fn matches_property_boundary(data: &[u8], offset: usize, boundary: PropertyValueBoundary) -> bool {
match boundary {
PropertyValueBoundary::End => offset == data.len(),
PropertyValueBoundary::Context(number) => {
tags::decode_tag(data, offset).is_ok_and(|(tag, _)| tag.is_context(number))
}
PropertyValueBoundary::ContextToEnd(number) => {
tags::decode_tag(data, offset).is_ok_and(|(tag, content_start)| {
tag.is_context(number)
&& content_start
.checked_add(tag.length as usize)
.is_some_and(|end| end == data.len())
})
}
PropertyValueBoundary::Closing(number) => {
tags::decode_tag(data, offset).is_ok_and(|(tag, _)| tag.is_closing_tag(number))
}
}
}
pub fn extract_property_value<'a>(
data: &'a [u8],
offset: usize,
closing_tag: u8,
property: PropertyIdentifier,
boundaries: &[PropertyValueBoundary],
) -> Result<(&'a [u8], usize), Error> {
if property == PropertyIdentifier::EVENT_PARAMETERS
&& data.get(offset..offset.saturating_add(2)) == Some(&[0xfe, 0xff])
{
let outer_close = (closing_tag << 4) | 0x0f;
let mut candidate = None;
for pos in offset.saturating_add(4)..data.len() {
let end = pos + 1;
if data[pos] == outer_close
&& data.get(pos - 2..pos) == Some(&[0xff, 0xff])
&& boundaries
.iter()
.any(|boundary| matches_property_boundary(data, end, *boundary))
{
if candidate.is_some() {
return Err(Error::invalid_tag(
offset,
"legacy EventParameters value has ambiguous closing tags",
));
}
candidate = Some((pos, end));
}
}
if let Some((pos, end)) = candidate {
return Ok((&data[offset..pos], end));
}
return Err(Error::missing(
offset,
"legacy EventParameters value is missing its closing tags",
));
}
tags::extract_context_value(data, offset, closing_tag)
}