Skip to main content

bacnet_services/
device_mgmt.rs

1//! Device management services per ASHRAE 135-2020 Clauses 15-16.
2//!
3//! - DeviceCommunicationControl (Clause 15.4)
4//! - ReinitializeDevice (Clause 15.4)
5//! - TimeSynchronization (Clause 16.10)
6//! - UTCTimeSynchronization (Clause 16.10)
7
8use 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// ---------------------------------------------------------------------------
16// DeviceCommunicationControlRequest (Clause 15.4.1)
17// ---------------------------------------------------------------------------
18
19/// DeviceCommunicationControl-Request service parameters.
20#[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        // [0] time-duration (optional)
30        if let Some(dur) = self.time_duration {
31            primitives::encode_ctx_unsigned(buf, 0, dur as u64);
32        }
33        // [1] enable-disable
34        primitives::encode_ctx_enumerated(buf, 1, self.enable_disable.to_raw());
35        // [2] password (optional)
36        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        // [0] time-duration (optional)
46        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        // [1] enable-disable
54        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        // [2] password (optional)
64        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                password = Some(s);
70            }
71        }
72
73        Ok(Self {
74            time_duration,
75            enable_disable,
76            password,
77        })
78    }
79}
80
81// ---------------------------------------------------------------------------
82// ReinitializeDeviceRequest (Clause 15.4.2)
83// ---------------------------------------------------------------------------
84
85/// ReinitializeDevice-Request service parameters.
86#[derive(Debug, Clone, PartialEq, Eq)]
87pub struct ReinitializeDeviceRequest {
88    pub reinitialized_state: ReinitializedState,
89    pub password: Option<String>,
90}
91
92impl ReinitializeDeviceRequest {
93    pub fn encode(&self, buf: &mut BytesMut) -> Result<(), Error> {
94        // [0] reinitialized-state
95        primitives::encode_ctx_enumerated(buf, 0, self.reinitialized_state.to_raw());
96        // [1] password (optional)
97        if let Some(ref pw) = self.password {
98            primitives::encode_ctx_character_string(buf, 1, pw)?;
99        }
100        Ok(())
101    }
102
103    pub fn decode(data: &[u8]) -> Result<Self, Error> {
104        let mut offset = 0;
105
106        // [0] reinitialized-state
107        let (tag, pos) = tags::decode_tag(data, offset)?;
108        let end = pos + tag.length as usize;
109        if end > data.len() {
110            return Err(Error::decoding(pos, "Reinitialize truncated at state"));
111        }
112        let reinitialized_state =
113            ReinitializedState::from_raw(primitives::decode_unsigned(&data[pos..end])? as u32);
114        offset = end;
115
116        // [1] password (optional)
117        let mut password = None;
118        if offset < data.len() {
119            let (opt_data, _new_offset) = tags::decode_optional_context(data, offset, 1)?;
120            if let Some(content) = opt_data {
121                let s = primitives::decode_character_string(content)?;
122                password = Some(s);
123            }
124        }
125
126        Ok(Self {
127            reinitialized_state,
128            password,
129        })
130    }
131}
132
133// ---------------------------------------------------------------------------
134// TimeSynchronizationRequest (Clause 16.10.5)
135// ---------------------------------------------------------------------------
136
137/// TimeSynchronization-Request service parameters (APPLICATION-tagged).
138///
139/// Used for both TimeSynchronization and UTCTimeSynchronization.
140#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct TimeSynchronizationRequest {
142    pub date: Date,
143    pub time: Time,
144}
145
146impl TimeSynchronizationRequest {
147    pub fn encode(&self, buf: &mut BytesMut) {
148        primitives::encode_app_date(buf, &self.date);
149        primitives::encode_app_time(buf, &self.time);
150    }
151
152    pub fn decode(data: &[u8]) -> Result<Self, Error> {
153        let mut offset = 0;
154
155        // App date (tag 10)
156        let (tag, pos) = tags::decode_tag(data, offset)?;
157        let end = pos + tag.length as usize;
158        if end > data.len() {
159            return Err(Error::decoding(pos, "TimeSync truncated at date"));
160        }
161        let date = Date::decode(&data[pos..end])?;
162        offset = end;
163
164        // App time (tag 11)
165        let (tag, pos) = tags::decode_tag(data, offset)?;
166        let end = pos + tag.length as usize;
167        if end > data.len() {
168            return Err(Error::decoding(pos, "TimeSync truncated at time"));
169        }
170        let time = Time::decode(&data[pos..end])?;
171
172        Ok(Self { date, time })
173    }
174}
175
176#[cfg(test)]
177mod tests {
178    use super::*;
179
180    #[test]
181    fn dcc_round_trip() {
182        let req = DeviceCommunicationControlRequest {
183            time_duration: Some(60),
184            enable_disable: EnableDisable::DISABLE,
185            password: Some("secret".into()),
186        };
187        let mut buf = BytesMut::new();
188        req.encode(&mut buf).unwrap();
189        let decoded = DeviceCommunicationControlRequest::decode(&buf).unwrap();
190        assert_eq!(req, decoded);
191    }
192
193    #[test]
194    fn dcc_no_optionals() {
195        let req = DeviceCommunicationControlRequest {
196            time_duration: None,
197            enable_disable: EnableDisable::ENABLE,
198            password: None,
199        };
200        let mut buf = BytesMut::new();
201        req.encode(&mut buf).unwrap();
202        let decoded = DeviceCommunicationControlRequest::decode(&buf).unwrap();
203        assert_eq!(req, decoded);
204    }
205
206    #[test]
207    fn reinitialize_round_trip() {
208        let req = ReinitializeDeviceRequest {
209            reinitialized_state: ReinitializedState::WARMSTART,
210            password: Some("admin".into()),
211        };
212        let mut buf = BytesMut::new();
213        req.encode(&mut buf).unwrap();
214        let decoded = ReinitializeDeviceRequest::decode(&buf).unwrap();
215        assert_eq!(req, decoded);
216    }
217
218    #[test]
219    fn time_sync_round_trip() {
220        let req = TimeSynchronizationRequest {
221            date: Date {
222                year: 124,
223                month: 6,
224                day: 15,
225                day_of_week: 6,
226            },
227            time: Time {
228                hour: 14,
229                minute: 30,
230                second: 0,
231                hundredths: 0,
232            },
233        };
234        let mut buf = BytesMut::new();
235        req.encode(&mut buf);
236        let decoded = TimeSynchronizationRequest::decode(&buf).unwrap();
237        assert_eq!(req, decoded);
238    }
239
240    // -----------------------------------------------------------------------
241    // Malformed-input decode error tests
242    // -----------------------------------------------------------------------
243
244    #[test]
245    fn test_decode_dcc_empty_input() {
246        assert!(DeviceCommunicationControlRequest::decode(&[]).is_err());
247    }
248
249    #[test]
250    fn test_decode_dcc_truncated_1_byte() {
251        let req = DeviceCommunicationControlRequest {
252            time_duration: Some(60),
253            enable_disable: EnableDisable::DISABLE,
254            password: Some("secret".into()),
255        };
256        let mut buf = BytesMut::new();
257        req.encode(&mut buf).unwrap();
258        assert!(DeviceCommunicationControlRequest::decode(&buf[..1]).is_err());
259    }
260
261    #[test]
262    fn test_decode_dcc_truncated_3_bytes() {
263        let req = DeviceCommunicationControlRequest {
264            time_duration: Some(60),
265            enable_disable: EnableDisable::DISABLE,
266            password: Some("secret".into()),
267        };
268        let mut buf = BytesMut::new();
269        req.encode(&mut buf).unwrap();
270        assert!(DeviceCommunicationControlRequest::decode(&buf[..3]).is_err());
271    }
272
273    #[test]
274    fn test_decode_dcc_invalid_tag() {
275        assert!(DeviceCommunicationControlRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
276    }
277
278    #[test]
279    fn test_decode_reinitialize_empty_input() {
280        assert!(ReinitializeDeviceRequest::decode(&[]).is_err());
281    }
282
283    #[test]
284    fn test_decode_reinitialize_truncated_1_byte() {
285        let req = ReinitializeDeviceRequest {
286            reinitialized_state: ReinitializedState::WARMSTART,
287            password: Some("admin".into()),
288        };
289        let mut buf = BytesMut::new();
290        req.encode(&mut buf).unwrap();
291        assert!(ReinitializeDeviceRequest::decode(&buf[..1]).is_err());
292    }
293
294    #[test]
295    fn test_decode_reinitialize_invalid_tag() {
296        assert!(ReinitializeDeviceRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
297    }
298
299    #[test]
300    fn test_decode_time_sync_empty_input() {
301        assert!(TimeSynchronizationRequest::decode(&[]).is_err());
302    }
303
304    #[test]
305    fn test_decode_time_sync_truncated_1_byte() {
306        let req = TimeSynchronizationRequest {
307            date: Date {
308                year: 124,
309                month: 6,
310                day: 15,
311                day_of_week: 6,
312            },
313            time: Time {
314                hour: 14,
315                minute: 30,
316                second: 0,
317                hundredths: 0,
318            },
319        };
320        let mut buf = BytesMut::new();
321        req.encode(&mut buf);
322        assert!(TimeSynchronizationRequest::decode(&buf[..1]).is_err());
323    }
324
325    #[test]
326    fn test_decode_time_sync_truncated_3_bytes() {
327        let req = TimeSynchronizationRequest {
328            date: Date {
329                year: 124,
330                month: 6,
331                day: 15,
332                day_of_week: 6,
333            },
334            time: Time {
335                hour: 14,
336                minute: 30,
337                second: 0,
338                hundredths: 0,
339            },
340        };
341        let mut buf = BytesMut::new();
342        req.encode(&mut buf);
343        assert!(TimeSynchronizationRequest::decode(&buf[..3]).is_err());
344    }
345
346    #[test]
347    fn test_decode_time_sync_truncated_half() {
348        let req = TimeSynchronizationRequest {
349            date: Date {
350                year: 124,
351                month: 6,
352                day: 15,
353                day_of_week: 6,
354            },
355            time: Time {
356                hour: 14,
357                minute: 30,
358                second: 0,
359                hundredths: 0,
360            },
361        };
362        let mut buf = BytesMut::new();
363        req.encode(&mut buf);
364        let half = buf.len() / 2;
365        assert!(TimeSynchronizationRequest::decode(&buf[..half]).is_err());
366    }
367
368    #[test]
369    fn test_decode_time_sync_invalid_tag() {
370        assert!(TimeSynchronizationRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
371    }
372}