bacnet_encoding/constructed/
action_list.rs1use bacnet_types::constructed::{BACnetActionCommand, BACnetActionList};
13use bacnet_types::enums::PropertyIdentifier;
14use bacnet_types::error::Error;
15use bytes::BytesMut;
16
17use super::tagged::{
18 decode_ctx_boolean, decode_ctx_object_id, decode_ctx_unsigned, decode_framed_value,
19 decode_optional_ctx, expect_opening,
20};
21use super::MAX_FRAMED_ITEMS;
22use crate::{primitives, tags};
23
24const WHAT: &str = "BACnetActionCommand";
25
26pub fn encode_action_command(
31 buf: &mut BytesMut,
32 command: &BACnetActionCommand,
33) -> Result<(), Error> {
34 if let Some(priority) = command.priority {
35 if !(1..=16).contains(&priority) {
36 return Err(Error::OutOfRange(format!(
37 "{WHAT}: priority {priority} is outside 1..=16"
38 )));
39 }
40 }
41 let start = buf.len();
42 if let Some(device) = &command.device_identifier {
43 primitives::encode_ctx_object_id(buf, 0, device);
44 }
45 primitives::encode_ctx_object_id(buf, 1, &command.object_identifier);
46 primitives::encode_ctx_enumerated(buf, 2, command.property_identifier.to_raw());
47 if let Some(index) = command.property_array_index {
48 primitives::encode_ctx_unsigned(buf, 3, u64::from(index));
49 }
50 tags::encode_opening_tag(buf, 4);
51 if let Err(error) = primitives::encode_property_value(buf, &command.property_value) {
52 buf.truncate(start);
53 return Err(error);
54 }
55 tags::encode_closing_tag(buf, 4);
56 if let Some(priority) = command.priority {
57 primitives::encode_ctx_unsigned(buf, 5, u64::from(priority));
58 }
59 if let Some(delay) = command.post_delay {
60 primitives::encode_ctx_unsigned(buf, 6, u64::from(delay));
61 }
62 primitives::encode_ctx_boolean(buf, 7, command.quit_on_failure);
63 primitives::encode_ctx_boolean(buf, 8, command.write_successful);
64 Ok(())
65}
66
67pub fn decode_action_command(
78 data: &[u8],
79 offset: usize,
80) -> Result<(BACnetActionCommand, usize), Error> {
81 let (device_identifier, offset) =
82 decode_optional_ctx(data, offset, 0, WHAT, decode_ctx_object_id)?;
83 let (object_identifier, offset) = decode_ctx_object_id(data, offset, 1, WHAT)?;
84 let (property, offset) = decode_ctx_unsigned::<u32>(data, offset, 2, WHAT)?;
85 let property_identifier = PropertyIdentifier::from_raw(property);
86 let (property_array_index, offset) =
87 decode_optional_ctx(data, offset, 3, WHAT, decode_ctx_unsigned::<u32>)?;
88
89 let content = expect_opening(data, offset, 4, WHAT)?;
90 let (property_value, offset) = decode_framed_value(data, content, 4, WHAT)?;
91
92 let (priority, next) = decode_optional_ctx(data, offset, 5, WHAT, decode_ctx_unsigned::<u32>)?;
93 let priority = priority
94 .map(|value| {
95 u8::try_from(value)
96 .ok()
97 .filter(|p| (1..=16).contains(p))
98 .ok_or_else(|| {
99 Error::decoding(
100 offset,
101 format!("{WHAT}: priority {value} is outside 1..=16"),
102 )
103 })
104 })
105 .transpose()?;
106 let (post_delay, offset) =
107 decode_optional_ctx(data, next, 6, WHAT, decode_ctx_unsigned::<u32>)?;
108 let (quit_on_failure, offset) = decode_ctx_boolean(data, offset, 7, WHAT)?;
109 let (write_successful, offset) = decode_ctx_boolean(data, offset, 8, WHAT)?;
110 Ok((
111 BACnetActionCommand {
112 device_identifier,
113 object_identifier,
114 property_identifier,
115 property_array_index,
116 property_value,
117 priority,
118 post_delay,
119 quit_on_failure,
120 write_successful,
121 },
122 offset,
123 ))
124}
125
126pub fn encode_action_list(buf: &mut BytesMut, list: &BACnetActionList) -> Result<(), Error> {
129 let start = buf.len();
130 tags::encode_opening_tag(buf, 0);
131 for command in &list.commands {
132 if let Err(error) = encode_action_command(buf, command) {
133 buf.truncate(start);
134 return Err(error);
135 }
136 }
137 tags::encode_closing_tag(buf, 0);
138 Ok(())
139}
140
141pub fn decode_action_list(data: &[u8], offset: usize) -> Result<(BACnetActionList, usize), Error> {
145 let content = expect_opening(data, offset, 0, "BACnetActionList")?;
146 let (inner, end) = tags::extract_context_value(data, content, 0)?;
147 let body = &data[..content + inner.len()];
148 let mut commands = Vec::new();
149 let mut offset = content;
150 while offset < body.len() {
151 if commands.len() >= MAX_FRAMED_ITEMS {
152 return Err(Error::overflow(
153 offset,
154 "BACnetActionList: commands exceed item limit",
155 ));
156 }
157 let (command, next) = decode_action_command(body, offset)?;
158 commands.push(command);
159 offset = next;
160 }
161 Ok((BACnetActionList { commands }, end))
162}