use super::*;
use bacnet_types::constructed::{
BACnetDeviceObjectPropertyReference, BACnetEventParameter, BACnetExtendedPropertyState,
BACnetPropertyStates, BACnetProprietaryPropertyState,
};
use bacnet_types::enums::ObjectType;
use bacnet_types::primitives::ObjectIdentifier;
mod access_credential;
mod access_rule;
mod action_list;
mod assigned_landing_calls;
mod audit_notification;
mod authentication_factor_format;
mod authentication_policy;
mod calendar;
mod channel_value;
mod color_command;
mod cov_subscription;
mod event_log_record;
mod event_notification;
mod event_notification_subscription;
mod event_parameter;
mod fault_parameter;
mod landing_call_status;
mod landing_door_status;
mod lift_car_call_list;
mod lighting_command;
mod log_multiple_record;
mod log_record;
mod name_value;
mod network_port;
mod object_identifier_invariant;
mod port_permission;
mod property_access_result;
mod property_value;
mod read_access;
mod recipient;
mod scale;
mod schedule;
mod shed_level;
mod staging;
mod tagged;
mod value_source;
pub(crate) fn assert_members_cut_short<T: std::fmt::Debug>(
what: &str,
wire: &[u8],
decode: impl Fn(&[u8]) -> Result<T, Error>,
) -> usize {
if let Err(error) = decode(wire) {
panic!("{what}: the whole value fails: {error}");
}
let mut pos = 0;
let mut framed = 0;
let mut depth = 0usize;
while pos < wire.len() {
let (tag, start) = tags::decode_tag(wire, pos).unwrap();
if tag.is_opening || tag.is_closing {
depth = if tag.is_opening { depth + 1 } else { depth - 1 };
pos = start;
continue;
}
if tag.class == TagClass::Application && tag.number == tags::app_tag::BOOLEAN {
pos = start;
continue;
}
let end = start + tag.length as usize;
for cut in start..end {
match decode(&wire[..cut]) {
Err(Error::BufferTooShort { need, have }) => {
assert_eq!((need, have), (end, cut), "{what} cut at {cut}");
}
other => panic!("{what} cut at {cut}, in the member at {pos}: {other:?}"),
}
}
if depth > 0 && end > start {
framed += 1;
}
pos = end;
}
framed
}
pub(crate) fn dopr_ai(instance: u32, property: u32) -> BACnetDeviceObjectPropertyReference {
BACnetDeviceObjectPropertyReference {
object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, instance).unwrap(),
property_identifier: property,
property_array_index: None,
device_identifier: None,
}
}
fn modeled_property_states() -> Vec<(u8, BACnetPropertyStates)> {
use BACnetPropertyStates as S;
vec![
(0, S::BooleanValue(true)),
(1, S::BinaryValue(1)),
(2, S::EventType(1)),
(3, S::Polarity(1)),
(4, S::ProgramChange(1)),
(5, S::ProgramState(1)),
(6, S::ReasonForHalt(1)),
(7, S::Reliability(1)),
(8, S::State(1)),
(9, S::SystemStatus(1)),
(10, S::Units(1)),
(11, S::UnsignedValue(1)),
(12, S::LifeSafetyMode(1)),
(13, S::LifeSafetyState(1)),
(14, S::RestartReason(1)),
(15, S::DoorAlarmState(1)),
(16, S::Action(1)),
(17, S::DoorSecuredStatus(1)),
(18, S::DoorStatus(1)),
(19, S::DoorValue(1)),
(20, S::FileAccessMethod(1)),
(21, S::LockStatus(1)),
(22, S::LifeSafetyOperation(1)),
(23, S::Maintenance(1)),
(24, S::NodeType(1)),
(25, S::NotifyType(1)),
(27, S::ShedState(1)),
(28, S::SilencedState(1)),
(30, S::AccessEvent(1)),
(31, S::ZoneOccupancyState(1)),
(32, S::AccessCredentialDisableReason(1)),
(33, S::AccessCredentialDisable(1)),
(34, S::AuthenticationStatus(1)),
(36, S::BackupState(1)),
(37, S::WriteStatus(1)),
(38, S::LightingInProgress(1)),
(39, S::LightingOperation(1)),
(40, S::LightingTransition(1)),
(41, S::IntegerValue(-1)),
(42, S::BinaryLightingValue(1)),
(43, S::TimerState(1)),
(44, S::TimerTransition(1)),
(45, S::BacnetIpMode(1)),
(46, S::NetworkPortCommand(1)),
(47, S::NetworkType(1)),
(48, S::NetworkNumberQuality(1)),
(49, S::EscalatorOperationDirection(1)),
(50, S::EscalatorFault(1)),
(51, S::EscalatorMode(1)),
(52, S::LiftCarDirection(1)),
(53, S::LiftCarDoorCommand(1)),
(54, S::LiftCarDriveStatus(1)),
(55, S::LiftCarMode(1)),
(56, S::LiftGroupMode(1)),
(57, S::LiftFault(1)),
(58, S::ProtocolLevel(1)),
(59, S::AuditLevel(1)),
(60, S::AuditOperation(1)),
(
63,
S::ExtendedValue(BACnetExtendedPropertyState::new(255, 1).unwrap()),
),
]
}
fn raw_property_state(tag: u8, content: &[u8]) -> BytesMut {
let mut buf = BytesMut::new();
tags::encode_tag(&mut buf, tag, TagClass::Context, content.len() as u32);
buf.extend_from_slice(content);
buf
}
#[test]
fn property_state_clause_21_tag_table_and_round_trips() {
for (tag, state) in modeled_property_states() {
let mut encoded = BytesMut::new();
encode_property_state(&mut encoded, &state).unwrap();
let expected = if let BACnetPropertyStates::ExtendedValue(value) = state {
let mut expected = vec![0xfc, 63];
expected.extend_from_slice(&value.encoded().to_be_bytes());
expected
} else {
let content = if matches!(state, BACnetPropertyStates::IntegerValue(_)) {
0xff
} else {
1
};
if tag <= 14 {
vec![(tag << 4) | 0x09, content]
} else {
vec![0xf9, tag, content]
}
};
assert_eq!(encoded.as_ref(), expected, "context tag {tag}");
let (decoded, end) = decode_property_state(&encoded, 0).unwrap();
assert_eq!(decoded, state, "context tag {tag}");
assert_eq!(end, encoded.len());
}
}
#[test]
fn property_state_unsigned_alternatives_enforce_u32() {
for (tag, state) in modeled_property_states() {
if matches!(
state,
BACnetPropertyStates::BooleanValue(_) | BACnetPropertyStates::IntegerValue(_)
) {
continue;
}
assert!(decode_property_state(&raw_property_state(tag, &[1, 0, 0, 0, 0]), 0).is_err());
assert!(decode_property_state(&raw_property_state(tag, &[0xff; 8]), 0).is_err());
let (decoded, _) =
decode_property_state(&raw_property_state(tag, &[0, 0xff, 0xff, 0xff, 0xff]), 0)
.unwrap();
let expected = if tag == 63 {
Some(u32::MAX % 100_000)
} else {
Some(u32::MAX)
};
assert_eq!(decoded.as_u32(), expected, "context tag {tag}");
}
}
#[test]
fn property_state_validates_boolean_integer_and_tag_forms() {
assert_eq!(
decode_property_state(&raw_property_state(0, &[0]), 0)
.unwrap()
.0,
BACnetPropertyStates::BooleanValue(false)
);
for content in [&[][..], &[2], &[0, 0]] {
assert!(decode_property_state(&raw_property_state(0, content), 0).is_err());
}
for value in [i32::MIN, -1, 0, i32::MAX] {
let mut encoded = BytesMut::new();
encode_property_state(&mut encoded, &BACnetPropertyStates::IntegerValue(value)).unwrap();
assert_eq!(
decode_property_state(&encoded, 0).unwrap().0,
BACnetPropertyStates::IntegerValue(value)
);
}
assert!(decode_property_state(&raw_property_state(41, &[0; 5]), 0).is_err());
for content in [&[0x00, 0x01][..], &[0xFF, 0x80]] {
assert!(decode_property_state(&raw_property_state(41, content), 0).is_err());
}
let mut application = BytesMut::new();
primitives::encode_app_unsigned(&mut application, 1);
assert!(decode_property_state(&application, 0).is_err());
let mut opening = BytesMut::new();
tags::encode_opening_tag(&mut opening, 0);
assert!(decode_property_state(&opening, 0).is_err());
let mut closing = BytesMut::new();
tags::encode_closing_tag(&mut closing, 0);
assert!(decode_property_state(&closing, 0).is_err());
assert!(decode_property_state(&[0x1a], 0).is_err());
assert!(decode_property_state(&[0xf9], 0).is_err());
assert!(decode_property_state(&[0xf9, 0xff, 0], 0).is_err());
assert_eq!(BACnetPropertyStates::BooleanValue(true).as_u32(), Some(1));
assert_eq!(BACnetPropertyStates::IntegerValue(7).as_u32(), Some(7));
assert_eq!(BACnetPropertyStates::IntegerValue(-1).as_u32(), None);
}
#[test]
fn property_state_rejects_reserved_tags_and_preserves_proprietary_tags() {
for tag in [26, 29, 35, 61, 62] {
assert!(decode_property_state(&raw_property_state(tag, &[0]), 0).is_err());
}
for tag in [64, 254] {
let expected = BACnetPropertyStates::Other(
BACnetProprietaryPropertyState::primitive(tag, vec![0xde, 0xad]).unwrap(),
);
let (decoded, _) =
decode_property_state(&raw_property_state(tag, &[0xde, 0xad]), 0).unwrap();
assert_eq!(decoded, expected);
let mut encoded = BytesMut::new();
encode_property_state(&mut encoded, &expected).unwrap();
assert_eq!(decode_property_state(&encoded, 0).unwrap().0, expected);
}
let expected = BACnetPropertyStates::Other(
BACnetProprietaryPropertyState::constructed(64, vec![0x21, 0x07]).unwrap(),
);
let mut encoded = BytesMut::new();
encode_property_state(&mut encoded, &expected).unwrap();
assert_eq!(encoded.as_ref(), &[0xfe, 64, 0x21, 0x07, 0xff, 64]);
assert_eq!(decode_property_state(&encoded, 0).unwrap().0, expected);
for tag in [0, 26, 63, 255] {
assert!(BACnetProprietaryPropertyState::primitive(tag, vec![]).is_err());
assert!(BACnetProprietaryPropertyState::constructed(tag, vec![]).is_err());
}
assert!(BACnetExtendedPropertyState::new(254, 0).is_err());
assert!(BACnetExtendedPropertyState::new(255, 100_000).is_err());
assert!(BACnetExtendedPropertyState::new(u32::MAX, 0).is_err());
assert!(decode_property_state(&raw_property_state(63, &[1]), 0).is_err());
let extended = BACnetExtendedPropertyState::new(256, 7).unwrap();
assert_eq!(extended.encoded(), 25_600_007);
assert_eq!(
BACnetExtendedPropertyState::from_encoded(extended.encoded()).unwrap(),
extended
);
let malformed = BACnetPropertyStates::Other(
BACnetProprietaryPropertyState::constructed(64, vec![0xde]).unwrap(),
);
let mut untouched = BytesMut::from(&[0xaa][..]);
assert!(encode_property_state(&mut untouched, &malformed).is_err());
assert_eq!(untouched.as_ref(), &[0xaa]);
let mismatched = BACnetPropertyStates::Other(
BACnetProprietaryPropertyState::constructed(64, vec![0x1e, 0x2f]).unwrap(),
);
assert!(encode_property_state(&mut BytesMut::new(), &mismatched).is_err());
assert!(decode_property_state(&[0xfe, 64, 0x1e, 0x2f, 0xff, 64], 0).is_err());
for body in [
vec![0x01, 0x00],
vec![0x12],
vec![0x0e, 0x01, 0x00, 0x0f],
vec![0x1e, 0x12, 0x1f],
] {
let malformed = BACnetPropertyStates::Other(
BACnetProprietaryPropertyState::constructed(64, body.clone()).unwrap(),
);
let mut untouched = BytesMut::from(&[0xaa][..]);
assert!(encode_property_state(&mut untouched, &malformed).is_err());
assert_eq!(untouched.as_ref(), &[0xaa]);
let mut framed = vec![0xfe, 64];
framed.extend_from_slice(&body);
framed.extend_from_slice(&[0xff, 64]);
assert!(decode_property_state(&framed, 0).is_err());
}
}
#[test]
fn dopr_body_round_trip_local_and_full() {
let local = dopr_ai(5, 85);
let mut buf = BytesMut::new();
encode_dopr_body(&mut buf, &local);
let (decoded, end) = decode_dopr_body(&buf, 0, "test").unwrap();
assert_eq!(decoded, local);
assert_eq!(end, buf.len());
let full = BACnetDeviceObjectPropertyReference {
object_identifier: ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 7).unwrap(),
property_identifier: 111,
property_array_index: Some(3),
device_identifier: Some(ObjectIdentifier::new(ObjectType::DEVICE, 8).unwrap()),
};
let mut buf = BytesMut::new();
encode_dopr_body(&mut buf, &full);
let (decoded, _) = decode_dopr_body(&buf, 0, "test").unwrap();
assert_eq!(decoded, full);
}
#[test]
fn device_object_property_references_decode_one_list_element_at_a_time() {
let mut indexed = dopr_ai(5, 85);
indexed.property_array_index = Some(2);
let full = BACnetDeviceObjectPropertyReference {
device_identifier: Some(ObjectIdentifier::new(ObjectType::DEVICE, 8).unwrap()),
..dopr_ai(7, 111)
};
let mut list = BytesMut::new();
for reference in [&indexed, &full] {
encode_device_object_property_reference(&mut list, reference);
}
let (first, next) = decode_device_object_property_reference(&list, 0).unwrap();
assert_eq!(first, indexed);
let (second, end) = decode_device_object_property_reference(&list, next).unwrap();
assert_eq!(second, full);
assert_eq!(end, list.len());
assert!(decode_device_object_property_reference(&list[..next - 1], 0).is_err());
}
#[test]
fn event_parameter_opaque_primitive_form_decodes() {
let data = [0xF8, 20];
let (decoded, end) = decode_event_parameter(&data, 0).unwrap();
assert_eq!(
decoded,
BACnetEventParameter::Opaque {
tag: 20,
data: Vec::new()
}
);
assert_eq!(end, data.len());
let mut encoded = BytesMut::new();
encode_event_parameter(&mut encoded, &decoded).unwrap();
assert_eq!(encoded.as_ref(), data);
}