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::with_detail(
106 error.error_class,
107 error.error_code,
108 error
109 .list_of_vt_session_identifiers
110 .map(ErrorDetail::VtSessionIdentifiers),
111 )
112 }
113}
114
115#[cfg(test)]
116mod tests {
117 use bacnet_types::error::RemoteError;
118 use super::*;
119 use bytes::Bytes;
120
121 fn sample(sessions: Option<Vec<u8>>) -> VTCloseError {
122 VTCloseError {
123 error_class: ErrorClass::SERVICES,
124 error_code: ErrorCode::VT_SESSION_TERMINATION_FAILURE,
125 list_of_vt_session_identifiers: sessions,
126 }
127 }
128
129 fn encoded(error: &VTCloseError) -> Vec<u8> {
130 let mut body = BytesMut::new();
131 error.encode(&mut body);
132 body.to_vec()
133 }
134
135 #[test]
136 fn vt_close_error_round_trips_with_exact_shape() {
137 let head = [0x0E, 0x91, 5, 0x91, 39, 0x0F];
139 for (sessions, tail) in [
140 (None, &[][..]),
141 (Some(vec![1, 4]), &[0x1E, 0x21, 1, 0x21, 4, 0x1F]),
142 (Some(vec![255]), &[0x1E, 0x21, 255, 0x1F]),
143 ] {
144 let error = sample(sessions);
145 let wire = [&head[..], tail].concat();
146 assert_eq!(encoded(&error), wire);
147 assert_eq!(VTCloseError::decode(&wire).unwrap(), error);
148 let pdu = error.to_error_pdu(2);
149 assert_eq!(VTCloseError::try_from(&pdu).unwrap(), error);
150 }
151 }
152
153 #[test]
154 fn only_a_present_list_is_a_detail() {
155 assert!(matches!(
156 Error::from(sample(None)),
157 Error::Remote(RemoteError::Error { class: ErrorClass::SERVICES, code: ErrorCode::VT_SESSION_TERMINATION_FAILURE })
158 ));
159 assert!(matches!(
160 Error::from(sample(Some(vec![1, 4]))),
161 Error::Structured { class: ErrorClass::SERVICES, code: ErrorCode::VT_SESSION_TERMINATION_FAILURE, detail }
162 if *detail == ErrorDetail::VtSessionIdentifiers(vec![1, 4])
163 ));
164 }
165
166 #[test]
167 fn vt_close_error_refuses_malformed_plain_and_mismatched() {
168 let body = encoded(&sample(Some(vec![1, 4])));
169 for malformed in [
170 body[..body.len() - 1].to_vec(),
171 [&body[..], &[0x00][..]].concat(),
172 [&body[..6], &[0x21, 1][..]].concat(),
173 [&body[..6], &[0x1E, 0x22, 1, 0, 0x1F][..]].concat(),
174 [&body[..6], &[0x1E, 0x09, 1, 0x1F][..]].concat(),
175 ] {
176 assert!(
177 VTCloseError::decode(&malformed).is_err(),
178 "{malformed:02x?}"
179 );
180 }
181
182 let mut pdu = sample(None).to_error_pdu(1);
183 pdu.error_code = ErrorCode::UNKNOWN_VT_SESSION;
184 assert!(VTCloseError::try_from(&pdu).is_err());
185 let mut pdu = sample(None).to_error_pdu(1);
186 pdu.error_data = Bytes::new();
187 assert!(VTCloseError::try_from(&pdu).is_err());
188 let mut pdu = sample(None).to_error_pdu(1);
189 pdu.service_choice = ConfirmedServiceChoice::VT_OPEN;
190 assert!(VTCloseError::try_from(&pdu).is_err());
191 }
192}