1#[cfg(not(feature = "std"))]
7use alloc::vec::Vec;
8
9use core::num::NonZeroU32;
10
11use bacnet_encoding::constructed::channel_value_end;
12use bacnet_encoding::primitives;
13use bacnet_encoding::tags;
14use bacnet_types::error::{DecodeError, DecodingKind, EncodeError, Error};
15use bytes::BytesMut;
16
17use crate::common::MAX_DECODED_ITEMS;
18use bacnet_encoding::constructed::tagged::{
19 decode_ctx_boolean, decode_ctx_primitive, next_is_context,
20};
21
22const PRIORITY_RANGE: core::ops::RangeInclusive<u64> = 1..=16;
28
29fn check_priority(label: &'static str, value: u8) -> Result<u8, Error> {
31 if PRIORITY_RANGE.contains(&u64::from(value)) {
32 Ok(value)
33 } else {
34 Err(EncodeError::new(label).with_value(value.into()).into())
35 }
36}
37
38fn decode_priority(label: &'static str, offset: usize, value: u64) -> Result<u8, Error> {
40 if PRIORITY_RANGE.contains(&value) {
41 Ok(value as u8)
42 } else {
43 Err(DecodeError::new(DecodingKind::InvalidEncoding, offset, label)
44 .with_value(value)
45 .into())
46 }
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
55pub struct GroupChannelValue {
56 pub channel: u16,
58 pub override_priority: Option<u8>,
61 pub value: Vec<u8>,
66}
67
68#[derive(Debug, Clone, PartialEq, Eq)]
83pub struct WriteGroupRequest {
84 pub group_number: NonZeroU32,
87 pub write_priority: u8,
90 pub change_list: Vec<GroupChannelValue>,
92 pub inhibit_delay: Option<bool>,
95}
96
97impl WriteGroupRequest {
98 pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
104 check_priority(
105 "WriteGroup write-priority {} out of range 1-16",
106 self.write_priority,
107 )?;
108 if self.change_list.is_empty() {
109 return Err(Error::encode("WriteGroup change list must contain at least one entry"));
110 }
111 for entry in &self.change_list {
112 if let Some(priority) = entry.override_priority {
113 check_priority(
114 "WriteGroup override-priority {} out of range 1-16",
115 priority,
116 )?;
117 }
118 let well_formed =
119 channel_value_end(&entry.value, 0).is_ok_and(|end| end == entry.value.len());
120 if !well_formed {
121 return Err(EncodeError::new(
122 "WriteGroup value for channel {} is not a single BACnetChannelValue",
123 )
124 .with_value(entry.channel.into())
125 .into());
126 }
127 }
128
129 primitives::encode_ctx_unsigned(buf, 0, u64::from(self.group_number.get()));
130 primitives::encode_ctx_unsigned(buf, 1, u64::from(self.write_priority));
131 tags::encode_opening_tag(buf, 2);
132 for entry in &self.change_list {
133 primitives::encode_ctx_unsigned(buf, 0, u64::from(entry.channel));
134 if let Some(priority) = entry.override_priority {
135 primitives::encode_ctx_unsigned(buf, 1, u64::from(priority));
136 }
137 buf.extend_from_slice(&entry.value);
138 }
139 tags::encode_closing_tag(buf, 2);
140 if let Some(inhibit) = self.inhibit_delay {
141 primitives::encode_ctx_boolean(buf, 3, inhibit);
142 }
143 Ok(())
144 }
145
146 pub fn decode(data: &[u8]) -> Result<Self, Error> {
150 let (content, mut offset) = decode_ctx_primitive(data, 0, 0, "WriteGroup group-number")?;
152 let group_raw = primitives::decode_unsigned(content)?;
153 let group_number = u32::try_from(group_raw)
154 .ok()
155 .and_then(NonZeroU32::new)
156 .ok_or_else(|| {
157 DecodeError::new(
158 DecodingKind::OutOfRange,
159 0,
160 "WriteGroup group number {} out of range 1-4294967295",
161 )
162 .with_value(group_raw)
163 })?;
164
165 let priority_pos = offset;
167 let (content, end) = decode_ctx_primitive(data, offset, 1, "WriteGroup write-priority")?;
168 let write_priority = decode_priority(
169 "WriteGroup write-priority {} out of range 1-16",
170 priority_pos,
171 primitives::decode_unsigned(content)?,
172 )?;
173 offset = end;
174
175 if offset >= data.len() {
177 return Err(Error::decoding(offset, "WriteGroup missing change list"));
178 }
179 let (tag, tag_end) = tags::decode_tag(data, offset)?;
180 if !tag.is_opening_tag(2) {
181 return Err(Error::decoding(offset, "WriteGroup expected opening tag 2"));
182 }
183 offset = tag_end;
184
185 let mut change_list = Vec::new();
186 loop {
187 if offset >= data.len() {
188 return Err(Error::decoding(offset, "WriteGroup missing closing tag 2"));
189 }
190 let (tag, tag_end) = tags::decode_tag(data, offset)?;
191 if tag.is_closing_tag(2) {
192 offset = tag_end;
193 break;
194 }
195 if change_list.len() >= MAX_DECODED_ITEMS {
196 return Err(Error::decoding(offset, "WriteGroup change list too large"));
197 }
198
199 let (content, end) = decode_ctx_primitive(data, offset, 0, "WriteGroup channel")?;
201 let channel_raw = primitives::decode_unsigned(content)?;
202 let channel = u16::try_from(channel_raw).map_err(|_| {
203 DecodeError::new(
204 DecodingKind::OutOfRange,
205 offset,
206 "WriteGroup channel {} exceeds 65535",
207 )
208 .with_value(channel_raw)
209 })?;
210 offset = end;
211
212 let mut override_priority = None;
214 if next_is_context(data, offset, 1)? {
215 let (content, end) =
216 decode_ctx_primitive(data, offset, 1, "WriteGroup override-priority")?;
217 override_priority = Some(decode_priority(
218 "WriteGroup override-priority {} out of range 1-16",
219 offset,
220 primitives::decode_unsigned(content)?,
221 )?);
222 offset = end;
223 }
224
225 let end = channel_value_end(data, offset)?;
227 change_list.push(GroupChannelValue {
228 channel,
229 override_priority,
230 value: data[offset..end].to_vec(),
231 });
232 offset = end;
233 }
234 if change_list.is_empty() {
235 return Err(Error::decoding(
236 offset,
237 "WriteGroup change list must contain at least one entry",
238 ));
239 }
240
241 let mut inhibit_delay = None;
243 if next_is_context(data, offset, 3)? {
244 let (inhibit, end) = decode_ctx_boolean(data, offset, 3, "WriteGroup inhibit-delay")?;
245 inhibit_delay = Some(inhibit);
246 offset = end;
247 }
248 if offset != data.len() {
249 return Err(Error::decoding(offset, "WriteGroup has trailing data"));
250 }
251
252 Ok(Self {
253 group_number,
254 write_priority,
255 change_list,
256 inhibit_delay,
257 })
258 }
259}
260
261#[cfg(test)]
262#[path = "write_group_tests.rs"]
263mod tests;