1#[cfg(not(feature = "std"))]
5use alloc::vec::Vec;
6
7use crate::common::error_type::{
8 decode_error_pdu, decode_error_type, encode_error_type, error_pdu,
9};
10use crate::common::MAX_DECODED_ITEMS;
11use bacnet_encoding::apdu::ErrorPdu;
12use bacnet_encoding::constructed::tagged::{
13 decode_app_unsigned, decode_ctx_constructed, expect_end,
14};
15use bacnet_encoding::{primitives, tags};
16use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode};
17use bacnet_types::error::{Error, ErrorDetail};
18use bytes::BytesMut;
19
20const WHAT: &str = "VTClose-Error";
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct VTCloseError {
25 pub error_class: ErrorClass,
27 pub error_code: ErrorCode,
29 pub list_of_vt_session_identifiers: Option<Vec<u8>>,
33}
34
35impl VTCloseError {
36 pub fn encode(&self, buf: &mut BytesMut) {
38 encode_error_type(buf, self.error_class, self.error_code);
39 if let Some(sessions) = &self.list_of_vt_session_identifiers {
40 tags::encode_opening_tag(buf, 1);
41 for &session in sessions {
42 primitives::encode_app_unsigned(buf, u64::from(session));
43 }
44 tags::encode_closing_tag(buf, 1);
45 }
46 }
47
48 pub fn decode(data: &[u8]) -> Result<Self, Error> {
50 let ((error_class, error_code), mut offset) = decode_error_type(data, WHAT)?;
51 let mut list_of_vt_session_identifiers = None;
52 if offset < data.len() {
53 let (frame, end) = decode_ctx_constructed(data, offset, 1, WHAT)?;
54 let mut sessions = Vec::new();
55 let mut position = 0;
56 while position < frame.len() {
57 if sessions.len() >= MAX_DECODED_ITEMS {
58 return Err(Error::overflow(position, "VTClose-Error too many sessions"));
59 }
60 let (session, next) =
61 decode_app_unsigned::<u8>(frame, position, "VTClose-Error session-identifier")?;
62 sessions.push(session);
63 position = next;
64 }
65 list_of_vt_session_identifiers = Some(sessions);
66 offset = end;
67 }
68 expect_end(data, offset, offset, WHAT)?;
69 Ok(Self {
70 error_class,
71 error_code,
72 list_of_vt_session_identifiers,
73 })
74 }
75
76 pub fn to_error_pdu(&self, invoke_id: u8) -> ErrorPdu {
78 error_pdu(
79 invoke_id,
80 ConfirmedServiceChoice::VT_CLOSE,
81 (self.error_class, self.error_code),
82 |body| self.encode(body),
83 )
84 }
85}
86
87impl TryFrom<&ErrorPdu> for VTCloseError {
88 type Error = Error;
89
90 fn try_from(pdu: &ErrorPdu) -> Result<Self, Self::Error> {
93 decode_error_pdu(
94 pdu,
95 &[ConfirmedServiceChoice::VT_CLOSE],
96 WHAT,
97 Self::decode,
98 |error| (error.error_class, error.error_code),
99 )
100 }
101}
102
103impl From<VTCloseError> for Error {
104 fn from(error: VTCloseError) -> Self {
105 Error::protocol(
106 error.error_class.to_raw() as u32,
107 error.error_code.to_raw() as u32,
108 error
109 .list_of_vt_session_identifiers
110 .map(ErrorDetail::VtSessionIdentifiers),
111 )
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use bytes::Bytes;
119
120 fn sample(sessions: Option<Vec<u8>>) -> VTCloseError {
121 VTCloseError {
122 error_class: ErrorClass::SERVICES,
123 error_code: ErrorCode::VT_SESSION_TERMINATION_FAILURE,
124 list_of_vt_session_identifiers: sessions,
125 }
126 }
127
128 fn encoded(error: &VTCloseError) -> Vec<u8> {
129 let mut body = BytesMut::new();
130 error.encode(&mut body);
131 body.to_vec()
132 }
133
134 #[test]
135 fn vt_close_error_round_trips_with_exact_shape() {
136 let head = [0x0E, 0x91, 5, 0x91, 39, 0x0F];
138 for (sessions, tail) in [
139 (None, &[][..]),
140 (Some(vec![1, 4]), &[0x1E, 0x21, 1, 0x21, 4, 0x1F]),
141 (Some(vec![255]), &[0x1E, 0x21, 255, 0x1F]),
142 ] {
143 let error = sample(sessions);
144 let wire = [&head[..], tail].concat();
145 assert_eq!(encoded(&error), wire);
146 assert_eq!(VTCloseError::decode(&wire).unwrap(), error);
147 let pdu = error.to_error_pdu(2);
148 assert_eq!(VTCloseError::try_from(&pdu).unwrap(), error);
149 }
150 }
151
152 #[test]
153 fn only_a_present_list_is_a_detail() {
154 assert!(matches!(
155 Error::from(sample(None)),
156 Error::Protocol { class: 5, code: 39 }
157 ));
158 assert!(matches!(
159 Error::from(sample(Some(vec![1, 4]))),
160 Error::Structured { class: 5, code: 39, detail }
161 if *detail == ErrorDetail::VtSessionIdentifiers(vec![1, 4])
162 ));
163 }
164
165 #[test]
166 fn vt_close_error_refuses_malformed_plain_and_mismatched() {
167 let body = encoded(&sample(Some(vec![1, 4])));
168 for malformed in [
169 body[..body.len() - 1].to_vec(),
170 [&body[..], &[0x00][..]].concat(),
171 [&body[..6], &[0x21, 1][..]].concat(),
172 [&body[..6], &[0x1E, 0x22, 1, 0, 0x1F][..]].concat(),
173 [&body[..6], &[0x1E, 0x09, 1, 0x1F][..]].concat(),
174 ] {
175 assert!(
176 VTCloseError::decode(&malformed).is_err(),
177 "{malformed:02x?}"
178 );
179 }
180
181 let mut pdu = sample(None).to_error_pdu(1);
182 pdu.error_code = ErrorCode::UNKNOWN_VT_SESSION;
183 assert!(VTCloseError::try_from(&pdu).is_err());
184 let mut pdu = sample(None).to_error_pdu(1);
185 pdu.error_data = Bytes::new();
186 assert!(VTCloseError::try_from(&pdu).is_err());
187 let mut pdu = sample(None).to_error_pdu(1);
188 pdu.service_choice = ConfirmedServiceChoice::VT_OPEN;
189 assert!(VTCloseError::try_from(&pdu).is_err());
190 }
191}