1use crate::common::error_type::{decode_error_type, encode_error_type};
4use bacnet_encoding::apdu::ErrorPdu;
5use bacnet_encoding::constructed::tagged::{decode_ctx_constructed, expect_end};
6use bacnet_encoding::constructed::{
7 decode_object_property_reference, encode_object_property_reference,
8};
9use bacnet_encoding::tags;
10use bacnet_types::constructed::BACnetObjectPropertyReference;
11use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode};
12use bacnet_types::error::{Error, ErrorDetail};
13use bytes::BytesMut;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct WritePropertyMultipleError {
18 pub error_class: ErrorClass,
20 pub error_code: ErrorCode,
22 pub first_failed_write_attempt: BACnetObjectPropertyReference,
24}
25
26impl WritePropertyMultipleError {
27 pub fn encode(&self, buf: &mut BytesMut) {
29 encode_error_type(buf, self.error_class, self.error_code);
30 tags::encode_opening_tag(buf, 1);
31 encode_object_property_reference(buf, &self.first_failed_write_attempt);
32 tags::encode_closing_tag(buf, 1);
33 }
34
35 pub fn decode(data: &[u8]) -> Result<Self, Error> {
37 let ((error_class, error_code), offset) = decode_error_type(data, "WPM Error")?;
38 let (reference_body, end) = decode_ctx_constructed(data, offset, 1, "WPM Error")?;
39 expect_end(data, end, end, "WPM Error")?;
40 let first_failed_write_attempt = decode_object_property_reference(reference_body)?;
41 Ok(Self {
42 error_class,
43 error_code,
44 first_failed_write_attempt,
45 })
46 }
47
48 pub fn to_error_pdu(&self, invoke_id: u8) -> ErrorPdu {
50 let mut body = BytesMut::new();
51 self.encode(&mut body);
52 ErrorPdu {
53 invoke_id,
54 service_choice: ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE,
55 error_class: self.error_class,
56 error_code: self.error_code,
57 error_data: body.freeze(),
58 }
59 }
60
61 pub fn from_error_pdu(pdu: &ErrorPdu) -> Result<Self, Error> {
63 Self::try_from(pdu)
64 }
65}
66
67impl TryFrom<&ErrorPdu> for WritePropertyMultipleError {
68 type Error = Error;
69
70 fn try_from(pdu: &ErrorPdu) -> Result<Self, Self::Error> {
71 if pdu.service_choice != ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE {
72 return Err(Error::decoding(
73 0,
74 "ErrorPdu is not a WritePropertyMultiple result",
75 ));
76 }
77 let decoded = Self::decode(&pdu.error_data)?;
78 if decoded.error_class != pdu.error_class || decoded.error_code != pdu.error_code {
79 return Err(Error::decoding(
80 0,
81 "formal WPM Error body disagrees with ErrorPdu class/code",
82 ));
83 }
84 Ok(decoded)
85 }
86}
87
88impl From<WritePropertyMultipleError> for Error {
89 fn from(error: WritePropertyMultipleError) -> Self {
90 Error::with_detail(
91 error.error_class,
92 error.error_code,
93 Some(ErrorDetail::FirstFailedWriteAttempt(
94 error.first_failed_write_attempt,
95 )),
96 )
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103 use bacnet_types::enums::{ObjectType, PropertyIdentifier};
104 use bacnet_types::primitives::ObjectIdentifier;
105 use bytes::Bytes;
106
107 fn sample(index: Option<u32>) -> WritePropertyMultipleError {
108 WritePropertyMultipleError {
109 error_class: ErrorClass::PROPERTY,
110 error_code: ErrorCode::WRITE_ACCESS_DENIED,
111 first_failed_write_attempt: BACnetObjectPropertyReference {
112 object_identifier: ObjectIdentifier::new(ObjectType::EVENT_ENROLLMENT, 7).unwrap(),
113 property_identifier: PropertyIdentifier::ACKED_TRANSITIONS.to_raw(),
114 property_array_index: index,
115 },
116 }
117 }
118
119 #[test]
120 fn indexed_and_unindexed_round_trip_with_exact_outer_shape() {
121 for index in [None, Some(3)] {
122 let error = sample(index);
123 let mut body = BytesMut::new();
124 error.encode(&mut body);
125 assert_eq!(
126 &body[..7],
127 &[
128 0x0e,
129 0x91,
130 ErrorClass::PROPERTY.to_raw() as u8,
131 0x91,
132 ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u8,
133 0x0f,
134 0x1e,
135 ]
136 );
137 assert_eq!(body.last(), Some(&0x1f));
138 assert!(!body.iter().any(|byte| matches!(byte, 0x4e | 0x4f)));
139 assert_eq!(WritePropertyMultipleError::decode(&body).unwrap(), error);
140 }
141 }
142
143 #[test]
144 fn error_pdu_conversion_retains_complete_formal_body() {
145 let error = sample(None);
146 let pdu = error.to_error_pdu(9);
147 assert_eq!(pdu.invoke_id, 9);
148 assert_eq!(pdu.error_data[0], 0x0e);
149 assert_eq!(WritePropertyMultipleError::try_from(&pdu).unwrap(), error);
150 assert!(matches!(
151 Error::from(error.clone()),
152 Error::Structured { class: ErrorClass::PROPERTY, code: ErrorCode::WRITE_ACCESS_DENIED, detail }
153 if *detail == ErrorDetail::FirstFailedWriteAttempt(
154 error.first_failed_write_attempt.clone()
155 )
156 ));
157 }
158
159 #[test]
160 fn strict_decode_rejects_missing_duplicate_wrong_and_trailing_fields() {
161 let error = sample(None);
162 let mut body = BytesMut::new();
163 error.encode(&mut body);
164
165 for malformed in [
166 body[..body.len() - 1].to_vec(),
167 body[5..].to_vec(),
168 {
169 let mut bytes = body.to_vec();
170 bytes[6] = 0x2e;
171 bytes
172 },
173 {
174 let mut bytes = body.to_vec();
175 bytes.extend_from_slice(&[0x1e, 0x1f]);
176 bytes
177 },
178 {
179 let mut bytes = body.to_vec();
180 bytes.splice(3..3, [0x91, 0x02]);
181 bytes
182 },
183 {
184 let mut bytes = body.to_vec();
185 let property = bytes
186 .windows(2)
187 .rposition(|window| window == [0x19, 0x00])
188 .unwrap();
189 bytes.drain(property..property + 2);
190 bytes
191 },
192 {
193 let mut bytes = body.to_vec();
194 let property = bytes
195 .windows(2)
196 .rposition(|window| window == [0x19, 0x00])
197 .unwrap();
198 bytes.splice(property..property, [0x19, 0x00]);
199 bytes
200 },
201 {
202 let mut bytes = body.to_vec();
203 let property = bytes
204 .windows(2)
205 .rposition(|window| window == [0x19, 0x00])
206 .unwrap();
207 bytes[property] = 0x29;
208 bytes
209 },
210 ] {
211 assert!(WritePropertyMultipleError::decode(&malformed).is_err());
212 }
213 }
214
215 #[test]
216 fn conversion_rejects_mismatched_projection() {
217 let error = sample(None);
218 let mut pdu = error.to_error_pdu(1);
219 pdu.error_code = ErrorCode::UNKNOWN_PROPERTY;
220 assert!(WritePropertyMultipleError::from_error_pdu(&pdu).is_err());
221
222 pdu.error_data = Bytes::new();
223 assert!(WritePropertyMultipleError::from_error_pdu(&pdu).is_err());
224 }
225}