#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bacnet_encoding::constructed::tagged::{
decode_app_enumerated, decode_ctx_object_id, decode_ctx_unsigned, decode_optional_ctx,
expect_closing, expect_opening,
};
use bacnet_encoding::constructed::{
decode_read_access_specification, encode_read_access_specification, extract_property_value,
PropertyValueBoundary,
};
use bacnet_encoding::primitives;
use bacnet_encoding::tags;
use bacnet_types::constructed::ReadAccessSpecification;
use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier};
use bacnet_types::error::Error;
use bacnet_types::primitives::ObjectIdentifier;
use bytes::BytesMut;
use crate::common::MAX_DECODED_ITEMS;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadPropertyMultipleRequest {
pub list_of_read_access_specs: Vec<ReadAccessSpecification>,
}
impl ReadPropertyMultipleRequest {
pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
if self.list_of_read_access_specs.is_empty()
|| self
.list_of_read_access_specs
.iter()
.any(|spec| spec.list_of_property_references.is_empty())
{
return Err(Error::encode("RPM requires nonempty object and property lists"));
}
for spec in &self.list_of_read_access_specs {
encode_read_access_specification(buf, spec);
}
Ok(())
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let mut offset = 0;
let mut specs = Vec::new();
while offset < data.len() {
if specs.len() >= MAX_DECODED_ITEMS {
return Err(Error::overflow(
offset,
"RPM request exceeds max decoded items",
));
}
let (spec, next) = decode_read_access_specification(data, offset)?;
specs.push(spec);
offset = next;
}
Ok(Self {
list_of_read_access_specs: specs,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadResultElement {
pub property_identifier: PropertyIdentifier,
pub property_array_index: Option<u32>,
pub property_value: Option<Vec<u8>>,
pub error: Option<(ErrorClass, ErrorCode)>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadAccessResult {
pub object_identifier: ObjectIdentifier,
pub list_of_results: Vec<ReadResultElement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReadPropertyMultipleACK {
pub list_of_read_access_results: Vec<ReadAccessResult>,
}
impl ReadAccessResult {
pub fn encode(&self, buf: &mut BytesMut) {
Self::encode_header(buf, &self.object_identifier);
for elem in &self.list_of_results {
elem.encode(buf);
}
Self::encode_footer(buf);
}
pub fn encode_header(buf: &mut BytesMut, object_identifier: &ObjectIdentifier) {
primitives::encode_ctx_object_id(buf, 0, object_identifier);
tags::encode_opening_tag(buf, 1);
}
pub fn encode_footer(buf: &mut BytesMut) {
tags::encode_closing_tag(buf, 1);
}
}
impl ReadResultElement {
pub fn encode(&self, buf: &mut BytesMut) {
primitives::encode_ctx_unsigned(buf, 2, self.property_identifier.to_raw() as u64);
if let Some(idx) = self.property_array_index {
primitives::encode_ctx_unsigned(buf, 3, idx as u64);
}
if let Some(ref value) = self.property_value {
tags::encode_opening_tag(buf, 4);
buf.extend_from_slice(value);
tags::encode_closing_tag(buf, 4);
} else if let Some((class, code)) = self.error {
tags::encode_opening_tag(buf, 5);
primitives::encode_app_enumerated(buf, u32::from(class.to_raw()));
primitives::encode_app_enumerated(buf, u32::from(code.to_raw()));
tags::encode_closing_tag(buf, 5);
}
}
}
impl ReadPropertyMultipleACK {
pub fn encode(&self, buf: &mut BytesMut) {
for result in &self.list_of_read_access_results {
result.encode(buf);
}
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let mut offset = 0;
let mut results = Vec::new();
while offset < data.len() {
if results.len() >= MAX_DECODED_ITEMS {
return Err(Error::overflow(offset, "RPM ACK exceeds max decoded items"));
}
let (object_identifier, end) =
decode_ctx_object_id(data, offset, 0, "RPM ACK object-id")?;
offset = expect_opening(data, end, 1, "RPM ACK list-of-results")?;
let mut elements = Vec::new();
loop {
if offset >= data.len() {
return Err(Error::decoding(offset, "RPM ACK missing closing tag 1"));
}
if elements.len() >= MAX_DECODED_ITEMS {
return Err(Error::overflow(offset, "RPM ACK results exceeds max"));
}
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if tag.is_closing_tag(1) {
offset = tag_end;
break;
}
let (prop_raw, end) =
decode_ctx_unsigned::<u32>(data, offset, 2, "RPM ACK property-id")?;
let property_identifier = PropertyIdentifier::from_raw(prop_raw);
let (array_index, end) = decode_optional_ctx(
data,
end,
3,
"RPM ACK array-index",
decode_ctx_unsigned::<u32>,
)?;
offset = end;
let (tag, tag_end) = tags::decode_tag(data, offset)?;
if tag.is_opening_tag(4) {
let (value_bytes, new_offset) = extract_property_value(
data,
tag_end,
4,
property_identifier,
&[
PropertyValueBoundary::Context(2),
PropertyValueBoundary::Closing(1),
],
)?;
elements.push(ReadResultElement {
property_identifier,
property_array_index: array_index,
property_value: Some(value_bytes.to_vec()),
error: None,
});
offset = new_offset;
} else if tag.is_opening_tag(5) {
let (error_class, error_code, new_offset) = decode_error_pair(data, tag_end)?;
elements.push(ReadResultElement {
property_identifier,
property_array_index: array_index,
property_value: None,
error: Some((error_class, error_code)),
});
offset = new_offset;
} else if array_index.is_some() {
return Err(Error::decoding(offset, "RPM ACK expected tag 4 or 5"));
} else {
return Err(Error::decoding(offset, "RPM ACK expected tag 3, 4, or 5"));
}
}
results.push(ReadAccessResult {
object_identifier,
list_of_results: elements,
});
}
Ok(Self {
list_of_read_access_results: results,
})
}
}
fn decode_error_pair(data: &[u8], offset: usize) -> Result<(ErrorClass, ErrorCode, usize), Error> {
let (error_class, offset) = decode_app_enumerated::<u16>(data, offset, "RPM error class")?;
let (error_code, offset) = decode_app_enumerated::<u16>(data, offset, "RPM error code")?;
let end = expect_closing(data, offset, 5, "RPM error")?;
Ok((
ErrorClass::from_raw(error_class),
ErrorCode::from_raw(error_code),
end,
))
}
#[cfg(test)]
#[path = "rpm_tests.rs"]
mod tests;