1use 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#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct SubscribeCOVPropertyMultipleError {
22 pub error_class: ErrorClass,
24 pub error_code: ErrorCode,
26 pub first_failed_subscription: Option<BACnetObjectPropertyReference>,
31}
32
33impl SubscribeCOVPropertyMultipleError {
34 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 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 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 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 (
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 [&body[..12], &[0x1F][..]].concat(),
229 [&[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}