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::{BACnetPropertyValue, MAX_DECODED_ITEMS};
10
11#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct WriteAccessSpecification {
18 pub object_identifier: ObjectIdentifier,
19 pub list_of_properties: Vec<BACnetPropertyValue>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct WritePropertyMultipleRequest {
27 pub list_of_write_access_specs: Vec<WriteAccessSpecification>,
28}
29
30impl WritePropertyMultipleRequest {
31 pub fn encode(&self, buf: &mut BytesMut) {
32 for spec in &self.list_of_write_access_specs {
33 primitives::encode_ctx_object_id(buf, 0, &spec.object_identifier);
34 tags::encode_opening_tag(buf, 1);
35 for prop_val in &spec.list_of_properties {
36 prop_val.encode(buf);
37 }
38 tags::encode_closing_tag(buf, 1);
39 }
40 }
41
42 pub fn decode(data: &[u8]) -> Result<Self, Error> {
43 let mut offset = 0;
44 let mut specs = Vec::new();
45
46 while offset < data.len() {
47 if specs.len() >= MAX_DECODED_ITEMS {
48 return Err(Error::decoding(
49 offset,
50 "WPM request exceeds max decoded items",
51 ));
52 }
53
54 let (tag, pos) = tags::decode_tag(data, offset)?;
56 let end = pos + tag.length as usize;
57 if end > data.len() {
58 return Err(Error::decoding(pos, "WPM request truncated at object-id"));
59 }
60 let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
61 offset = end;
62
63 let (tag, tag_end) = tags::decode_tag(data, offset)?;
65 if !tag.is_opening_tag(1) {
66 return Err(Error::decoding(
67 offset,
68 "WPM request expected opening tag 1",
69 ));
70 }
71 offset = tag_end;
72
73 let mut props = Vec::new();
74 loop {
75 if offset >= data.len() {
76 return Err(Error::decoding(offset, "WPM request missing closing tag 1"));
77 }
78 if props.len() >= MAX_DECODED_ITEMS {
79 return Err(Error::decoding(offset, "WPM properties exceeds max"));
80 }
81 let (tag, tag_end) = tags::decode_tag(data, offset)?;
82 if tag.is_closing_tag(1) {
83 offset = tag_end;
84 break;
85 }
86 let (pv, new_offset) = BACnetPropertyValue::decode(data, offset)?;
87 props.push(pv);
88 offset = new_offset;
89 }
90
91 specs.push(WriteAccessSpecification {
92 object_identifier,
93 list_of_properties: props,
94 });
95 }
96
97 Ok(Self {
98 list_of_write_access_specs: specs,
99 })
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106 use bacnet_types::enums::{ObjectType, PropertyIdentifier};
107
108 #[test]
109 fn request_round_trip() {
110 let req = WritePropertyMultipleRequest {
111 list_of_write_access_specs: vec![WriteAccessSpecification {
112 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
113 list_of_properties: vec![BACnetPropertyValue {
114 property_identifier: PropertyIdentifier::PRESENT_VALUE,
115 property_array_index: None,
116 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
117 priority: Some(8),
118 }],
119 }],
120 };
121 let mut buf = BytesMut::new();
122 req.encode(&mut buf);
123 let decoded = WritePropertyMultipleRequest::decode(&buf).unwrap();
124 assert_eq!(req, decoded);
125 }
126
127 #[test]
128 fn multi_object_round_trip() {
129 let req = WritePropertyMultipleRequest {
130 list_of_write_access_specs: vec![
131 WriteAccessSpecification {
132 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
133 list_of_properties: vec![BACnetPropertyValue {
134 property_identifier: PropertyIdentifier::PRESENT_VALUE,
135 property_array_index: None,
136 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
137 priority: None,
138 }],
139 },
140 WriteAccessSpecification {
141 object_identifier: ObjectIdentifier::new(ObjectType::BINARY_OUTPUT, 2).unwrap(),
142 list_of_properties: vec![BACnetPropertyValue {
143 property_identifier: PropertyIdentifier::PRESENT_VALUE,
144 property_array_index: None,
145 value: vec![0x91, 0x01],
146 priority: Some(8),
147 }],
148 },
149 ],
150 };
151 let mut buf = BytesMut::new();
152 req.encode(&mut buf);
153 let decoded = WritePropertyMultipleRequest::decode(&buf).unwrap();
154 assert_eq!(req, decoded);
155 }
156
157 #[test]
162 fn test_decode_wpm_request_truncated_1_byte() {
163 let req = WritePropertyMultipleRequest {
164 list_of_write_access_specs: vec![WriteAccessSpecification {
165 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
166 list_of_properties: vec![BACnetPropertyValue {
167 property_identifier: PropertyIdentifier::PRESENT_VALUE,
168 property_array_index: None,
169 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
170 priority: Some(8),
171 }],
172 }],
173 };
174 let mut buf = BytesMut::new();
175 req.encode(&mut buf);
176 assert!(WritePropertyMultipleRequest::decode(&buf[..1]).is_err());
177 }
178
179 #[test]
180 fn test_decode_wpm_request_truncated_3_bytes() {
181 let req = WritePropertyMultipleRequest {
182 list_of_write_access_specs: vec![WriteAccessSpecification {
183 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
184 list_of_properties: vec![BACnetPropertyValue {
185 property_identifier: PropertyIdentifier::PRESENT_VALUE,
186 property_array_index: None,
187 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
188 priority: Some(8),
189 }],
190 }],
191 };
192 let mut buf = BytesMut::new();
193 req.encode(&mut buf);
194 assert!(WritePropertyMultipleRequest::decode(&buf[..3]).is_err());
195 }
196
197 #[test]
198 fn test_decode_wpm_request_truncated_half() {
199 let req = WritePropertyMultipleRequest {
200 list_of_write_access_specs: vec![WriteAccessSpecification {
201 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
202 list_of_properties: vec![BACnetPropertyValue {
203 property_identifier: PropertyIdentifier::PRESENT_VALUE,
204 property_array_index: None,
205 value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
206 priority: Some(8),
207 }],
208 }],
209 };
210 let mut buf = BytesMut::new();
211 req.encode(&mut buf);
212 let half = buf.len() / 2;
213 assert!(WritePropertyMultipleRequest::decode(&buf[..half]).is_err());
214 }
215
216 #[test]
217 fn test_decode_wpm_request_invalid_tag() {
218 assert!(WritePropertyMultipleRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
219 }
220}