1use bacnet_encoding::primitives;
4use bacnet_encoding::tags::{self, TagClass};
5use bacnet_types::enums::PropertyIdentifier;
6use bacnet_types::error::Error;
7use bacnet_types::primitives::ObjectIdentifier;
8use bytes::BytesMut;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct WritePropertyRequest {
29 pub object_identifier: ObjectIdentifier,
30 pub property_identifier: PropertyIdentifier,
31 pub property_array_index: Option<u32>,
32 pub property_value: Vec<u8>,
33 pub priority: Option<u8>,
34}
35
36impl WritePropertyRequest {
37 pub fn encode(&self, buf: &mut BytesMut) {
38 primitives::encode_ctx_object_id(buf, 0, &self.object_identifier);
39 primitives::encode_ctx_unsigned(buf, 1, self.property_identifier.to_raw() as u64);
40 if let Some(idx) = self.property_array_index {
41 primitives::encode_ctx_unsigned(buf, 2, idx as u64);
42 }
43 tags::encode_opening_tag(buf, 3);
44 buf.extend_from_slice(&self.property_value);
45 tags::encode_closing_tag(buf, 3);
46 if let Some(prio) = self.priority {
47 primitives::encode_ctx_unsigned(buf, 4, prio as u64);
48 }
49 }
50
51 pub fn decode(data: &[u8]) -> Result<Self, Error> {
52 let mut offset = 0;
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, "WriteProperty truncated at object-id"));
59 }
60 let object_identifier = ObjectIdentifier::decode(&data[pos..end])?;
61 offset = end;
62
63 let (tag, pos) = tags::decode_tag(data, offset)?;
65 let end = pos + tag.length as usize;
66 if end > data.len() {
67 return Err(Error::decoding(
68 pos,
69 "WriteProperty truncated at property-id",
70 ));
71 }
72 let prop_raw = primitives::decode_unsigned(&data[pos..end])? as u32;
73 let property_identifier = PropertyIdentifier::from_raw(prop_raw);
74 offset = end;
75
76 let mut property_array_index = None;
78 let (tag, tag_end) = tags::decode_tag(data, offset)?;
79 if tag.class == TagClass::Context && tag.number == 2 && !tag.is_opening && !tag.is_closing {
80 let end = tag_end + tag.length as usize;
81 if end > data.len() {
82 return Err(Error::decoding(
83 tag_end,
84 "WriteProperty truncated at array-index",
85 ));
86 }
87 property_array_index = Some(primitives::decode_unsigned(&data[tag_end..end])? as u32);
88 offset = end;
89 }
90
91 let (tag, tag_end) = tags::decode_tag(data, offset)?;
93 if !tag.is_opening_tag(3) {
94 return Err(Error::decoding(
95 offset,
96 "WriteProperty expected opening tag 3",
97 ));
98 }
99 let (value_bytes, new_offset) = tags::extract_context_value(data, tag_end, 3)?;
100 let property_value = value_bytes.to_vec();
101 offset = new_offset;
102
103 let mut priority = None;
105 if offset < data.len() {
106 let (tag, pos) = tags::decode_tag(data, offset)?;
107 if tag.is_context(4) {
108 let end = pos + tag.length as usize;
109 if end > data.len() {
110 return Err(Error::decoding(pos, "WriteProperty truncated at priority"));
111 }
112 let prio = primitives::decode_unsigned(&data[pos..end])? as u8;
113 if !(1..=16).contains(&prio) {
114 return Err(Error::decoding(
115 pos,
116 format!("WriteProperty priority {prio} out of range 1-16"),
117 ));
118 }
119 priority = Some(prio);
120 }
121 }
122
123 Ok(Self {
124 object_identifier,
125 property_identifier,
126 property_array_index,
127 property_value,
128 priority,
129 })
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use bacnet_types::enums::ObjectType;
137
138 #[test]
139 fn request_round_trip() {
140 let req = WritePropertyRequest {
141 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
142 property_identifier: PropertyIdentifier::PRESENT_VALUE,
143 property_array_index: None,
144 property_value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
145 priority: None,
146 };
147 let mut buf = BytesMut::new();
148 req.encode(&mut buf);
149 let decoded = WritePropertyRequest::decode(&buf).unwrap();
150 assert_eq!(req, decoded);
151 }
152
153 #[test]
154 fn request_with_all_fields() {
155 let req = WritePropertyRequest {
156 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
157 property_identifier: PropertyIdentifier::PRESENT_VALUE,
158 property_array_index: Some(5),
159 property_value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
160 priority: Some(8),
161 };
162 let mut buf = BytesMut::new();
163 req.encode(&mut buf);
164 let decoded = WritePropertyRequest::decode(&buf).unwrap();
165 assert_eq!(req, decoded);
166 }
167
168 #[test]
169 fn priority_validation() {
170 let req = WritePropertyRequest {
171 object_identifier: ObjectIdentifier::new(ObjectType::BINARY_OUTPUT, 1).unwrap(),
172 property_identifier: PropertyIdentifier::PRESENT_VALUE,
173 property_array_index: None,
174 property_value: vec![0x91, 0x01], priority: Some(16), };
177 let mut buf = BytesMut::new();
178 req.encode(&mut buf);
179 let decoded = WritePropertyRequest::decode(&buf).unwrap();
180 assert_eq!(decoded.priority, Some(16));
181 }
182
183 #[test]
188 fn test_decode_write_property_empty_input() {
189 assert!(WritePropertyRequest::decode(&[]).is_err());
190 }
191
192 #[test]
193 fn test_decode_write_property_truncated_1_byte() {
194 let req = WritePropertyRequest {
195 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
196 property_identifier: PropertyIdentifier::PRESENT_VALUE,
197 property_array_index: None,
198 property_value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
199 priority: None,
200 };
201 let mut buf = BytesMut::new();
202 req.encode(&mut buf);
203 assert!(WritePropertyRequest::decode(&buf[..1]).is_err());
204 }
205
206 #[test]
207 fn test_decode_write_property_truncated_2_bytes() {
208 let req = WritePropertyRequest {
209 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
210 property_identifier: PropertyIdentifier::PRESENT_VALUE,
211 property_array_index: None,
212 property_value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
213 priority: None,
214 };
215 let mut buf = BytesMut::new();
216 req.encode(&mut buf);
217 assert!(WritePropertyRequest::decode(&buf[..2]).is_err());
218 }
219
220 #[test]
221 fn test_decode_write_property_truncated_3_bytes() {
222 let req = WritePropertyRequest {
223 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
224 property_identifier: PropertyIdentifier::PRESENT_VALUE,
225 property_array_index: None,
226 property_value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
227 priority: None,
228 };
229 let mut buf = BytesMut::new();
230 req.encode(&mut buf);
231 assert!(WritePropertyRequest::decode(&buf[..3]).is_err());
232 }
233
234 #[test]
235 fn test_decode_write_property_truncated_half() {
236 let req = WritePropertyRequest {
237 object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_OUTPUT, 1).unwrap(),
238 property_identifier: PropertyIdentifier::PRESENT_VALUE,
239 property_array_index: None,
240 property_value: vec![0x44, 0x42, 0x90, 0x00, 0x00],
241 priority: Some(8),
242 };
243 let mut buf = BytesMut::new();
244 req.encode(&mut buf);
245 let half = buf.len() / 2;
246 assert!(WritePropertyRequest::decode(&buf[..half]).is_err());
247 }
248
249 #[test]
250 fn test_decode_write_property_invalid_tag() {
251 assert!(WritePropertyRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
252 }
253
254 #[test]
255 fn test_decode_write_property_oversized_length() {
256 assert!(WritePropertyRequest::decode(&[0x05, 0xFF]).is_err());
258 }
259}