Skip to main content

bacnet_services/private_transfer/
error.rs

1//! ConfirmedPrivateTransfer-Error (Clause 21): `[0]` Error, `[1]` vendor
2//! identifier, `[2]` service number and optional `[3]` error parameters.
3
4#[cfg(not(feature = "std"))]
5use alloc::vec::Vec;
6
7use crate::common::error_type::{
8    decode_error_pdu, decode_error_type, encode_error_type, error_pdu,
9};
10use bacnet_encoding::apdu::ErrorPdu;
11use bacnet_encoding::constructed::tagged::{
12    decode_ctx_constructed, decode_ctx_unsigned, expect_end,
13};
14use bacnet_encoding::{primitives, tags};
15use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode};
16use bacnet_types::error::{Error, ErrorDetail};
17use bytes::{BufMut, BytesMut};
18
19const WHAT: &str = "ConfirmedPrivateTransfer-Error";
20
21/// The Result(-) body of ConfirmedPrivateTransfer (Clause 16.2.1.3).
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct PrivateTransferError {
24    /// Error class from the inner BACnetError.
25    pub error_class: ErrorClass,
26    /// Error code from the inner BACnetError.
27    pub error_code: ErrorCode,
28    /// Vendor identifier of the private service the error answers.
29    pub vendor_id: u32,
30    /// Vendor-defined service number the error answers.
31    pub service_number: u32,
32    /// Vendor-defined error parameters (raw bytes inside the `[3]` frame,
33    /// opaque to the stack); `None` when absent.
34    pub error_parameters: Option<Vec<u8>>,
35}
36
37impl PrivateTransferError {
38    /// Encode the body: the error, vendor, service and any parameters.
39    pub fn encode(&self, buf: &mut BytesMut) {
40        encode_error_type(buf, self.error_class, self.error_code);
41        primitives::encode_ctx_unsigned(buf, 1, u64::from(self.vendor_id));
42        primitives::encode_ctx_unsigned(buf, 2, u64::from(self.service_number));
43        if let Some(parameters) = &self.error_parameters {
44            tags::encode_opening_tag(buf, 3);
45            buf.put_slice(parameters);
46            tags::encode_closing_tag(buf, 3);
47        }
48    }
49
50    /// Decode one complete body with no trailing content.
51    pub fn decode(data: &[u8]) -> Result<Self, Error> {
52        let ((error_class, error_code), offset) = decode_error_type(data, WHAT)?;
53        let (vendor_id, offset) = decode_ctx_unsigned::<u32>(
54            data,
55            offset,
56            1,
57            "ConfirmedPrivateTransfer-Error vendor-id",
58        )?;
59        let (service_number, mut offset) = decode_ctx_unsigned::<u32>(
60            data,
61            offset,
62            2,
63            "ConfirmedPrivateTransfer-Error service-number",
64        )?;
65        let mut error_parameters = None;
66        if offset < data.len() {
67            let (parameters, end) = decode_ctx_constructed(data, offset, 3, WHAT)?;
68            error_parameters = Some(parameters.to_vec());
69            offset = end;
70        }
71        expect_end(data, offset, offset, WHAT)?;
72        Ok(Self {
73            error_class,
74            error_code,
75            vendor_id,
76            service_number,
77            error_parameters,
78        })
79    }
80
81    /// Build the ConfirmedPrivateTransfer Error PDU carrying this body.
82    pub fn to_error_pdu(&self, invoke_id: u8) -> ErrorPdu {
83        error_pdu(
84            invoke_id,
85            ConfirmedServiceChoice::CONFIRMED_PRIVATE_TRANSFER,
86            (self.error_class, self.error_code),
87            |body| self.encode(body),
88        )
89    }
90}
91
92impl TryFrom<&ErrorPdu> for PrivateTransferError {
93    type Error = Error;
94
95    /// Decode and verify the body of a ConfirmedPrivateTransfer Error PDU. A
96    /// plain class/code error, which older devices send, is refused.
97    fn try_from(pdu: &ErrorPdu) -> Result<Self, Self::Error> {
98        decode_error_pdu(
99            pdu,
100            &[ConfirmedServiceChoice::CONFIRMED_PRIVATE_TRANSFER],
101            WHAT,
102            Self::decode,
103            |error| (error.error_class, error.error_code),
104        )
105    }
106}
107
108impl From<PrivateTransferError> for Error {
109    fn from(error: PrivateTransferError) -> Self {
110        Error::with_detail(
111            error.error_class,
112            error.error_code,
113            Some(ErrorDetail::PrivateTransfer {
114                vendor_id: error.vendor_id,
115                service_number: error.service_number,
116                error_parameters: error.error_parameters,
117            }),
118        )
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use bytes::Bytes;
126
127    fn sample(error_parameters: Option<Vec<u8>>) -> PrivateTransferError {
128        PrivateTransferError {
129            error_class: ErrorClass::SERVICES,
130            error_code: ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
131            vendor_id: 555,
132            service_number: 7,
133            error_parameters,
134        }
135    }
136
137    fn encoded(error: &PrivateTransferError) -> Vec<u8> {
138        let mut body = BytesMut::new();
139        error.encode(&mut body);
140        body.to_vec()
141    }
142
143    #[test]
144    fn private_transfer_error_round_trips_with_exact_shape() {
145        // [0] { 5, 45 }, [1] vendor 555 in two octets, [2] service 7, then
146        // [3] around the parameters when present (empty is kept as empty).
147        let head = [0x0E, 0x91, 5, 0x91, 45, 0x0F, 0x1A, 0x02, 0x2B, 0x29, 7];
148        for (parameters, tail) in [
149            (None, &[][..]),
150            (Some(vec![0x21, 1]), &[0x3E, 0x21, 1, 0x3F]),
151            (Some(Vec::new()), &[0x3E, 0x3F]),
152            (
153                Some(vec![0x3E, 0x21, 1, 0x3F]),
154                &[0x3E, 0x3E, 0x21, 1, 0x3F, 0x3F],
155            ),
156        ] {
157            let error = sample(parameters);
158            let wire = [&head[..], tail].concat();
159            assert_eq!(encoded(&error), wire);
160            assert_eq!(PrivateTransferError::decode(&wire).unwrap(), error);
161            let pdu = error.to_error_pdu(3);
162            assert_eq!(PrivateTransferError::try_from(&pdu).unwrap(), error);
163        }
164    }
165
166    #[test]
167    fn private_transfer_error_projects_vendor_service_and_parameters() {
168        assert!(matches!(
169            Error::from(sample(Some(vec![0x21, 1]))),
170            Error::Structured { class: ErrorClass::SERVICES, code: ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED, detail }
171                if *detail == ErrorDetail::PrivateTransfer {
172                    vendor_id: 555,
173                    service_number: 7,
174                    error_parameters: Some(vec![0x21, 1]),
175                }
176        ));
177    }
178
179    #[test]
180    fn private_transfer_error_refuses_malformed_plain_and_mismatched() {
181        let body = encoded(&sample(Some(vec![0x21, 1])));
182        for malformed in [
183            body[..6].to_vec(),
184            body[..9].to_vec(),
185            body[..body.len() - 1].to_vec(),
186            [&body[..], &[0x00][..]].concat(),
187            [&body[..6], &[0x29, 7, 0x1A, 0x02, 0x2B][..]].concat(),
188            [&body[..11], &[0x39, 1][..]].concat(),
189        ] {
190            assert!(
191                PrivateTransferError::decode(&malformed).is_err(),
192                "{malformed:02x?}"
193            );
194        }
195
196        let mut pdu = sample(None).to_error_pdu(1);
197        pdu.error_class = ErrorClass::DEVICE;
198        assert!(PrivateTransferError::try_from(&pdu).is_err());
199        let mut pdu = sample(None).to_error_pdu(1);
200        pdu.error_data = Bytes::new();
201        assert!(PrivateTransferError::try_from(&pdu).is_err());
202        let mut pdu = sample(None).to_error_pdu(1);
203        pdu.service_choice = ConfirmedServiceChoice::CONFIRMED_TEXT_MESSAGE;
204        assert!(PrivateTransferError::try_from(&pdu).is_err());
205    }
206}