bacnet_encoding/constructed/property_value/
mod.rs1use bacnet_types::constructed::BACnetPropertyValue;
15use bacnet_types::enums::PropertyIdentifier;
16use bacnet_types::error::Error;
17use bytes::{BufMut, BytesMut};
18
19use crate::{primitives, tags};
20
21mod decode;
22
23pub use decode::{
24 decode_bacnet_property_value, decode_bacnet_property_value_in_list,
25 decode_bacnet_property_value_in_list_detailed, PropertyValueDecodeError,
26 PropertyValueDecodeFailure, PropertyValueDecodeStage,
27};
28
29pub fn encode_bacnet_property_value(value: &BACnetPropertyValue, buf: &mut BytesMut) {
34 primitives::encode_ctx_unsigned(buf, 0, value.property_identifier.to_raw() as u64);
36 if let Some(idx) = value.property_array_index {
38 primitives::encode_ctx_unsigned(buf, 1, idx as u64);
39 }
40 tags::encode_opening_tag(buf, 2);
42 buf.put_slice(&value.value);
43 tags::encode_closing_tag(buf, 2);
44 if let Some(prio) = value.priority {
46 primitives::encode_ctx_unsigned(buf, 3, prio as u64);
47 }
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub enum PropertyValueBoundary {
54 End,
56 Context(u8),
58 ContextToEnd(u8),
61 Closing(u8),
63}
64
65fn matches_property_boundary(data: &[u8], offset: usize, boundary: PropertyValueBoundary) -> bool {
67 match boundary {
68 PropertyValueBoundary::End => offset == data.len(),
69 PropertyValueBoundary::Context(number) => {
70 tags::decode_tag(data, offset).is_ok_and(|(tag, _)| tag.is_context(number))
71 }
72 PropertyValueBoundary::ContextToEnd(number) => {
73 tags::decode_tag(data, offset).is_ok_and(|(tag, content_start)| {
74 tag.is_context(number)
75 && content_start
76 .checked_add(tag.length as usize)
77 .is_some_and(|end| end == data.len())
78 })
79 }
80 PropertyValueBoundary::Closing(number) => {
81 tags::decode_tag(data, offset).is_ok_and(|(tag, _)| tag.is_closing_tag(number))
82 }
83 }
84}
85
86pub fn extract_property_value<'a>(
95 data: &'a [u8],
96 offset: usize,
97 closing_tag: u8,
98 property: PropertyIdentifier,
99 boundaries: &[PropertyValueBoundary],
100) -> Result<(&'a [u8], usize), Error> {
101 if property == PropertyIdentifier::EVENT_PARAMETERS
102 && data.get(offset..offset.saturating_add(2)) == Some(&[0xfe, 0xff])
103 {
104 let outer_close = (closing_tag << 4) | 0x0f;
109 let mut candidate = None;
110 for pos in offset.saturating_add(4)..data.len() {
111 let end = pos + 1;
112 if data[pos] == outer_close
113 && data.get(pos - 2..pos) == Some(&[0xff, 0xff])
114 && boundaries
115 .iter()
116 .any(|boundary| matches_property_boundary(data, end, *boundary))
117 {
118 if candidate.is_some() {
119 return Err(Error::invalid_tag(
120 offset,
121 "legacy EventParameters value has ambiguous closing tags",
122 ));
123 }
124 candidate = Some((pos, end));
125 }
126 }
127 if let Some((pos, end)) = candidate {
128 return Ok((&data[offset..pos], end));
129 }
130 return Err(Error::missing(
131 offset,
132 "legacy EventParameters value is missing its closing tags",
133 ));
134 }
135
136 tags::extract_context_value(data, offset, closing_tag)
137}