use bacnet_types::enums::RejectReason;
use bacnet_types::error::Error;
use crate::alarm_event::{
AcknowledgeAlarmRequest, GetEventInformationAck, GetEventInformationRequest,
};
use crate::audit::{AuditLogQueryAck, AuditLogQueryRequest};
use crate::cov::{COVNotificationRequest, SubscribeCOVPropertyRequest, SubscribeCOVRequest};
use crate::cov_multiple::{COVNotificationMultipleRequest, SubscribeCOVPropertyMultipleRequest};
use crate::life_safety::LifeSafetyOperationRequest;
use crate::list_manipulation::ChangeListError;
use crate::object_mgmt::CreateObjectError;
use crate::private_transfer::PrivateTransferError;
use crate::read_property::{ReadPropertyACK, ReadPropertyRequest};
use crate::read_range::{ReadRangeAck, ReadRangeRequest};
use crate::virtual_terminal::{VTCloseRequest, VTDataRequest, VTOpenAck, VTOpenRequest};
use crate::who_am_i::{WhoAmIRequest, YouAreRequest};
use crate::write_group::WriteGroupRequest;
use crate::write_property::WritePropertyRequest;
type Decode = fn(&[u8]) -> Result<(), Error>;
macro_rules! decoder {
($ty:ty) => {
(
stringify!($ty),
(|d: &[u8]| <$ty>::decode(d).map(drop)) as Decode,
)
};
}
const PROCESS_CUT: [u8; 2] = [0x0A, 0x01];
const OBJECT_CUT: [u8; 3] = [0x0C, 0x00, 0x80];
const PROPERTY_CUT: [u8; 7] = [0x0C, 0x00, 0x80, 0x00, 0x01, 0x1A, 0x55];
const ERROR_THEN_CUT: [u8; 8] = [0x0E, 0x91, 0x02, 0x91, 0x00, 0x0F, 0x1A, 0x01];
const APP_UNSIGNED_CUT: [u8; 2] = [0x22, 0x01];
#[test]
fn members_cut_short_are_a_short_buffer() {
let cases: [((&str, Decode), &[u8]); 26] = [
(decoder!(SubscribeCOVRequest), &PROCESS_CUT),
(decoder!(SubscribeCOVPropertyRequest), &PROCESS_CUT),
(decoder!(SubscribeCOVPropertyMultipleRequest), &PROCESS_CUT),
(decoder!(COVNotificationRequest), &PROCESS_CUT),
(decoder!(COVNotificationMultipleRequest), &PROCESS_CUT),
(decoder!(AcknowledgeAlarmRequest), &PROCESS_CUT),
(decoder!(LifeSafetyOperationRequest), &PROCESS_CUT),
(decoder!(WriteGroupRequest), &PROCESS_CUT),
(decoder!(GetEventInformationRequest), &OBJECT_CUT),
(decoder!(GetEventInformationAck), &[0x0E, 0x0C, 0x00]),
(decoder!(AuditLogQueryRequest), &OBJECT_CUT),
(decoder!(AuditLogQueryAck), &OBJECT_CUT),
(decoder!(ReadRangeRequest), &OBJECT_CUT),
(decoder!(ReadRangeAck), &OBJECT_CUT),
(decoder!(ReadPropertyRequest), &PROPERTY_CUT),
(decoder!(ReadPropertyACK), &PROPERTY_CUT),
(decoder!(WritePropertyRequest), &PROPERTY_CUT),
(decoder!(CreateObjectError), &ERROR_THEN_CUT),
(decoder!(ChangeListError), &ERROR_THEN_CUT),
(decoder!(PrivateTransferError), &ERROR_THEN_CUT),
(decoder!(VTOpenRequest), &[0x92, 0x00]),
(decoder!(VTOpenAck), &APP_UNSIGNED_CUT),
(decoder!(VTCloseRequest), &APP_UNSIGNED_CUT),
(decoder!(VTDataRequest), &APP_UNSIGNED_CUT),
(decoder!(WhoAmIRequest), &APP_UNSIGNED_CUT),
(decoder!(YouAreRequest), &APP_UNSIGNED_CUT),
];
for ((name, decode), data) in cases {
let result = decode(data);
assert!(
matches!(result, Err(Error::BufferTooShort { .. })),
"{name} {data:02X?}: {result:?}"
);
}
}
#[test]
fn fixed_size_members_of_the_wrong_length_stay_malformed_when_cut_short() {
let object = [0x09, 0x01, 0x1B, 0x00];
let boolean = [0x09, 0x01, 0x1C, 0x00, 0x80, 0x00, 0x01, 0x2A, 0x01];
for data in [&object[..], &boolean] {
let result = SubscribeCOVRequest::decode(data);
assert!(
matches!(result, Err(Error::Decoding(_))),
"{data:02X?}: {result:?}"
);
}
let result = SubscribeCOVRequest::decode(&[0x09, 0x01, 0x1C, 0x00]);
assert!(
matches!(result, Err(Error::BufferTooShort { .. })),
"{result:?}"
);
}
fn wrong_and_right_length_cut(data: &[u8], tag: u8) -> [Vec<u8>; 2] {
let header = tag << 4 | 0x08;
[
[data, &[header | 0x05, 0x05, 0x42, 0x90, 0x00]].concat(),
[data, &[header | 0x04, 0x42, 0x90, 0x00]].concat(),
]
}
#[test]
fn reals_and_times_check_their_length_before_their_contents() {
let property = [
0x09, 0x01, 0x1C, 0x00, 0x80, 0x00, 0x01, 0x4E, 0x09, 0x55, 0x4F,
];
let [wrong, cut] = wrong_and_right_length_cut(&property, 5);
let wrong = SubscribeCOVPropertyRequest::decode(&wrong);
assert!(matches!(wrong, Err(Error::Decoding(_))), "{wrong:?}");
let cut = SubscribeCOVPropertyRequest::decode(&cut);
assert!(matches!(cut, Err(Error::BufferTooShort { .. })), "{cut:?}");
let multiple = [
0x09, 0x01, 0x19, 0x00, 0x4E, 0x0C, 0x00, 0x80, 0x00, 0x01, 0x1E, 0x0E, 0x09, 0x55, 0x0F,
];
let [wrong, cut] = wrong_and_right_length_cut(&multiple, 1);
let wrong = SubscribeCOVPropertyMultipleRequest::decode(&wrong);
assert!(matches!(wrong, Err(Error::Decoding(_))), "{wrong:?}");
let cut = SubscribeCOVPropertyMultipleRequest::decode(&cut);
assert!(matches!(cut, Err(Error::BufferTooShort { .. })), "{cut:?}");
let notification = [
0x09, 0x01, 0x1C, 0x02, 0x00, 0x00, 0x01, 0x29, 0x00, 0x4E, 0x0C, 0x00, 0x80, 0x00, 0x01,
0x1E, 0x09, 0x55, 0x2E, 0x44, 0x00, 0x00, 0x00, 0x00, 0x2F,
];
let [wrong, cut] = wrong_and_right_length_cut(¬ification, 3);
let wrong = COVNotificationMultipleRequest::decode(&wrong);
assert!(
matches!(wrong, Err(Error::Reject { reason })
if reason == RejectReason::INVALID_DATA_ENCODING),
"{wrong:?}"
);
let cut = COVNotificationMultipleRequest::decode(&cut);
assert!(matches!(cut, Err(Error::BufferTooShort { .. })), "{cut:?}");
}
#[test]
fn confirmed_cov_notification_rejects_keep_their_reasons() {
let reason = |data: &[u8]| {
let error = COVNotificationRequest::decode_detailed(data).unwrap_err();
let reason = error.reject_reason();
(reason, error.into_error())
};
let (cut, error) = reason(&PROCESS_CUT);
assert_eq!(cut, RejectReason::MISSING_REQUIRED_PARAMETER);
assert!(matches!(error, Error::BufferTooShort { .. }), "{error:?}");
for data in [&[][..], &[0x0D], &[0x19, 0x01]] {
let (missing, error) = reason(data);
assert_eq!(
missing,
RejectReason::MISSING_REQUIRED_PARAMETER,
"{data:02X?}"
);
assert!(matches!(error, Error::Decoding(_)), "{error:?}");
}
let (wrong, error) = reason(&[0x21, 0x01]);
assert_eq!(wrong, RejectReason::INVALID_TAG);
assert!(matches!(error, Error::Decoding(_)), "{error:?}");
let padded = [0x0A, 0x00, 0x01];
assert_eq!(reason(&padded).0, RejectReason::INVALID_DATA_ENCODING);
let wide = [0x0D, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00];
assert_eq!(reason(&wide).0, RejectReason::PARAMETER_OUT_OF_RANGE);
}