Skip to main content

bacnet_services/
virtual_terminal.rs

1//! Virtual Terminal (VT) services per ASHRAE 135-2020 Clauses 16.3–16.5.
2//!
3//! Legacy services needed for full spec coverage. All fields use APPLICATION
4//! tags (not context-specific) unless noted.
5
6use bacnet_encoding::primitives;
7use bacnet_encoding::tags;
8use bacnet_types::error::Error;
9use bytes::BytesMut;
10
11use crate::common::MAX_DECODED_ITEMS;
12
13// ---------------------------------------------------------------------------
14// VTOpenRequest / VTOpenAck
15// ---------------------------------------------------------------------------
16
17/// VT-Open-Request service parameters.
18///
19/// `vt_class` is an APPLICATION-tagged ENUMERATED.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct VTOpenRequest {
22    pub vt_class: u32,
23}
24
25impl VTOpenRequest {
26    pub fn encode(&self, buf: &mut BytesMut) {
27        primitives::encode_app_enumerated(buf, self.vt_class);
28    }
29
30    pub fn decode(data: &[u8]) -> Result<Self, Error> {
31        let (tag, pos) = tags::decode_tag(data, 0)?;
32        let end = pos + tag.length as usize;
33        if end > data.len() {
34            return Err(Error::decoding(pos, "VTOpen truncated at vt-class"));
35        }
36        let vt_class = primitives::decode_unsigned(&data[pos..end])? as u32;
37        Ok(Self { vt_class })
38    }
39}
40
41/// VT-Open-Ack service parameters.
42///
43/// `remote_vt_session_identifier` is an APPLICATION-tagged Unsigned8.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct VTOpenAck {
46    pub remote_vt_session_identifier: u8,
47}
48
49impl VTOpenAck {
50    pub fn encode(&self, buf: &mut BytesMut) {
51        primitives::encode_app_unsigned(buf, self.remote_vt_session_identifier as u64);
52    }
53
54    pub fn decode(data: &[u8]) -> Result<Self, Error> {
55        let (tag, pos) = tags::decode_tag(data, 0)?;
56        let end = pos + tag.length as usize;
57        if end > data.len() {
58            return Err(Error::decoding(
59                pos,
60                "VTOpenAck truncated at session-identifier",
61            ));
62        }
63        let id = primitives::decode_unsigned(&data[pos..end])? as u8;
64        Ok(Self {
65            remote_vt_session_identifier: id,
66        })
67    }
68}
69
70// ---------------------------------------------------------------------------
71// VTCloseRequest
72// ---------------------------------------------------------------------------
73
74/// VT-Close-Request service parameters.
75///
76/// Contains a SEQUENCE OF Unsigned8 (APPLICATION tagged).
77#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct VTCloseRequest {
79    pub list_of_remote_vt_session_identifiers: Vec<u8>,
80}
81
82impl VTCloseRequest {
83    pub fn encode(&self, buf: &mut BytesMut) {
84        for &id in &self.list_of_remote_vt_session_identifiers {
85            primitives::encode_app_unsigned(buf, id as u64);
86        }
87    }
88
89    pub fn decode(data: &[u8]) -> Result<Self, Error> {
90        let mut offset = 0;
91        let mut ids = Vec::new();
92        while offset < data.len() {
93            if ids.len() >= MAX_DECODED_ITEMS {
94                return Err(Error::decoding(offset, "VTClose too many session IDs"));
95            }
96            let (tag, pos) = tags::decode_tag(data, offset)?;
97            let end = pos + tag.length as usize;
98            if end > data.len() {
99                return Err(Error::decoding(
100                    pos,
101                    "VTClose truncated at session-identifier",
102                ));
103            }
104            ids.push(primitives::decode_unsigned(&data[pos..end])? as u8);
105            offset = end;
106        }
107        Ok(Self {
108            list_of_remote_vt_session_identifiers: ids,
109        })
110    }
111}
112
113// ---------------------------------------------------------------------------
114// VTDataRequest / VTDataAck
115// ---------------------------------------------------------------------------
116
117/// VT-Data-Request service parameters.
118///
119/// All fields are APPLICATION tagged.
120#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct VTDataRequest {
122    pub vt_session_identifier: u8,
123    pub vt_new_data: Vec<u8>,
124    pub vt_data_flag: bool,
125}
126
127impl VTDataRequest {
128    pub fn encode(&self, buf: &mut BytesMut) {
129        primitives::encode_app_unsigned(buf, self.vt_session_identifier as u64);
130        primitives::encode_app_octet_string(buf, &self.vt_new_data);
131        primitives::encode_app_boolean(buf, self.vt_data_flag);
132    }
133
134    pub fn decode(data: &[u8]) -> Result<Self, Error> {
135        let mut offset = 0;
136
137        let (tag, pos) = tags::decode_tag(data, offset)?;
138        let end = pos + tag.length as usize;
139        if end > data.len() {
140            return Err(Error::decoding(
141                pos,
142                "VTData truncated at session-identifier",
143            ));
144        }
145        let vt_session_identifier = primitives::decode_unsigned(&data[pos..end])? as u8;
146        offset = end;
147
148        let (tag, pos) = tags::decode_tag(data, offset)?;
149        let end = pos + tag.length as usize;
150        if end > data.len() {
151            return Err(Error::decoding(pos, "VTData truncated at new-data"));
152        }
153        let vt_new_data = data[pos..end].to_vec();
154        offset = end;
155
156        let (tag, pos) = tags::decode_tag(data, offset)?;
157        let vt_data_flag = tag.length != 0;
158        let _ = pos;
159
160        Ok(Self {
161            vt_session_identifier,
162            vt_new_data,
163            vt_data_flag,
164        })
165    }
166}
167
168/// VT-Data-Ack service parameters.
169#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct VTDataAck {
171    /// [0] allNewDataAccepted OPTIONAL
172    pub all_new_data_accepted: Option<bool>,
173    /// [1] acceptedOctetCount OPTIONAL
174    pub accepted_octet_count: Option<u32>,
175}
176
177impl VTDataAck {
178    pub fn encode(&self, buf: &mut BytesMut) {
179        if let Some(v) = self.all_new_data_accepted {
180            primitives::encode_ctx_boolean(buf, 0, v);
181        }
182        if let Some(v) = self.accepted_octet_count {
183            primitives::encode_ctx_unsigned(buf, 1, v as u64);
184        }
185    }
186
187    pub fn decode(data: &[u8]) -> Result<Self, Error> {
188        let mut offset = 0;
189
190        // [0] allNewDataAccepted OPTIONAL
191        let mut all_new_data_accepted = None;
192        if offset < data.len() {
193            let (opt, new_off) = tags::decode_optional_context(data, offset, 0)?;
194            if let Some(content) = opt {
195                all_new_data_accepted = Some(!content.is_empty() && content[0] != 0);
196                offset = new_off;
197            }
198        }
199
200        // [1] acceptedOctetCount OPTIONAL
201        let mut accepted_octet_count = None;
202        if offset < data.len() {
203            let (opt, new_off) = tags::decode_optional_context(data, offset, 1)?;
204            if let Some(content) = opt {
205                accepted_octet_count = Some(primitives::decode_unsigned(content)? as u32);
206                offset = new_off;
207            }
208        }
209        let _ = offset;
210
211        Ok(Self {
212            all_new_data_accepted,
213            accepted_octet_count,
214        })
215    }
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    #[test]
223    fn vt_open_round_trip() {
224        let req = VTOpenRequest { vt_class: 1 };
225        let mut buf = BytesMut::new();
226        req.encode(&mut buf);
227        let decoded = VTOpenRequest::decode(&buf).unwrap();
228        assert_eq!(req, decoded);
229    }
230
231    #[test]
232    fn vt_open_ack_round_trip() {
233        let ack = VTOpenAck {
234            remote_vt_session_identifier: 42,
235        };
236        let mut buf = BytesMut::new();
237        ack.encode(&mut buf);
238        let decoded = VTOpenAck::decode(&buf).unwrap();
239        assert_eq!(ack, decoded);
240    }
241
242    #[test]
243    fn vt_close_round_trip() {
244        let req = VTCloseRequest {
245            list_of_remote_vt_session_identifiers: vec![1, 2, 3],
246        };
247        let mut buf = BytesMut::new();
248        req.encode(&mut buf);
249        let decoded = VTCloseRequest::decode(&buf).unwrap();
250        assert_eq!(req, decoded);
251    }
252
253    #[test]
254    fn vt_close_empty() {
255        let req = VTCloseRequest {
256            list_of_remote_vt_session_identifiers: vec![],
257        };
258        let mut buf = BytesMut::new();
259        req.encode(&mut buf);
260        assert!(buf.is_empty());
261        let decoded = VTCloseRequest::decode(&buf).unwrap();
262        assert_eq!(req, decoded);
263    }
264
265    #[test]
266    fn vt_data_round_trip() {
267        let req = VTDataRequest {
268            vt_session_identifier: 1,
269            vt_new_data: vec![0x48, 0x65, 0x6C, 0x6C, 0x6F], // "Hello"
270            vt_data_flag: true,
271        };
272        let mut buf = BytesMut::new();
273        req.encode(&mut buf);
274        let decoded = VTDataRequest::decode(&buf).unwrap();
275        assert_eq!(req, decoded);
276    }
277
278    #[test]
279    fn vt_data_flag_false() {
280        let req = VTDataRequest {
281            vt_session_identifier: 5,
282            vt_new_data: vec![0x01],
283            vt_data_flag: false,
284        };
285        let mut buf = BytesMut::new();
286        req.encode(&mut buf);
287        let decoded = VTDataRequest::decode(&buf).unwrap();
288        assert_eq!(req, decoded);
289    }
290
291    #[test]
292    fn vt_data_ack_round_trip() {
293        let ack = VTDataAck {
294            all_new_data_accepted: Some(true),
295            accepted_octet_count: Some(100),
296        };
297        let mut buf = BytesMut::new();
298        ack.encode(&mut buf);
299        let decoded = VTDataAck::decode(&buf).unwrap();
300        assert_eq!(ack, decoded);
301    }
302
303    #[test]
304    fn vt_data_ack_empty() {
305        let ack = VTDataAck {
306            all_new_data_accepted: None,
307            accepted_octet_count: None,
308        };
309        let mut buf = BytesMut::new();
310        ack.encode(&mut buf);
311        assert!(buf.is_empty());
312        let decoded = VTDataAck::decode(&buf).unwrap();
313        assert_eq!(ack, decoded);
314    }
315
316    #[test]
317    fn vt_open_empty_input() {
318        assert!(VTOpenRequest::decode(&[]).is_err());
319    }
320
321    #[test]
322    fn vt_data_empty_input() {
323        assert!(VTDataRequest::decode(&[]).is_err());
324    }
325}