Skip to main content

bacnet_services/object_mgmt/
error.rs

1//! CreateObject-Error (Clause 21), the error body of CreateObject. It has the
2//! shape of ChangeList-Error.
3
4use crate::common::error_type::{
5    decode_element_error, decode_error_pdu, encode_element_error, error_pdu,
6};
7use bacnet_encoding::apdu::ErrorPdu;
8use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode};
9use bacnet_types::error::{Error, ErrorDetail};
10use bytes::BytesMut;
11
12/// The Result(-) body of CreateObject (Clause 15.3.1.3): the error, and the
13/// 1-based position of the initial value in the request's List of Initial
14/// Values that could not be applied, or 0 when the request failed for a
15/// reason other than one of its initial values.
16#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct CreateObjectError {
18    /// Error class from the inner BACnetError.
19    pub error_class: ErrorClass,
20    /// Error code from the inner BACnetError.
21    pub error_code: ErrorCode,
22    /// Position of the failed initial value, starting at 1, or 0.
23    pub first_failed_element_number: u32,
24}
25
26impl CreateObjectError {
27    /// Encode `[0] Error` followed by `[1]` first-failed-element-number.
28    pub fn encode(&self, buf: &mut BytesMut) {
29        encode_element_error(
30            buf,
31            self.error_class,
32            self.error_code,
33            self.first_failed_element_number,
34        );
35    }
36
37    /// Decode one complete body with no trailing content.
38    pub fn decode(data: &[u8]) -> Result<Self, Error> {
39        let (error_class, error_code, first_failed_element_number) = decode_element_error(
40            data,
41            "CreateObject-Error",
42            "CreateObject-Error first-failed-element-number",
43        )?;
44        Ok(Self {
45            error_class,
46            error_code,
47            first_failed_element_number,
48        })
49    }
50
51    /// Build the CreateObject Error PDU carrying this body.
52    pub fn to_error_pdu(&self, invoke_id: u8) -> ErrorPdu {
53        error_pdu(
54            invoke_id,
55            ConfirmedServiceChoice::CREATE_OBJECT,
56            (self.error_class, self.error_code),
57            |body| self.encode(body),
58        )
59    }
60}
61
62impl TryFrom<&ErrorPdu> for CreateObjectError {
63    type Error = Error;
64
65    /// Decode and verify the CreateObject-Error of a CreateObject Error PDU.
66    /// A plain class/code error, which older devices send, is refused.
67    fn try_from(pdu: &ErrorPdu) -> Result<Self, Self::Error> {
68        decode_error_pdu(
69            pdu,
70            &[ConfirmedServiceChoice::CREATE_OBJECT],
71            "CreateObject-Error",
72            Self::decode,
73            |error| (error.error_class, error.error_code),
74        )
75    }
76}
77
78impl From<CreateObjectError> for Error {
79    fn from(error: CreateObjectError) -> Self {
80        Error::with_detail(
81            error.error_class,
82            error.error_code,
83            Some(ErrorDetail::FirstFailedElementNumber(
84                error.first_failed_element_number,
85            )),
86        )
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use bytes::Bytes;
94
95    fn sample(first_failed_element_number: u32) -> CreateObjectError {
96        CreateObjectError {
97            error_class: ErrorClass::PROPERTY,
98            error_code: ErrorCode::WRITE_ACCESS_DENIED,
99            first_failed_element_number,
100        }
101    }
102
103    #[test]
104    fn create_object_error_round_trips_with_exact_shape() {
105        // [0] around Enumerated 2 and 40, then [1] holding the element number
106        // in its shortest form.
107        for (number, tail) in [
108            (0, &[0x19, 0][..]),
109            (3, &[0x19, 3]),
110            (300, &[0x1A, 1, 44]),
111            (u32::MAX, &[0x1C, 0xFF, 0xFF, 0xFF, 0xFF]),
112        ] {
113            let error = sample(number);
114            let mut body = BytesMut::new();
115            error.encode(&mut body);
116            assert_eq!(&body[..6], &[0x0E, 0x91, 2, 0x91, 40, 0x0F]);
117            assert_eq!(&body[6..], tail);
118            assert_eq!(CreateObjectError::decode(&body).unwrap(), error);
119        }
120    }
121
122    #[test]
123    fn create_object_error_pdu_conversion_and_projection() {
124        let pdu = sample(2).to_error_pdu(9);
125        assert_eq!(pdu.invoke_id, 9);
126        assert_eq!(pdu.service_choice, ConfirmedServiceChoice::CREATE_OBJECT);
127        assert_eq!(CreateObjectError::try_from(&pdu).unwrap(), sample(2));
128        assert!(matches!(
129            Error::from(sample(2)),
130            Error::Structured { class: ErrorClass::PROPERTY, code: ErrorCode::WRITE_ACCESS_DENIED, detail }
131                if *detail == ErrorDetail::FirstFailedElementNumber(2)
132        ));
133    }
134
135    #[test]
136    fn create_object_error_refuses_malformed_plain_and_mismatched() {
137        let mut body = BytesMut::new();
138        sample(2).encode(&mut body);
139        for malformed in [
140            body[..6].to_vec(),
141            [&body[..6], &[0x29, 2][..]].concat(),
142            [&body[..], &[0x19, 2][..]].concat(),
143            [&body[..6], &[0x1D, 5, 1, 0, 0, 0, 0][..]].concat(),
144        ] {
145            assert!(
146                CreateObjectError::decode(&malformed).is_err(),
147                "{malformed:02x?}"
148            );
149        }
150
151        let mut pdu = sample(1).to_error_pdu(1);
152        pdu.error_code = ErrorCode::INVALID_DATA_TYPE;
153        assert!(CreateObjectError::try_from(&pdu).is_err());
154        let mut pdu = sample(1).to_error_pdu(1);
155        pdu.error_data = Bytes::new();
156        assert!(CreateObjectError::try_from(&pdu).is_err());
157        let mut pdu = sample(1).to_error_pdu(1);
158        pdu.service_choice = ConfirmedServiceChoice::ADD_LIST_ELEMENT;
159        assert!(CreateObjectError::try_from(&pdu).is_err());
160    }
161}