1use bacnet_encoding::primitives;
9use bacnet_encoding::tags;
10use bacnet_types::enums::{EnableDisable, ReinitializedState};
11use bacnet_types::error::Error;
12use bacnet_types::primitives::{Date, Time};
13use bytes::BytesMut;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct DeviceCommunicationControlRequest {
22 pub time_duration: Option<u16>,
23 pub enable_disable: EnableDisable,
24 pub password: Option<String>,
25}
26
27impl DeviceCommunicationControlRequest {
28 pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
29 if let Some(dur) = self.time_duration {
31 primitives::encode_ctx_unsigned(buf, 0, dur as u64);
32 }
33 primitives::encode_ctx_enumerated(buf, 1, self.enable_disable.to_raw());
35 if let Some(ref pw) = self.password {
37 primitives::encode_ctx_character_string(buf, 2, pw)?;
38 }
39 Ok(())
40 }
41
42 pub fn decode(data: &[u8]) -> Result<Self, Error> {
43 let mut offset = 0;
44
45 let mut time_duration = None;
47 let (opt_data, new_offset) = tags::decode_optional_context(data, offset, 0)?;
48 if let Some(content) = opt_data {
49 time_duration = Some(primitives::decode_unsigned(content)? as u16);
50 offset = new_offset;
51 }
52
53 let (tag, pos) = tags::decode_tag(data, offset)?;
55 let end = pos + tag.length as usize;
56 if end > data.len() {
57 return Err(Error::decoding(pos, "DCC truncated at enable-disable"));
58 }
59 let enable_disable =
60 EnableDisable::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
61 offset = end;
62
63 let mut password = None;
65 if offset < data.len() {
66 let (opt_data, _new_offset) = tags::decode_optional_context(data, offset, 2)?;
67 if let Some(content) = opt_data {
68 let s = primitives::decode_character_string(content)?;
69 if s.len() > 20 {
70 return Err(Error::Encoding("DCC password exceeds 20 characters".into()));
71 }
72 password = Some(s);
73 }
74 }
75
76 Ok(Self {
77 time_duration,
78 enable_disable,
79 password,
80 })
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct ReinitializeDeviceRequest {
91 pub reinitialized_state: ReinitializedState,
92 pub password: Option<String>,
93}
94
95impl ReinitializeDeviceRequest {
96 pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
97 primitives::encode_ctx_enumerated(buf, 0, self.reinitialized_state.to_raw());
99 if let Some(ref pw) = self.password {
101 primitives::encode_ctx_character_string(buf, 1, pw)?;
102 }
103 Ok(())
104 }
105
106 pub fn decode(data: &[u8]) -> Result<Self, Error> {
107 let mut offset = 0;
108
109 let (tag, pos) = tags::decode_tag(data, offset)?;
111 let end = pos + tag.length as usize;
112 if end > data.len() {
113 return Err(Error::decoding(pos, "Reinitialize truncated at state"));
114 }
115 let reinitialized_state =
116 ReinitializedState::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
117 offset = end;
118
119 let mut password = None;
121 if offset < data.len() {
122 let (opt_data, _new_offset) = tags::decode_optional_context(data, offset, 1)?;
123 if let Some(content) = opt_data {
124 let s = primitives::decode_character_string(content)?;
125 if s.len() > 20 {
126 return Err(Error::decoding(
127 offset,
128 "ReinitializeDevice password exceeds 20 characters",
129 ));
130 }
131 password = Some(s);
132 }
133 }
134
135 Ok(Self {
136 reinitialized_state,
137 password,
138 })
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct TimeSynchronizationRequest {
151 pub date: Date,
152 pub time: Time,
153}
154
155impl TimeSynchronizationRequest {
156 pub fn encode(&self, buf: &mut BytesMut) {
157 primitives::encode_app_date(buf, &self.date);
158 primitives::encode_app_time(buf, &self.time);
159 }
160
161 pub fn decode(data: &[u8]) -> Result<Self, Error> {
162 let mut offset = 0;
163
164 let (tag, pos) = tags::decode_tag(data, offset)?;
165 let end = pos + tag.length as usize;
166 if end > data.len() {
167 return Err(Error::decoding(pos, "TimeSync truncated at date"));
168 }
169 let date = Date::decode(&data[pos..end])?;
170 offset = end;
171
172 let (tag, pos) = tags::decode_tag(data, offset)?;
173 let end = pos + tag.length as usize;
174 if end > data.len() {
175 return Err(Error::decoding(pos, "TimeSync truncated at time"));
176 }
177 let time = Time::decode(&data[pos..end])?;
178
179 Ok(Self { date, time })
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn dcc_round_trip() {
189 let req = DeviceCommunicationControlRequest {
190 time_duration: Some(60),
191 enable_disable: EnableDisable::DISABLE,
192 password: Some("secret".into()),
193 };
194 let mut buf = BytesMut::new();
195 req.encode(&mut buf).unwrap();
196 let decoded = DeviceCommunicationControlRequest::decode(&buf).unwrap();
197 assert_eq!(req, decoded);
198 }
199
200 #[test]
201 fn dcc_no_optionals() {
202 let req = DeviceCommunicationControlRequest {
203 time_duration: None,
204 enable_disable: EnableDisable::ENABLE,
205 password: None,
206 };
207 let mut buf = BytesMut::new();
208 req.encode(&mut buf).unwrap();
209 let decoded = DeviceCommunicationControlRequest::decode(&buf).unwrap();
210 assert_eq!(req, decoded);
211 }
212
213 #[test]
214 fn reinitialize_round_trip() {
215 let req = ReinitializeDeviceRequest {
216 reinitialized_state: ReinitializedState::WARMSTART,
217 password: Some("admin".into()),
218 };
219 let mut buf = BytesMut::new();
220 req.encode(&mut buf).unwrap();
221 let decoded = ReinitializeDeviceRequest::decode(&buf).unwrap();
222 assert_eq!(req, decoded);
223 }
224
225 #[test]
226 fn time_sync_round_trip() {
227 let req = TimeSynchronizationRequest {
228 date: Date {
229 year: 124,
230 month: 6,
231 day: 15,
232 day_of_week: 6,
233 },
234 time: Time {
235 hour: 14,
236 minute: 30,
237 second: 0,
238 hundredths: 0,
239 },
240 };
241 let mut buf = BytesMut::new();
242 req.encode(&mut buf);
243 let decoded = TimeSynchronizationRequest::decode(&buf).unwrap();
244 assert_eq!(req, decoded);
245 }
246
247 #[test]
252 fn test_decode_dcc_empty_input() {
253 assert!(DeviceCommunicationControlRequest::decode(&[]).is_err());
254 }
255
256 #[test]
257 fn test_decode_dcc_truncated_1_byte() {
258 let req = DeviceCommunicationControlRequest {
259 time_duration: Some(60),
260 enable_disable: EnableDisable::DISABLE,
261 password: Some("secret".into()),
262 };
263 let mut buf = BytesMut::new();
264 req.encode(&mut buf).unwrap();
265 assert!(DeviceCommunicationControlRequest::decode(&buf[..1]).is_err());
266 }
267
268 #[test]
269 fn test_decode_dcc_truncated_3_bytes() {
270 let req = DeviceCommunicationControlRequest {
271 time_duration: Some(60),
272 enable_disable: EnableDisable::DISABLE,
273 password: Some("secret".into()),
274 };
275 let mut buf = BytesMut::new();
276 req.encode(&mut buf).unwrap();
277 assert!(DeviceCommunicationControlRequest::decode(&buf[..3]).is_err());
278 }
279
280 #[test]
281 fn test_decode_dcc_invalid_tag() {
282 assert!(DeviceCommunicationControlRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
283 }
284
285 #[test]
286 fn test_decode_reinitialize_empty_input() {
287 assert!(ReinitializeDeviceRequest::decode(&[]).is_err());
288 }
289
290 #[test]
291 fn test_decode_reinitialize_truncated_1_byte() {
292 let req = ReinitializeDeviceRequest {
293 reinitialized_state: ReinitializedState::WARMSTART,
294 password: Some("admin".into()),
295 };
296 let mut buf = BytesMut::new();
297 req.encode(&mut buf).unwrap();
298 assert!(ReinitializeDeviceRequest::decode(&buf[..1]).is_err());
299 }
300
301 #[test]
302 fn test_decode_reinitialize_invalid_tag() {
303 assert!(ReinitializeDeviceRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
304 }
305
306 #[test]
307 fn test_decode_time_sync_empty_input() {
308 assert!(TimeSynchronizationRequest::decode(&[]).is_err());
309 }
310
311 #[test]
312 fn test_decode_time_sync_truncated_1_byte() {
313 let req = TimeSynchronizationRequest {
314 date: Date {
315 year: 124,
316 month: 6,
317 day: 15,
318 day_of_week: 6,
319 },
320 time: Time {
321 hour: 14,
322 minute: 30,
323 second: 0,
324 hundredths: 0,
325 },
326 };
327 let mut buf = BytesMut::new();
328 req.encode(&mut buf);
329 assert!(TimeSynchronizationRequest::decode(&buf[..1]).is_err());
330 }
331
332 #[test]
333 fn test_decode_time_sync_truncated_3_bytes() {
334 let req = TimeSynchronizationRequest {
335 date: Date {
336 year: 124,
337 month: 6,
338 day: 15,
339 day_of_week: 6,
340 },
341 time: Time {
342 hour: 14,
343 minute: 30,
344 second: 0,
345 hundredths: 0,
346 },
347 };
348 let mut buf = BytesMut::new();
349 req.encode(&mut buf);
350 assert!(TimeSynchronizationRequest::decode(&buf[..3]).is_err());
351 }
352
353 #[test]
354 fn test_decode_time_sync_truncated_half() {
355 let req = TimeSynchronizationRequest {
356 date: Date {
357 year: 124,
358 month: 6,
359 day: 15,
360 day_of_week: 6,
361 },
362 time: Time {
363 hour: 14,
364 minute: 30,
365 second: 0,
366 hundredths: 0,
367 },
368 };
369 let mut buf = BytesMut::new();
370 req.encode(&mut buf);
371 let half = buf.len() / 2;
372 assert!(TimeSynchronizationRequest::decode(&buf[..half]).is_err());
373 }
374
375 #[test]
376 fn test_decode_time_sync_invalid_tag() {
377 assert!(TimeSynchronizationRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
378 }
379}