Skip to main content

bacnet_services/
private_transfer.rs

1//! ConfirmedPrivateTransfer / UnconfirmedPrivateTransfer services
2//! per ASHRAE 135-2020 Clauses 15.19 and 16.10.6.
3
4use bacnet_encoding::primitives;
5use bacnet_encoding::tags;
6use bacnet_types::error::Error;
7use bytes::{BufMut, BytesMut};
8
9// ---------------------------------------------------------------------------
10// PrivateTransferRequest
11// ---------------------------------------------------------------------------
12
13/// Request parameters shared by ConfirmedPrivateTransfer and
14/// UnconfirmedPrivateTransfer.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct PrivateTransferRequest {
17    pub vendor_id: u32,
18    pub service_number: u32,
19    /// Vendor-defined payload (raw bytes, opaque to the stack).
20    pub service_parameters: Option<Vec<u8>>,
21}
22
23impl PrivateTransferRequest {
24    pub fn encode(&self, buf: &mut BytesMut) {
25        // [0] vendorID
26        primitives::encode_ctx_unsigned(buf, 0, self.vendor_id as u64);
27        // [1] serviceNumber
28        primitives::encode_ctx_unsigned(buf, 1, self.service_number as u64);
29        // [2] serviceParameters (optional, opening/closing)
30        if let Some(ref params) = self.service_parameters {
31            tags::encode_opening_tag(buf, 2);
32            buf.put_slice(params);
33            tags::encode_closing_tag(buf, 2);
34        }
35    }
36
37    pub fn decode(data: &[u8]) -> Result<Self, Error> {
38        let mut offset = 0;
39
40        // [0] vendorID
41        let (tag, pos) = tags::decode_tag(data, offset)?;
42        let end = pos + tag.length as usize;
43        if end > data.len() {
44            return Err(Error::decoding(
45                pos,
46                "PrivateTransfer truncated at vendorID",
47            ));
48        }
49        let vendor_id = primitives::decode_unsigned(&data[pos..end])? as u32;
50        offset = end;
51
52        // [1] serviceNumber
53        let (tag, pos) = tags::decode_tag(data, offset)?;
54        let end = pos + tag.length as usize;
55        if end > data.len() {
56            return Err(Error::decoding(
57                pos,
58                "PrivateTransfer truncated at serviceNumber",
59            ));
60        }
61        let service_number = primitives::decode_unsigned(&data[pos..end])? as u32;
62        offset = end;
63
64        // [2] serviceParameters (optional, opening/closing)
65        let mut service_parameters = None;
66        if offset < data.len() {
67            let (tag, tag_end) = tags::decode_tag(data, offset)?;
68            if tag.is_opening_tag(2) {
69                let (value_bytes, new_offset) = tags::extract_context_value(data, tag_end, 2)?;
70                service_parameters = Some(value_bytes.to_vec());
71                offset = new_offset;
72                let _ = offset;
73            }
74        }
75
76        Ok(Self {
77            vendor_id,
78            service_number,
79            service_parameters,
80        })
81    }
82}
83
84// ---------------------------------------------------------------------------
85// PrivateTransferAck
86// ---------------------------------------------------------------------------
87
88/// ConfirmedPrivateTransfer-ACK service parameters.
89#[derive(Debug, Clone, PartialEq, Eq)]
90pub struct PrivateTransferAck {
91    pub vendor_id: u32,
92    pub service_number: u32,
93    /// Vendor-defined result (raw bytes, opaque to the stack).
94    pub result_block: Option<Vec<u8>>,
95}
96
97impl PrivateTransferAck {
98    pub fn encode(&self, buf: &mut BytesMut) {
99        // [0] vendorID
100        primitives::encode_ctx_unsigned(buf, 0, self.vendor_id as u64);
101        // [1] serviceNumber
102        primitives::encode_ctx_unsigned(buf, 1, self.service_number as u64);
103        // [2] resultBlock (optional, opening/closing)
104        if let Some(ref block) = self.result_block {
105            tags::encode_opening_tag(buf, 2);
106            buf.put_slice(block);
107            tags::encode_closing_tag(buf, 2);
108        }
109    }
110
111    pub fn decode(data: &[u8]) -> Result<Self, Error> {
112        let mut offset = 0;
113
114        // [0] vendorID
115        let (tag, pos) = tags::decode_tag(data, offset)?;
116        let end = pos + tag.length as usize;
117        if end > data.len() {
118            return Err(Error::decoding(
119                pos,
120                "PrivateTransferAck truncated at vendorID",
121            ));
122        }
123        let vendor_id = primitives::decode_unsigned(&data[pos..end])? as u32;
124        offset = end;
125
126        // [1] serviceNumber
127        let (tag, pos) = tags::decode_tag(data, offset)?;
128        let end = pos + tag.length as usize;
129        if end > data.len() {
130            return Err(Error::decoding(
131                pos,
132                "PrivateTransferAck truncated at serviceNumber",
133            ));
134        }
135        let service_number = primitives::decode_unsigned(&data[pos..end])? as u32;
136        offset = end;
137
138        // [2] resultBlock (optional, opening/closing)
139        let mut result_block = None;
140        if offset < data.len() {
141            let (tag, tag_end) = tags::decode_tag(data, offset)?;
142            if tag.is_opening_tag(2) {
143                let (value_bytes, new_offset) = tags::extract_context_value(data, tag_end, 2)?;
144                result_block = Some(value_bytes.to_vec());
145                offset = new_offset;
146                let _ = offset;
147            }
148        }
149
150        Ok(Self {
151            vendor_id,
152            service_number,
153            result_block,
154        })
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161
162    #[test]
163    fn request_round_trip() {
164        let req = PrivateTransferRequest {
165            vendor_id: 42,
166            service_number: 7,
167            service_parameters: Some(vec![0x21, 0x05]),
168        };
169        let mut buf = BytesMut::new();
170        req.encode(&mut buf);
171        let decoded = PrivateTransferRequest::decode(&buf).unwrap();
172        assert_eq!(req, decoded);
173    }
174
175    #[test]
176    fn request_no_params_round_trip() {
177        let req = PrivateTransferRequest {
178            vendor_id: 999,
179            service_number: 1,
180            service_parameters: None,
181        };
182        let mut buf = BytesMut::new();
183        req.encode(&mut buf);
184        let decoded = PrivateTransferRequest::decode(&buf).unwrap();
185        assert_eq!(req, decoded);
186    }
187
188    #[test]
189    fn ack_round_trip() {
190        let ack = PrivateTransferAck {
191            vendor_id: 42,
192            service_number: 7,
193            result_block: Some(vec![0x44, 0x42, 0x90, 0x00, 0x00]),
194        };
195        let mut buf = BytesMut::new();
196        ack.encode(&mut buf);
197        let decoded = PrivateTransferAck::decode(&buf).unwrap();
198        assert_eq!(ack, decoded);
199    }
200
201    #[test]
202    fn ack_no_result_round_trip() {
203        let ack = PrivateTransferAck {
204            vendor_id: 100,
205            service_number: 3,
206            result_block: None,
207        };
208        let mut buf = BytesMut::new();
209        ack.encode(&mut buf);
210        let decoded = PrivateTransferAck::decode(&buf).unwrap();
211        assert_eq!(ack, decoded);
212    }
213
214    // -----------------------------------------------------------------------
215    // Malformed-input decode error tests
216    // -----------------------------------------------------------------------
217
218    #[test]
219    fn test_decode_request_empty_input() {
220        assert!(PrivateTransferRequest::decode(&[]).is_err());
221    }
222
223    #[test]
224    fn test_decode_request_truncated_1_byte() {
225        let req = PrivateTransferRequest {
226            vendor_id: 42,
227            service_number: 7,
228            service_parameters: Some(vec![0x21, 0x05]),
229        };
230        let mut buf = BytesMut::new();
231        req.encode(&mut buf);
232        assert!(PrivateTransferRequest::decode(&buf[..1]).is_err());
233    }
234
235    #[test]
236    fn test_decode_request_truncated_half() {
237        let req = PrivateTransferRequest {
238            vendor_id: 42,
239            service_number: 7,
240            service_parameters: Some(vec![0x21, 0x05, 0x44, 0x42, 0x90, 0x00, 0x00]),
241        };
242        let mut buf = BytesMut::new();
243        req.encode(&mut buf);
244        let half = buf.len() / 2;
245        assert!(PrivateTransferRequest::decode(&buf[..half]).is_err());
246    }
247
248    #[test]
249    fn test_decode_request_invalid_tag() {
250        assert!(PrivateTransferRequest::decode(&[0xFF, 0xFF, 0xFF]).is_err());
251    }
252
253    #[test]
254    fn test_decode_ack_empty_input() {
255        assert!(PrivateTransferAck::decode(&[]).is_err());
256    }
257
258    #[test]
259    fn test_decode_ack_truncated_1_byte() {
260        let ack = PrivateTransferAck {
261            vendor_id: 42,
262            service_number: 7,
263            result_block: Some(vec![0x44, 0x42]),
264        };
265        let mut buf = BytesMut::new();
266        ack.encode(&mut buf);
267        assert!(PrivateTransferAck::decode(&buf[..1]).is_err());
268    }
269
270    #[test]
271    fn test_decode_ack_invalid_tag() {
272        assert!(PrivateTransferAck::decode(&[0xFF, 0xFF, 0xFF]).is_err());
273    }
274}