#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bacnet_types::constructed::{BACnetActionCommand, BACnetActionList};
use bacnet_types::enums::PropertyIdentifier;
use bacnet_types::error::Error;
use bytes::BytesMut;
use super::tagged::{
decode_ctx_boolean, decode_ctx_object_id, decode_ctx_unsigned, decode_framed_value,
decode_optional_ctx, expect_opening,
};
use super::MAX_FRAMED_ITEMS;
use crate::{primitives, tags};
const WHAT: &str = "BACnetActionCommand";
pub fn encode_action_command(
buf: &mut BytesMut,
command: &BACnetActionCommand,
) -> Result<(), Error> {
if let Some(priority) = command.priority {
if !(1..=16).contains(&priority) {
return Err(Error::OutOfRange(format!(
"{WHAT}: priority {priority} is outside 1..=16"
)));
}
}
let start = buf.len();
if let Some(device) = &command.device_identifier {
primitives::encode_ctx_object_id(buf, 0, device);
}
primitives::encode_ctx_object_id(buf, 1, &command.object_identifier);
primitives::encode_ctx_enumerated(buf, 2, command.property_identifier.to_raw());
if let Some(index) = command.property_array_index {
primitives::encode_ctx_unsigned(buf, 3, u64::from(index));
}
tags::encode_opening_tag(buf, 4);
if let Err(error) = primitives::encode_property_value(buf, &command.property_value) {
buf.truncate(start);
return Err(error);
}
tags::encode_closing_tag(buf, 4);
if let Some(priority) = command.priority {
primitives::encode_ctx_unsigned(buf, 5, u64::from(priority));
}
if let Some(delay) = command.post_delay {
primitives::encode_ctx_unsigned(buf, 6, u64::from(delay));
}
primitives::encode_ctx_boolean(buf, 7, command.quit_on_failure);
primitives::encode_ctx_boolean(buf, 8, command.write_successful);
Ok(())
}
pub fn decode_action_command(
data: &[u8],
offset: usize,
) -> Result<(BACnetActionCommand, usize), Error> {
let (device_identifier, offset) =
decode_optional_ctx(data, offset, 0, WHAT, decode_ctx_object_id)?;
let (object_identifier, offset) = decode_ctx_object_id(data, offset, 1, WHAT)?;
let (property, offset) = decode_ctx_unsigned::<u32>(data, offset, 2, WHAT)?;
let property_identifier = PropertyIdentifier::from_raw(property);
let (property_array_index, offset) =
decode_optional_ctx(data, offset, 3, WHAT, decode_ctx_unsigned::<u32>)?;
let content = expect_opening(data, offset, 4, WHAT)?;
let (property_value, offset) = decode_framed_value(data, content, 4, WHAT)?;
let (priority, next) = decode_optional_ctx(data, offset, 5, WHAT, decode_ctx_unsigned::<u32>)?;
let priority = priority
.map(|value| {
u8::try_from(value)
.ok()
.filter(|p| (1..=16).contains(p))
.ok_or_else(|| {
Error::decoding(
offset,
format!("{WHAT}: priority {value} is outside 1..=16"),
)
})
})
.transpose()?;
let (post_delay, offset) =
decode_optional_ctx(data, next, 6, WHAT, decode_ctx_unsigned::<u32>)?;
let (quit_on_failure, offset) = decode_ctx_boolean(data, offset, 7, WHAT)?;
let (write_successful, offset) = decode_ctx_boolean(data, offset, 8, WHAT)?;
Ok((
BACnetActionCommand {
device_identifier,
object_identifier,
property_identifier,
property_array_index,
property_value,
priority,
post_delay,
quit_on_failure,
write_successful,
},
offset,
))
}
pub fn encode_action_list(buf: &mut BytesMut, list: &BACnetActionList) -> Result<(), Error> {
let start = buf.len();
tags::encode_opening_tag(buf, 0);
for command in &list.commands {
if let Err(error) = encode_action_command(buf, command) {
buf.truncate(start);
return Err(error);
}
}
tags::encode_closing_tag(buf, 0);
Ok(())
}
pub fn decode_action_list(data: &[u8], offset: usize) -> Result<(BACnetActionList, usize), Error> {
let content = expect_opening(data, offset, 0, "BACnetActionList")?;
let (inner, end) = tags::extract_context_value(data, content, 0)?;
let body = &data[..content + inner.len()];
let mut commands = Vec::new();
let mut offset = content;
while offset < body.len() {
if commands.len() >= MAX_FRAMED_ITEMS {
return Err(Error::overflow(
offset,
"BACnetActionList: commands exceed item limit",
));
}
let (command, next) = decode_action_command(body, offset)?;
commands.push(command);
offset = next;
}
Ok((BACnetActionList { commands }, end))
}