bacnet_encoding/constructed/property_value/
mod.rs1#[cfg(feature = "alloc")]
15use bacnet_types::constructed::BACnetPropertyValue;
16use bacnet_types::enums::PropertyIdentifier;
17use bacnet_types::error::Error;
18#[cfg(feature = "alloc")]
19use bytes::{BufMut, BytesMut};
20
21#[cfg(feature = "alloc")]
22use crate::primitives;
23use crate::tags;
24
25#[cfg(feature = "alloc")]
26mod decode;
27
28#[cfg(feature = "alloc")]
29pub use decode::{
30 PropertyValueDecodeError, PropertyValueDecodeFailure, PropertyValueDecodeStage,
31 decode_bacnet_property_value, decode_bacnet_property_value_in_list,
32 decode_bacnet_property_value_in_list_detailed,
33};
34
35#[cfg(feature = "alloc")]
40pub fn encode_bacnet_property_value(value: &BACnetPropertyValue, buf: &mut BytesMut) {
41 primitives::encode_ctx_unsigned(buf, 0, value.property_identifier.to_raw() as u64);
43 if let Some(idx) = value.property_array_index {
45 primitives::encode_ctx_unsigned(buf, 1, idx as u64);
46 }
47 tags::encode_opening_tag(buf, 2);
49 buf.put_slice(&value.value);
50 tags::encode_closing_tag(buf, 2);
51 if let Some(prio) = value.priority {
53 primitives::encode_ctx_unsigned(buf, 3, prio as u64);
54 }
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum PropertyValueBoundary {
61 End,
63 Context(u8),
65 ContextToEnd(u8),
68 Closing(u8),
70}
71
72fn matches_property_boundary(data: &[u8], offset: usize, boundary: PropertyValueBoundary) -> bool {
74 match boundary {
75 PropertyValueBoundary::End => offset == data.len(),
76 PropertyValueBoundary::Context(number) => {
77 tags::decode_tag(data, offset).is_ok_and(|(tag, _)| tag.is_context(number))
78 }
79 PropertyValueBoundary::ContextToEnd(number) => {
80 tags::decode_tag(data, offset).is_ok_and(|(tag, content_start)| {
81 tag.is_context(number)
82 && content_start
83 .checked_add(tag.length as usize)
84 .is_some_and(|end| end == data.len())
85 })
86 }
87 PropertyValueBoundary::Closing(number) => {
88 tags::decode_tag(data, offset).is_ok_and(|(tag, _)| tag.is_closing_tag(number))
89 }
90 }
91}
92
93pub fn extract_property_value<'a>(
102 data: &'a [u8],
103 offset: usize,
104 closing_tag: u8,
105 property: PropertyIdentifier,
106 boundaries: &[PropertyValueBoundary],
107) -> Result<(&'a [u8], usize), Error> {
108 if property == PropertyIdentifier::EVENT_PARAMETERS
109 && data.get(offset..offset.saturating_add(2)) == Some(&[0xfe, 0xff])
110 {
111 let outer_close = (closing_tag << 4) | 0x0f;
116 let mut candidate = None;
117 for pos in offset.saturating_add(4)..data.len() {
118 let end = pos + 1;
119 if data[pos] == outer_close
120 && data.get(pos - 2..pos) == Some(&[0xff, 0xff])
121 && boundaries
122 .iter()
123 .any(|boundary| matches_property_boundary(data, end, *boundary))
124 {
125 if candidate.is_some() {
126 return Err(Error::invalid_tag(
127 offset,
128 "legacy EventParameters value has ambiguous closing tags",
129 ));
130 }
131 candidate = Some((pos, end));
132 }
133 }
134 if let Some((pos, end)) = candidate {
135 return Ok((&data[offset..pos], end));
136 }
137 return Err(Error::missing(
138 offset,
139 "legacy EventParameters value is missing its closing tags",
140 ));
141 }
142
143 tags::extract_context_value(data, offset, closing_tag)
144}