Skip to main content

bacnet_services/
structured_error.rs

1//! The Clause 21 error productions that replace the plain class/code pair,
2//! read by the service the Error PDU answers:
3//!
4//! | Service | Body |
5//! |---|---|
6//! | AddListElement, RemoveListElement | [`ChangeListError`] |
7//! | CreateObject | [`CreateObjectError`] |
8//! | WritePropertyMultiple | [`WritePropertyMultipleError`] |
9//! | SubscribeCOVPropertyMultiple | [`SubscribeCOVPropertyMultipleError`] |
10//! | ConfirmedPrivateTransfer | [`PrivateTransferError`] |
11//! | VT-Close | [`VTCloseError`] |
12
13use crate::cov_multiple::SubscribeCOVPropertyMultipleError;
14use crate::list_manipulation::ChangeListError;
15use crate::object_mgmt::CreateObjectError;
16use crate::private_transfer::PrivateTransferError;
17use crate::virtual_terminal::VTCloseError;
18use crate::wpm::WritePropertyMultipleError;
19use bacnet_encoding::apdu::ErrorPdu;
20use bacnet_types::enums::ConfirmedServiceChoice;
21use bacnet_types::error::{Error, ErrorDetail};
22
23/// The detail `pdu`'s structured body adds to its class and code. `None` for
24/// a plain class/code error, for a service with no structured body, and for
25/// a body whose optional fields are all absent (SubscribeCOVPropertyMultiple's
26/// general error, VTClose-Error without its list).
27pub fn detail(pdu: &ErrorPdu) -> Option<ErrorDetail> {
28    let service = pdu.service_choice;
29    let error = if service == ConfirmedServiceChoice::ADD_LIST_ELEMENT
30        || service == ConfirmedServiceChoice::REMOVE_LIST_ELEMENT
31    {
32        ChangeListError::try_from(pdu).map(Error::from)
33    } else if service == ConfirmedServiceChoice::CREATE_OBJECT {
34        CreateObjectError::try_from(pdu).map(Error::from)
35    } else if service == ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE {
36        WritePropertyMultipleError::try_from(pdu).map(Error::from)
37    } else if service == ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE {
38        SubscribeCOVPropertyMultipleError::try_from(pdu).map(Error::from)
39    } else if service == ConfirmedServiceChoice::CONFIRMED_PRIVATE_TRANSFER {
40        PrivateTransferError::try_from(pdu).map(Error::from)
41    } else if service == ConfirmedServiceChoice::VT_CLOSE {
42        VTCloseError::try_from(pdu).map(Error::from)
43    } else {
44        return None;
45    };
46    match error {
47        Ok(Error::Structured { detail, .. }) => Some(*detail),
48        _ => None,
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55    use bacnet_types::constructed::BACnetObjectPropertyReference;
56    use bacnet_types::enums::{ErrorClass, ErrorCode, ObjectType, PropertyIdentifier};
57    use bacnet_types::primitives::ObjectIdentifier;
58    use bytes::Bytes;
59
60    #[test]
61    fn each_service_reads_its_own_body() {
62        let reference = BACnetObjectPropertyReference {
63            object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 1).unwrap(),
64            property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
65            property_array_index: None,
66        };
67        let (class, code) = (ErrorClass::PROPERTY, ErrorCode::WRITE_ACCESS_DENIED);
68        let cases = [
69            (
70                ChangeListError {
71                    error_class: class,
72                    error_code: code,
73                    first_failed_element_number: 2,
74                }
75                .to_error_pdu(1, ConfirmedServiceChoice::REMOVE_LIST_ELEMENT),
76                Some(ErrorDetail::FirstFailedElementNumber(2)),
77            ),
78            (
79                CreateObjectError {
80                    error_class: class,
81                    error_code: code,
82                    first_failed_element_number: 3,
83                }
84                .to_error_pdu(1),
85                Some(ErrorDetail::FirstFailedElementNumber(3)),
86            ),
87            (
88                WritePropertyMultipleError {
89                    error_class: class,
90                    error_code: code,
91                    first_failed_write_attempt: reference.clone(),
92                }
93                .to_error_pdu(1),
94                Some(ErrorDetail::FirstFailedWriteAttempt(reference.clone())),
95            ),
96            (
97                SubscribeCOVPropertyMultipleError {
98                    error_class: class,
99                    error_code: code,
100                    first_failed_subscription: Some(reference.clone()),
101                }
102                .to_error_pdu(1),
103                Some(ErrorDetail::FirstFailedSubscription(reference.clone())),
104            ),
105            (
106                SubscribeCOVPropertyMultipleError {
107                    error_class: class,
108                    error_code: code,
109                    first_failed_subscription: None,
110                }
111                .to_error_pdu(1),
112                None,
113            ),
114            (
115                PrivateTransferError {
116                    error_class: class,
117                    error_code: code,
118                    vendor_id: 9,
119                    service_number: 1,
120                    error_parameters: None,
121                }
122                .to_error_pdu(1),
123                Some(ErrorDetail::PrivateTransfer {
124                    vendor_id: 9,
125                    service_number: 1,
126                    error_parameters: None,
127                }),
128            ),
129            (
130                VTCloseError {
131                    error_class: class,
132                    error_code: code,
133                    list_of_vt_session_identifiers: Some(vec![3]),
134                }
135                .to_error_pdu(1),
136                Some(ErrorDetail::VtSessionIdentifiers(vec![3])),
137            ),
138        ];
139        for (pdu, expected) in cases {
140            assert_eq!(detail(&pdu), expected, "{:?}", pdu.service_choice);
141            // The same body under a service without one, or a plain error
142            // under the right service, has no detail.
143            let mut other = pdu.clone();
144            other.service_choice = ConfirmedServiceChoice::READ_PROPERTY;
145            assert_eq!(detail(&other), None);
146            let mut plain = pdu;
147            plain.error_data = Bytes::new();
148            assert_eq!(detail(&plain), None);
149        }
150    }
151}