use crate::common::error_type::{
decode_element_error, decode_error_pdu, encode_element_error, error_pdu,
};
use bacnet_encoding::apdu::ErrorPdu;
use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode};
use bacnet_types::error::{Error, ErrorDetail};
use bytes::BytesMut;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CreateObjectError {
pub error_class: ErrorClass,
pub error_code: ErrorCode,
pub first_failed_element_number: u32,
}
impl CreateObjectError {
pub fn encode(&self, buf: &mut BytesMut) {
encode_element_error(
buf,
self.error_class,
self.error_code,
self.first_failed_element_number,
);
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let (error_class, error_code, first_failed_element_number) = decode_element_error(
data,
"CreateObject-Error",
"CreateObject-Error first-failed-element-number",
)?;
Ok(Self {
error_class,
error_code,
first_failed_element_number,
})
}
pub fn to_error_pdu(&self, invoke_id: u8) -> ErrorPdu {
error_pdu(
invoke_id,
ConfirmedServiceChoice::CREATE_OBJECT,
(self.error_class, self.error_code),
|body| self.encode(body),
)
}
}
impl TryFrom<&ErrorPdu> for CreateObjectError {
type Error = Error;
fn try_from(pdu: &ErrorPdu) -> Result<Self, Self::Error> {
decode_error_pdu(
pdu,
&[ConfirmedServiceChoice::CREATE_OBJECT],
"CreateObject-Error",
Self::decode,
|error| (error.error_class, error.error_code),
)
}
}
impl From<CreateObjectError> for Error {
fn from(error: CreateObjectError) -> Self {
Error::with_detail(
error.error_class,
error.error_code,
Some(ErrorDetail::FirstFailedElementNumber(
error.first_failed_element_number,
)),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
fn sample(first_failed_element_number: u32) -> CreateObjectError {
CreateObjectError {
error_class: ErrorClass::PROPERTY,
error_code: ErrorCode::WRITE_ACCESS_DENIED,
first_failed_element_number,
}
}
#[test]
fn create_object_error_round_trips_with_exact_shape() {
for (number, tail) in [
(0, &[0x19, 0][..]),
(3, &[0x19, 3]),
(300, &[0x1A, 1, 44]),
(u32::MAX, &[0x1C, 0xFF, 0xFF, 0xFF, 0xFF]),
] {
let error = sample(number);
let mut body = BytesMut::new();
error.encode(&mut body);
assert_eq!(&body[..6], &[0x0E, 0x91, 2, 0x91, 40, 0x0F]);
assert_eq!(&body[6..], tail);
assert_eq!(CreateObjectError::decode(&body).unwrap(), error);
}
}
#[test]
fn create_object_error_pdu_conversion_and_projection() {
let pdu = sample(2).to_error_pdu(9);
assert_eq!(pdu.invoke_id, 9);
assert_eq!(pdu.service_choice, ConfirmedServiceChoice::CREATE_OBJECT);
assert_eq!(CreateObjectError::try_from(&pdu).unwrap(), sample(2));
assert!(matches!(
Error::from(sample(2)),
Error::Structured { class: ErrorClass::PROPERTY, code: ErrorCode::WRITE_ACCESS_DENIED, detail }
if *detail == ErrorDetail::FirstFailedElementNumber(2)
));
}
#[test]
fn create_object_error_refuses_malformed_plain_and_mismatched() {
let mut body = BytesMut::new();
sample(2).encode(&mut body);
for malformed in [
body[..6].to_vec(),
[&body[..6], &[0x29, 2][..]].concat(),
[&body[..], &[0x19, 2][..]].concat(),
[&body[..6], &[0x1D, 5, 1, 0, 0, 0, 0][..]].concat(),
] {
assert!(
CreateObjectError::decode(&malformed).is_err(),
"{malformed:02x?}"
);
}
let mut pdu = sample(1).to_error_pdu(1);
pdu.error_code = ErrorCode::INVALID_DATA_TYPE;
assert!(CreateObjectError::try_from(&pdu).is_err());
let mut pdu = sample(1).to_error_pdu(1);
pdu.error_data = Bytes::new();
assert!(CreateObjectError::try_from(&pdu).is_err());
let mut pdu = sample(1).to_error_pdu(1);
pdu.service_choice = ConfirmedServiceChoice::ADD_LIST_ELEMENT;
assert!(CreateObjectError::try_from(&pdu).is_err());
}
}