#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::common::error_type::{
decode_error_pdu, decode_error_type, encode_error_type, error_pdu,
};
use bacnet_encoding::apdu::ErrorPdu;
use bacnet_encoding::constructed::tagged::{
decode_ctx_constructed, decode_ctx_unsigned, expect_end,
};
use bacnet_encoding::{primitives, tags};
use bacnet_types::enums::{ConfirmedServiceChoice, ErrorClass, ErrorCode};
use bacnet_types::error::{Error, ErrorDetail};
use bytes::{BufMut, BytesMut};
const WHAT: &str = "ConfirmedPrivateTransfer-Error";
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrivateTransferError {
pub error_class: ErrorClass,
pub error_code: ErrorCode,
pub vendor_id: u32,
pub service_number: u32,
pub error_parameters: Option<Vec<u8>>,
}
impl PrivateTransferError {
pub fn encode(&self, buf: &mut BytesMut) {
encode_error_type(buf, self.error_class, self.error_code);
primitives::encode_ctx_unsigned(buf, 1, u64::from(self.vendor_id));
primitives::encode_ctx_unsigned(buf, 2, u64::from(self.service_number));
if let Some(parameters) = &self.error_parameters {
tags::encode_opening_tag(buf, 3);
buf.put_slice(parameters);
tags::encode_closing_tag(buf, 3);
}
}
pub fn decode(data: &[u8]) -> Result<Self, Error> {
let ((error_class, error_code), offset) = decode_error_type(data, WHAT)?;
let (vendor_id, offset) = decode_ctx_unsigned::<u32>(
data,
offset,
1,
"ConfirmedPrivateTransfer-Error vendor-id",
)?;
let (service_number, mut offset) = decode_ctx_unsigned::<u32>(
data,
offset,
2,
"ConfirmedPrivateTransfer-Error service-number",
)?;
let mut error_parameters = None;
if offset < data.len() {
let (parameters, end) = decode_ctx_constructed(data, offset, 3, WHAT)?;
error_parameters = Some(parameters.to_vec());
offset = end;
}
expect_end(data, offset, offset, WHAT)?;
Ok(Self {
error_class,
error_code,
vendor_id,
service_number,
error_parameters,
})
}
pub fn to_error_pdu(&self, invoke_id: u8) -> ErrorPdu {
error_pdu(
invoke_id,
ConfirmedServiceChoice::CONFIRMED_PRIVATE_TRANSFER,
(self.error_class, self.error_code),
|body| self.encode(body),
)
}
}
impl TryFrom<&ErrorPdu> for PrivateTransferError {
type Error = Error;
fn try_from(pdu: &ErrorPdu) -> Result<Self, Self::Error> {
decode_error_pdu(
pdu,
&[ConfirmedServiceChoice::CONFIRMED_PRIVATE_TRANSFER],
WHAT,
Self::decode,
|error| (error.error_class, error.error_code),
)
}
}
impl From<PrivateTransferError> for Error {
fn from(error: PrivateTransferError) -> Self {
Error::with_detail(
error.error_class,
error.error_code,
Some(ErrorDetail::PrivateTransfer {
vendor_id: error.vendor_id,
service_number: error.service_number,
error_parameters: error.error_parameters,
}),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
fn sample(error_parameters: Option<Vec<u8>>) -> PrivateTransferError {
PrivateTransferError {
error_class: ErrorClass::SERVICES,
error_code: ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED,
vendor_id: 555,
service_number: 7,
error_parameters,
}
}
fn encoded(error: &PrivateTransferError) -> Vec<u8> {
let mut body = BytesMut::new();
error.encode(&mut body);
body.to_vec()
}
#[test]
fn private_transfer_error_round_trips_with_exact_shape() {
let head = [0x0E, 0x91, 5, 0x91, 45, 0x0F, 0x1A, 0x02, 0x2B, 0x29, 7];
for (parameters, tail) in [
(None, &[][..]),
(Some(vec![0x21, 1]), &[0x3E, 0x21, 1, 0x3F]),
(Some(Vec::new()), &[0x3E, 0x3F]),
(
Some(vec![0x3E, 0x21, 1, 0x3F]),
&[0x3E, 0x3E, 0x21, 1, 0x3F, 0x3F],
),
] {
let error = sample(parameters);
let wire = [&head[..], tail].concat();
assert_eq!(encoded(&error), wire);
assert_eq!(PrivateTransferError::decode(&wire).unwrap(), error);
let pdu = error.to_error_pdu(3);
assert_eq!(PrivateTransferError::try_from(&pdu).unwrap(), error);
}
}
#[test]
fn private_transfer_error_projects_vendor_service_and_parameters() {
assert!(matches!(
Error::from(sample(Some(vec![0x21, 1]))),
Error::Structured { class: ErrorClass::SERVICES, code: ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED, detail }
if *detail == ErrorDetail::PrivateTransfer {
vendor_id: 555,
service_number: 7,
error_parameters: Some(vec![0x21, 1]),
}
));
}
#[test]
fn private_transfer_error_refuses_malformed_plain_and_mismatched() {
let body = encoded(&sample(Some(vec![0x21, 1])));
for malformed in [
body[..6].to_vec(),
body[..9].to_vec(),
body[..body.len() - 1].to_vec(),
[&body[..], &[0x00][..]].concat(),
[&body[..6], &[0x29, 7, 0x1A, 0x02, 0x2B][..]].concat(),
[&body[..11], &[0x39, 1][..]].concat(),
] {
assert!(
PrivateTransferError::decode(&malformed).is_err(),
"{malformed:02x?}"
);
}
let mut pdu = sample(None).to_error_pdu(1);
pdu.error_class = ErrorClass::DEVICE;
assert!(PrivateTransferError::try_from(&pdu).is_err());
let mut pdu = sample(None).to_error_pdu(1);
pdu.error_data = Bytes::new();
assert!(PrivateTransferError::try_from(&pdu).is_err());
let mut pdu = sample(None).to_error_pdu(1);
pdu.service_choice = ConfirmedServiceChoice::CONFIRMED_TEXT_MESSAGE;
assert!(PrivateTransferError::try_from(&pdu).is_err());
}
}