Skip to main content

bacnet_services/
cov_multiple_error.rs

1//! SubscribeCOVPropertyMultiple-Error (Clause 21), a CHOICE: a general error
2//! under `[0]`, or under `[1]` the first failed subscription's `[0]` monitored
3//! object identifier, `[1]` property reference and `[2]` error.
4
5use crate::common::error_type::{decode_error_in, decode_error_pdu, encode_error_in, error_pdu};
6use bacnet_encoding::apdu::ErrorPdu;
7use bacnet_encoding::constructed::tagged::{
8    decode_ctx_constructed, decode_ctx_object_id, expect_end,
9};
10use bacnet_encoding::constructed::{decode_property_reference, encode_property_reference};
11use bacnet_encoding::{primitives, tags};
12use bacnet_types::constructed::{BACnetObjectPropertyReference, PropertyReference};
13use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode, PropertyIdentifier};
14use bacnet_types::error::{Error, ErrorDetail};
15use bytes::BytesMut;
16
17const WHAT: &str = "SubscribeCOVPropertyMultiple-Error";
18
19/// The Result(-) body of SubscribeCOVPropertyMultiple (Clause 13.16.1.3).
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct SubscribeCOVPropertyMultipleError {
22    /// Error class, of the general error or of the failed subscription.
23    pub error_class: ErrorClass,
24    /// Error code, of the general error or of the failed subscription.
25    pub error_code: ErrorCode,
26    /// The monitored object and the property reference (property and array
27    /// index) of the COV reference that failed, for the
28    /// first-failed-subscription choice; `None` for the general error choice,
29    /// which reports a failure before any subscription was processed.
30    pub first_failed_subscription: Option<BACnetObjectPropertyReference>,
31}
32
33impl SubscribeCOVPropertyMultipleError {
34    /// Encode the choice the body holds.
35    pub fn encode(&self, buf: &mut BytesMut) {
36        let Some(subscription) = &self.first_failed_subscription else {
37            encode_error_in(buf, 0, self.error_class, self.error_code);
38            return;
39        };
40        tags::encode_opening_tag(buf, 1);
41        primitives::encode_ctx_object_id(buf, 0, &subscription.object_identifier);
42        tags::encode_opening_tag(buf, 1);
43        encode_property_reference(
44            buf,
45            &PropertyReference {
46                property_identifier: PropertyIdentifier::from_raw(subscription.property_identifier),
47                property_array_index: subscription.property_array_index,
48            },
49        );
50        tags::encode_closing_tag(buf, 1);
51        encode_error_in(buf, 2, self.error_class, self.error_code);
52        tags::encode_closing_tag(buf, 1);
53    }
54
55    /// Decode one complete body, either choice, with no trailing content.
56    pub fn decode(data: &[u8]) -> Result<Self, Error> {
57        let opening = tags::decode_tag(data, 0)?.0;
58        if opening.is_opening_tag(0) {
59            let ((error_class, error_code), end) = decode_error_in(data, 0, 0, WHAT)?;
60            expect_end(data, end, end, WHAT)?;
61            return Ok(Self {
62                error_class,
63                error_code,
64                first_failed_subscription: None,
65            });
66        }
67        let (subscription, end) = decode_ctx_constructed(data, 0, 1, WHAT)?;
68        expect_end(data, end, end, WHAT)?;
69        let (object_identifier, offset) = decode_ctx_object_id(
70            subscription,
71            0,
72            0,
73            "SubscribeCOVPropertyMultiple-Error monitored object identifier",
74        )?;
75        let reference_at = offset;
76        let (reference_body, offset) = decode_ctx_constructed(subscription, offset, 1, WHAT)?;
77        let (reference, reference_end) = decode_property_reference(reference_body, 0)?;
78        expect_end(
79            reference_body,
80            reference_end,
81            reference_at,
82            "SubscribeCOVPropertyMultiple-Error monitored property reference",
83        )?;
84        let ((error_class, error_code), end) = decode_error_in(subscription, offset, 2, WHAT)?;
85        expect_end(subscription, end, end, WHAT)?;
86        Ok(Self {
87            error_class,
88            error_code,
89            first_failed_subscription: Some(BACnetObjectPropertyReference {
90                object_identifier,
91                property_identifier: reference.property_identifier.to_raw(),
92                property_array_index: reference.property_array_index,
93            }),
94        })
95    }
96
97    /// Build the SubscribeCOVPropertyMultiple Error PDU carrying this body.
98    pub fn to_error_pdu(&self, invoke_id: u8) -> ErrorPdu {
99        error_pdu(
100            invoke_id,
101            ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE,
102            (self.error_class, self.error_code),
103            |body| self.encode(body),
104        )
105    }
106}
107
108impl TryFrom<&ErrorPdu> for SubscribeCOVPropertyMultipleError {
109    type Error = Error;
110
111    /// Decode and verify the body of a SubscribeCOVPropertyMultiple Error
112    /// PDU. A plain class/code error, which older devices send, is refused.
113    fn try_from(pdu: &ErrorPdu) -> Result<Self, Self::Error> {
114        decode_error_pdu(
115            pdu,
116            &[ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY_MULTIPLE],
117            WHAT,
118            Self::decode,
119            |error| (error.error_class, error.error_code),
120        )
121    }
122}
123
124impl From<SubscribeCOVPropertyMultipleError> for Error {
125    fn from(error: SubscribeCOVPropertyMultipleError) -> Self {
126        Error::protocol(
127            error.error_class.to_raw() as u32,
128            error.error_code.to_raw() as u32,
129            error
130                .first_failed_subscription
131                .map(ErrorDetail::FirstFailedSubscription),
132        )
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use super::*;
139    use bacnet_types::enums::ObjectType;
140    use bacnet_types::primitives::ObjectIdentifier;
141    use bytes::Bytes;
142
143    fn subscription(index: Option<u32>) -> SubscribeCOVPropertyMultipleError {
144        SubscribeCOVPropertyMultipleError {
145            error_class: ErrorClass::PROPERTY,
146            error_code: ErrorCode::NOT_COV_PROPERTY,
147            first_failed_subscription: Some(BACnetObjectPropertyReference {
148                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 7).unwrap(),
149                property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
150                property_array_index: index,
151            }),
152        }
153    }
154
155    fn general() -> SubscribeCOVPropertyMultipleError {
156        SubscribeCOVPropertyMultipleError {
157            error_class: ErrorClass::SERVICES,
158            error_code: ErrorCode::VALUE_OUT_OF_RANGE,
159            first_failed_subscription: None,
160        }
161    }
162
163    fn encoded(error: &SubscribeCOVPropertyMultipleError) -> Vec<u8> {
164        let mut body = BytesMut::new();
165        error.encode(&mut body);
166        body.to_vec()
167    }
168
169    #[test]
170    fn both_choices_round_trip_with_exact_shape() {
171        for (error, wire) in [
172            (general(), &[0x0E, 0x91, 5, 0x91, 37, 0x0F][..]),
173            // [1] { [0] object (0x0C, four octets), [1] { [0] property 85 },
174            // [2] { class 2, code 44 } }
175            (
176                subscription(None),
177                &[
178                    0x1E, 0x0C, 0x00, 0x80, 0x00, 0x07, 0x1E, 0x09, 85, 0x1F, 0x2E, 0x91, 2, 0x91,
179                    44, 0x2F, 0x1F,
180                ],
181            ),
182            (
183                subscription(Some(8)),
184                &[
185                    0x1E, 0x0C, 0x00, 0x80, 0x00, 0x07, 0x1E, 0x09, 85, 0x19, 8, 0x1F, 0x2E, 0x91,
186                    2, 0x91, 44, 0x2F, 0x1F,
187                ],
188            ),
189        ] {
190            assert_eq!(encoded(&error), wire);
191            assert_eq!(
192                SubscribeCOVPropertyMultipleError::decode(wire).unwrap(),
193                error
194            );
195            let pdu = error.to_error_pdu(4);
196            assert_eq!(
197                SubscribeCOVPropertyMultipleError::try_from(&pdu).unwrap(),
198                error
199            );
200        }
201    }
202
203    #[test]
204    fn only_the_subscription_choice_carries_a_detail() {
205        assert!(matches!(
206            Error::from(general()),
207            Error::Protocol { class: 5, code: 37 }
208        ));
209        let error = subscription(Some(8));
210        let reference = error.first_failed_subscription.clone().unwrap();
211        assert!(matches!(
212            Error::from(error),
213            Error::Structured { class: 2, code: 44, detail }
214                if *detail == ErrorDetail::FirstFailedSubscription(reference)
215        ));
216    }
217
218    #[test]
219    fn decode_refuses_malformed_plain_and_mismatched() {
220        let body = encoded(&subscription(Some(8)));
221        for malformed in [
222            Vec::new(),
223            body[..body.len() - 1].to_vec(),
224            [&body[..], &[0x00][..]].concat(),
225            [&encoded(&general())[..], &body[..]].concat(),
226            // The failed subscription without its [2] error.
227            [&body[..12], &[0x1F][..]].concat(),
228            // A three-octet object identifier.
229            [&[0x1E, 0x0B][..], &body[3..]].concat(),
230        ] {
231            assert!(
232                SubscribeCOVPropertyMultipleError::decode(&malformed).is_err(),
233                "{malformed:02x?}"
234            );
235        }
236
237        let mut pdu = subscription(None).to_error_pdu(1);
238        pdu.error_code = ErrorCode::UNKNOWN_OBJECT;
239        assert!(SubscribeCOVPropertyMultipleError::try_from(&pdu).is_err());
240        let mut pdu = general().to_error_pdu(1);
241        pdu.error_data = Bytes::from_static(&[0x91, 5, 0x91, 37]);
242        assert!(SubscribeCOVPropertyMultipleError::try_from(&pdu).is_err());
243        let mut pdu = general().to_error_pdu(1);
244        pdu.service_choice = ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY;
245        assert!(SubscribeCOVPropertyMultipleError::try_from(&pdu).is_err());
246    }
247}