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 if group_number == 0 {
84 return Err(Error::Encoding(
85 "WriteGroup group number 0 is reserved".into(),
86 ));
87 }
88 offset = end;
89
90 let (tag, pos) = tags::decode_tag(data, offset)?;
92 let end = pos + tag.length as usize;
93 if end > data.len() {
94 return Err(Error::decoding(
95 pos,
96 "WriteGroup truncated at write-priority",
97 ));
98 }
99 let write_priority = primitives::decode_unsigned(&data[pos..end])? as u8;
100 if !(1..=16).contains(&write_priority) {
101 return Err(Error::decoding(
102 pos,
103 format!("WriteGroup write-priority {write_priority} out of range 1-16"),
104 ));
105 }
106 offset = end;
107
108 let (tag, tag_end) = tags::decode_tag(data, offset)?;
110 if !tag.is_opening_tag(2) {
111 return Err(Error::decoding(offset, "WriteGroup expected opening tag 2"));
112 }
113 offset = tag_end;
114
115 let mut change_list = Vec::new();
116 loop {
117 if offset >= data.len() {
118 return Err(Error::decoding(offset, "WriteGroup missing closing tag 2"));
119 }
120 if change_list.len() >= MAX_DECODED_ITEMS {
121 return Err(Error::decoding(offset, "WriteGroup change list too large"));
122 }
123 let (tag, tag_end) = tags::decode_tag(data, offset)?;
124 if tag.is_closing_tag(2) {
125 offset = tag_end;
126 break;
127 }
128
129 let mut channel = None;
131 if tag.is_context(0) {
132 let end = tag_end + tag.length as usize;
133 if end > data.len() {
134 return Err(Error::decoding(tag_end, "WriteGroup truncated at channel"));
135 }
136 channel = Some(ObjectIdentifier::decode(&data[tag_end..end])?);
137 offset = end;
138 } else {
139 offset = tag_end - (tag_end - offset); }
141
142 let mut override_priority = None;
144 if offset < data.len() {
145 let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
146 if let Some(content) = opt {
147 override_priority = Some(primitives::decode_unsigned(content)? as u8);
148 offset = new_off;
149 }
150 }
151
152 let (tag, tag_end) = tags::decode_tag(data, offset)?;
154 if !tag.is_opening_tag(2) {
155 return Err(Error::decoding(
156 offset,
157 "WriteGroup expected opening tag 2 for value",
158 ));
159 }
160 let (value_bytes, new_off) = tags::extract_context_value(data, tag_end, 2)?;
161 let value = value_bytes.to_vec();
162 offset = new_off;
163
164 change_list.push(GroupChannelValue {
165 channel,
166 override_priority,
167 value,
168 });
169 }
170
171 let mut inhibit_delay = None;
173 if offset < data.len() {
174 let (opt, new_off) = tags::decode_optional_context(data, offset, 3)?;
175 if let Some(content) = opt {
176 inhibit_delay = Some(!content.is_empty() && content[0] != 0);
177 offset = new_off;
178 }
179 }
180 let _ = offset;
181
182 Ok(Self {
183 group_number,
184 write_priority,
185 change_list,
186 inhibit_delay,
187 })
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use bacnet_types::enums::ObjectType;
195
196 #[test]
197 fn write_group_round_trip() {
198 let req = WriteGroupRequest {
199 group_number: 1,
200 write_priority: 8,
201 change_list: vec![
202 GroupChannelValue {
203 channel: Some(ObjectIdentifier::new(ObjectType::CHANNEL, 1).unwrap()),
204 override_priority: Some(10),
205 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
206 },
207 GroupChannelValue {
208 channel: None,
209 override_priority: None,
210 value: vec![0x91, 0x01],
211 },
212 ],
213 inhibit_delay: Some(true),
214 };
215 let mut buf = BytesMut::new();
216 req.encode(&mut buf);
217 let decoded = WriteGroupRequest::decode(&buf).unwrap();
218 assert_eq!(req, decoded);
219 }
220
221 #[test]
222 fn write_group_minimal() {
223 let req = WriteGroupRequest {
224 group_number: 100,
225 write_priority: 16,
226 change_list: vec![GroupChannelValue {
227 channel: None,
228 override_priority: None,
229 value: vec![0x10],
230 }],
231 inhibit_delay: None,
232 };
233 let mut buf = BytesMut::new();
234 req.encode(&mut buf);
235 let decoded = WriteGroupRequest::decode(&buf).unwrap();
236 assert_eq!(req, decoded);
237 }
238
239 #[test]
240 fn write_group_priority_validation() {
241 let req = WriteGroupRequest {
243 group_number: 1,
244 write_priority: 8,
245 change_list: vec![GroupChannelValue {
246 channel: None,
247 override_priority: None,
248 value: vec![0x10],
249 }],
250 inhibit_delay: None,
251 };
252 let mut buf = BytesMut::new();
253 req.encode(&mut buf);
254 let mut data = buf.to_vec();
255 for i in 0..data.len() - 1 {
259 if data[i] == 0x19 && data[i + 1] == 0x08 {
260 data[i + 1] = 0x00; break;
262 }
263 }
264 assert!(WriteGroupRequest::decode(&data).is_err());
265 }
266
267 #[test]
268 fn write_group_empty_input() {
269 assert!(WriteGroupRequest::decode(&[]).is_err());
270 }
271}