1use bacnet_encoding::primitives;
4use bacnet_encoding::tags;
5use bacnet_types::error::Error;
6use bacnet_types::primitives::ObjectIdentifier;
7use bytes::BytesMut;
8
9use crate::common::MAX_DECODED_ITEMS;
10
11#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct GroupChannelValue {
18 pub channel: Option<ObjectIdentifier>,
20 pub override_priority: Option<u8>,
22 pub value: Vec<u8>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct WriteGroupRequest {
38 pub group_number: u32,
39 pub write_priority: u8,
40 pub change_list: Vec<GroupChannelValue>,
41 pub inhibit_delay: Option<bool>,
42}
43
44impl WriteGroupRequest {
45 pub fn encode(&self, buf: &mut BytesMut) {
46 primitives::encode_ctx_unsigned(buf, 0, self.group_number as u64);
48 primitives::encode_ctx_unsigned(buf, 1, self.write_priority as u64);
50 tags::encode_opening_tag(buf, 2);
52 for entry in &self.change_list {
53 if let Some(ref ch) = entry.channel {
55 primitives::encode_ctx_object_id(buf, 0, ch);
56 }
57 if let Some(prio) = entry.override_priority {
59 primitives::encode_ctx_unsigned(buf, 1, prio as u64);
60 }
61 tags::encode_opening_tag(buf, 2);
63 buf.extend_from_slice(&entry.value);
64 tags::encode_closing_tag(buf, 2);
65 }
66 tags::encode_closing_tag(buf, 2);
67 if let Some(v) = self.inhibit_delay {
69 primitives::encode_ctx_boolean(buf, 3, v);
70 }
71 }
72
73 pub fn decode(data: &[u8]) -> Result<Self, Error> {
74 let mut offset = 0;
75
76 let (tag, pos) = tags::decode_tag(data, offset)?;
78 let end = pos + tag.length as usize;
79 if end > data.len() {
80 return Err(Error::decoding(pos, "WriteGroup truncated at group-number"));
81 }
82 let group_number = primitives::decode_unsigned(&data[pos..end])? as u32;
83 offset = end;
84
85 let (tag, pos) = tags::decode_tag(data, offset)?;
87 let end = pos + tag.length as usize;
88 if end > data.len() {
89 return Err(Error::decoding(
90 pos,
91 "WriteGroup truncated at write-priority",
92 ));
93 }
94 let write_priority = primitives::decode_unsigned(&data[pos..end])? as u8;
95 if !(1..=16).contains(&write_priority) {
96 return Err(Error::decoding(
97 pos,
98 format!("WriteGroup write-priority {write_priority} out of range 1-16"),
99 ));
100 }
101 offset = end;
102
103 let (tag, tag_end) = tags::decode_tag(data, offset)?;
105 if !tag.is_opening_tag(2) {
106 return Err(Error::decoding(offset, "WriteGroup expected opening tag 2"));
107 }
108 offset = tag_end;
109
110 let mut change_list = Vec::new();
111 loop {
112 if offset >= data.len() {
113 return Err(Error::decoding(offset, "WriteGroup missing closing tag 2"));
114 }
115 if change_list.len() >= MAX_DECODED_ITEMS {
116 return Err(Error::decoding(offset, "WriteGroup change list too large"));
117 }
118 let (tag, tag_end) = tags::decode_tag(data, offset)?;
119 if tag.is_closing_tag(2) {
120 offset = tag_end;
121 break;
122 }
123
124 let mut channel = None;
126 if tag.is_context(0) {
127 let end = tag_end + tag.length as usize;
128 if end > data.len() {
129 return Err(Error::decoding(tag_end, "WriteGroup truncated at channel"));
130 }
131 channel = Some(ObjectIdentifier::decode(&data[tag_end..end])?);
132 offset = end;
133 } else {
134 offset = tag_end - (tag_end - offset); }
136
137 let mut override_priority = None;
139 if offset < data.len() {
140 let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
141 if let Some(content) = opt {
142 override_priority = Some(primitives::decode_unsigned(content)? as u8);
143 offset = new_off;
144 }
145 }
146
147 let (tag, tag_end) = tags::decode_tag(data, offset)?;
149 if !tag.is_opening_tag(2) {
150 return Err(Error::decoding(
151 offset,
152 "WriteGroup expected opening tag 2 for value",
153 ));
154 }
155 let (value_bytes, new_off) = tags::extract_context_value(data, tag_end, 2)?;
156 let value = value_bytes.to_vec();
157 offset = new_off;
158
159 change_list.push(GroupChannelValue {
160 channel,
161 override_priority,
162 value,
163 });
164 }
165
166 let mut inhibit_delay = None;
168 if offset < data.len() {
169 let (opt, new_off) = tags::decode_optional_context(data, offset, 3)?;
170 if let Some(content) = opt {
171 inhibit_delay = Some(!content.is_empty() && content[0] != 0);
172 offset = new_off;
173 }
174 }
175 let _ = offset;
176
177 Ok(Self {
178 group_number,
179 write_priority,
180 change_list,
181 inhibit_delay,
182 })
183 }
184}
185
186#[cfg(test)]
191mod tests {
192 use super::*;
193 use bacnet_types::enums::ObjectType;
194
195 #[test]
196 fn write_group_round_trip() {
197 let req = WriteGroupRequest {
198 group_number: 1,
199 write_priority: 8,
200 change_list: vec![
201 GroupChannelValue {
202 channel: Some(ObjectIdentifier::new(ObjectType::CHANNEL, 1).unwrap()),
203 override_priority: Some(10),
204 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
205 },
206 GroupChannelValue {
207 channel: None,
208 override_priority: None,
209 value: vec![0x91, 0x01],
210 },
211 ],
212 inhibit_delay: Some(true),
213 };
214 let mut buf = BytesMut::new();
215 req.encode(&mut buf);
216 let decoded = WriteGroupRequest::decode(&buf).unwrap();
217 assert_eq!(req, decoded);
218 }
219
220 #[test]
221 fn write_group_minimal() {
222 let req = WriteGroupRequest {
223 group_number: 100,
224 write_priority: 16,
225 change_list: vec![GroupChannelValue {
226 channel: None,
227 override_priority: None,
228 value: vec![0x10],
229 }],
230 inhibit_delay: None,
231 };
232 let mut buf = BytesMut::new();
233 req.encode(&mut buf);
234 let decoded = WriteGroupRequest::decode(&buf).unwrap();
235 assert_eq!(req, decoded);
236 }
237
238 #[test]
239 fn write_group_priority_validation() {
240 let req = WriteGroupRequest {
242 group_number: 1,
243 write_priority: 8,
244 change_list: vec![GroupChannelValue {
245 channel: None,
246 override_priority: None,
247 value: vec![0x10],
248 }],
249 inhibit_delay: None,
250 };
251 let mut buf = BytesMut::new();
252 req.encode(&mut buf);
253 let mut data = buf.to_vec();
254 for i in 0..data.len() - 1 {
258 if data[i] == 0x19 && data[i + 1] == 0x08 {
259 data[i + 1] = 0x00; break;
261 }
262 }
263 assert!(WriteGroupRequest::decode(&data).is_err());
264 }
265
266 #[test]
267 fn write_group_empty_input() {
268 assert!(WriteGroupRequest::decode(&[]).is_err());
269 }
270}