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::with_detail(
127            error.error_class,
128            error.error_code,
129            error
130                .first_failed_subscription
131                .map(ErrorDetail::FirstFailedSubscription),
132        )
133    }
134}
135
136#[cfg(test)]
137mod tests {
138    use bacnet_types::error::RemoteError;
139    use super::*;
140    use bacnet_types::enums::ObjectType;
141    use bacnet_types::primitives::ObjectIdentifier;
142    use bytes::Bytes;
143
144    fn subscription(index: Option<u32>) -> SubscribeCOVPropertyMultipleError {
145        SubscribeCOVPropertyMultipleError {
146            error_class: ErrorClass::PROPERTY,
147            error_code: ErrorCode::NOT_COV_PROPERTY,
148            first_failed_subscription: Some(BACnetObjectPropertyReference {
149                object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_VALUE, 7).unwrap(),
150                property_identifier: PropertyIdentifier::PRESENT_VALUE.to_raw(),
151                property_array_index: index,
152            }),
153        }
154    }
155
156    fn general() -> SubscribeCOVPropertyMultipleError {
157        SubscribeCOVPropertyMultipleError {
158            error_class: ErrorClass::SERVICES,
159            error_code: ErrorCode::VALUE_OUT_OF_RANGE,
160            first_failed_subscription: None,
161        }
162    }
163
164    fn encoded(error: &SubscribeCOVPropertyMultipleError) -> Vec<u8> {
165        let mut body = BytesMut::new();
166        error.encode(&mut body);
167        body.to_vec()
168    }
169
170    #[test]
171    fn both_choices_round_trip_with_exact_shape() {
172        for (error, wire) in [
173            (general(), &[0x0E, 0x91, 5, 0x91, 37, 0x0F][..]),
174            // [1] { [0] object (0x0C, four octets), [1] { [0] property 85 },
175            // [2] { class 2, code 44 } }
176            (
177                subscription(None),
178                &[
179                    0x1E, 0x0C, 0x00, 0x80, 0x00, 0x07, 0x1E, 0x09, 85, 0x1F, 0x2E, 0x91, 2, 0x91,
180                    44, 0x2F, 0x1F,
181                ],
182            ),
183            (
184                subscription(Some(8)),
185                &[
186                    0x1E, 0x0C, 0x00, 0x80, 0x00, 0x07, 0x1E, 0x09, 85, 0x19, 8, 0x1F, 0x2E, 0x91,
187                    2, 0x91, 44, 0x2F, 0x1F,
188                ],
189            ),
190        ] {
191            assert_eq!(encoded(&error), wire);
192            assert_eq!(
193                SubscribeCOVPropertyMultipleError::decode(wire).unwrap(),
194                error
195            );
196            let pdu = error.to_error_pdu(4);
197            assert_eq!(
198                SubscribeCOVPropertyMultipleError::try_from(&pdu).unwrap(),
199                error
200            );
201        }
202    }
203
204    #[test]
205    fn only_the_subscription_choice_carries_a_detail() {
206        assert!(matches!(
207            Error::from(general()),
208            Error::Remote(RemoteError::Error { class: ErrorClass::SERVICES, code: ErrorCode::VALUE_OUT_OF_RANGE })
209        ));
210        let error = subscription(Some(8));
211        let reference = error.first_failed_subscription.clone().unwrap();
212        assert!(matches!(
213            Error::from(error),
214            Error::Structured { class: ErrorClass::PROPERTY, code: ErrorCode::NOT_COV_PROPERTY, detail }
215                if *detail == ErrorDetail::FirstFailedSubscription(reference)
216        ));
217    }
218
219    #[test]
220    fn decode_refuses_malformed_plain_and_mismatched() {
221        let body = encoded(&subscription(Some(8)));
222        for malformed in [
223            Vec::new(),
224            body[..body.len() - 1].to_vec(),
225            [&body[..], &[0x00][..]].concat(),
226            [&encoded(&general())[..], &body[..]].concat(),
227            // The failed subscription without its [2] error.
228            [&body[..12], &[0x1F][..]].concat(),
229            // A three-octet object identifier.
230            [&[0x1E, 0x0B][..], &body[3..]].concat(),
231        ] {
232            assert!(
233                SubscribeCOVPropertyMultipleError::decode(&malformed).is_err(),
234                "{malformed:02x?}"
235            );
236        }
237
238        let mut pdu = subscription(None).to_error_pdu(1);
239        pdu.error_code = ErrorCode::UNKNOWN_OBJECT;
240        assert!(SubscribeCOVPropertyMultipleError::try_from(&pdu).is_err());
241        let mut pdu = general().to_error_pdu(1);
242        pdu.error_data = Bytes::from_static(&[0x91, 5, 0x91, 37]);
243        assert!(SubscribeCOVPropertyMultipleError::try_from(&pdu).is_err());
244        let mut pdu = general().to_error_pdu(1);
245        pdu.service_choice = ConfirmedServiceChoice::SUBSCRIBE_COV_PROPERTY;
246        assert!(SubscribeCOVPropertyMultipleError::try_from(&pdu).is_err());
247    }
248}