use super::*;
use bacnet_types::constructed::{BACnetAccessRule, BACnetDeviceObjectReference};
use bacnet_types::enums::{AccessRuleLocationSpecifier, AccessRuleTimeRangeSpecifier};
fn oid(object_type: ObjectType, instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(object_type, instance).unwrap()
}
fn schedule_present_value() -> BACnetDeviceObjectPropertyReference {
BACnetDeviceObjectPropertyReference::new_local(oid(ObjectType::SCHEDULE, 1), 85)
}
fn round_trip(rule: &BACnetAccessRule, golden: &[u8]) {
let mut encoded = BytesMut::new();
encode_access_rule(&mut encoded, rule);
assert_eq!(&encoded[..], golden, "{rule:?}");
let (decoded, end) = decode_access_rule(golden, 0).unwrap();
assert_eq!(&decoded, rule);
assert_eq!(end, golden.len());
}
#[test]
fn access_rule_golden_bytes_and_round_trip() {
let local = BACnetAccessRule::new(
Some(schedule_present_value()),
Some(oid(ObjectType::ACCESS_POINT, 2).into()),
true,
);
round_trip(
&local,
&[
0x09, 0x00, 0x1E, 0x0C, 0x04, 0x40, 0x00, 0x01, 0x19, 0x55, 0x1F, 0x29, 0x00, 0x3E, 0x1C, 0x08, 0x40, 0x00, 0x02, 0x3F, 0x49, 0x01, ],
);
round_trip(
&BACnetAccessRule::new(None, None, false),
&[0x09, 0x01, 0x29, 0x01, 0x49, 0x00],
);
let remote = BACnetAccessRule::new(
Some(
BACnetDeviceObjectPropertyReference::new_remote(
oid(ObjectType::SCHEDULE, 1),
85,
oid(ObjectType::DEVICE, 99),
)
.with_index(3),
),
Some(BACnetDeviceObjectReference {
device_identifier: Some(oid(ObjectType::DEVICE, 99)),
object_identifier: oid(ObjectType::ACCESS_ZONE, 5),
}),
true,
);
round_trip(
&remote,
&[
0x09, 0x00, 0x1E, 0x0C, 0x04, 0x40, 0x00, 0x01, 0x19, 0x55, 0x29, 0x03, 0x3C, 0x02, 0x00, 0x00, 0x63, 0x1F, 0x29, 0x00, 0x3E, 0x0C, 0x02, 0x00, 0x00, 0x63, 0x1C, 0x09, 0x00, 0x00, 0x05, 0x3F, 0x49, 0x01,
],
);
}
#[test]
fn access_rule_decoder_keeps_what_the_object_judges() {
let unspecified = BACnetDeviceObjectPropertyReference::new_local(
oid(ObjectType::SCHEDULE, ObjectIdentifier::MAX_INSTANCE),
85,
);
let kept = BACnetAccessRule {
time_range_specifier: AccessRuleTimeRangeSpecifier::ALWAYS,
time_range: Some(unspecified),
location_specifier: AccessRuleLocationSpecifier::from_raw(7),
location: None,
enable: true,
};
let mut encoded = BytesMut::new();
encode_access_rule(&mut encoded, &kept);
let (decoded, end) = decode_access_rule(&encoded, 0).unwrap();
assert_eq!(decoded, kept);
assert_eq!(end, encoded.len());
let bare: &[u8] = &[0x09, 0x00, 0x29, 0x00, 0x49, 0x01];
let (decoded, _) = decode_access_rule(bare, 0).unwrap();
assert_eq!(
decoded.time_range_specifier,
AccessRuleTimeRangeSpecifier::SPECIFIED
);
assert_eq!((decoded.time_range, decoded.location), (None, None));
}
#[test]
fn access_rules_decode_one_element_at_a_time() {
let first = BACnetAccessRule::new(None, Some(oid(ObjectType::ACCESS_POINT, 2).into()), true);
let second = BACnetAccessRule::new(Some(schedule_present_value()), None, false);
let mut array = BytesMut::new();
encode_access_rule(&mut array, &first);
let boundary = array.len();
encode_access_rule(&mut array, &second);
let (decoded, end) = decode_access_rule(&array, 0).unwrap();
assert_eq!((decoded, end), (first, boundary));
let (decoded, end) = decode_access_rule(&array, boundary).unwrap();
assert_eq!((decoded, end), (second, array.len()));
}
#[test]
fn access_rule_rejects_malformed_elements() {
let malformed: [&[u8]; 13] = [
&[],
&[0x29, 0x01, 0x49, 0x01],
&[0x29, 0x01, 0x09, 0x01, 0x49, 0x01],
&[0x09, 0x01, 0x29, 0x01],
&[0x09, 0x01, 0x29, 0x01, 0x49, 0x02],
&[0x09, 0x01, 0x29, 0x01, 0x39, 0x01],
&[
0x0D, 0x05, 0x01, 0x00, 0x00, 0x00, 0x00, 0x29, 0x01, 0x49, 0x01,
],
&[
0x09, 0x00, 0x1E, 0x0C, 0x04, 0x40, 0x00, 0x01, 0x19, 0x55, 0x29, 0x01, 0x49, 0x01,
],
&[0x09, 0x00, 0x1E, 0x1F, 0x29, 0x01, 0x49, 0x01],
&[
0x09, 0x00, 0x1E, 0x1C, 0x04, 0x40, 0x00, 0x01, 0x1F, 0x29, 0x01, 0x49, 0x01,
],
&[
0x09, 0x01, 0x29, 0x00, 0x3E, 0x1C, 0x08, 0x40, 0x00, 0x02, 0x21, 0x01, 0x3F, 0x49,
0x01,
],
&[
0x09, 0x01, 0x29, 0x00, 0x1C, 0x08, 0x40, 0x00, 0x02, 0x49, 0x01,
],
&[0x09, 0x01, 0x29, 0x00, 0x3E, 0x1C, 0x08, 0x40],
];
for bytes in malformed {
assert!(
decode_access_rule(bytes, 0).is_err(),
"{bytes:02X?} must not decode"
);
}
let truncated: &[u8] = &[0x09, 0x01, 0x29, 0x00, 0x3E, 0x1C, 0x08, 0x40];
assert!(
matches!(
decode_access_rule(truncated, 0),
Err(Error::BufferTooShort { .. })
),
"{:?}",
decode_access_rule(truncated, 0)
);
let wrong_tag: &[u8] = &[0x09, 0x01, 0x29, 0x01, 0x39, 0x01];
assert!(
matches!(
decode_access_rule(wrong_tag, 0),
Err(Error::Decoding { .. })
),
"{:?}",
decode_access_rule(wrong_tag, 0)
);
}
#[test]
fn access_rule_all_keeps_an_unspecified_location() {
let rule = BACnetAccessRule {
time_range_specifier: AccessRuleTimeRangeSpecifier::ALWAYS,
time_range: None,
location_specifier: AccessRuleLocationSpecifier::ALL,
location: Some(BACnetDeviceObjectReference {
device_identifier: Some(oid(ObjectType::DEVICE, ObjectIdentifier::MAX_INSTANCE)),
object_identifier: oid(ObjectType::ACCESS_POINT, ObjectIdentifier::MAX_INSTANCE),
}),
enable: true,
};
round_trip(
&rule,
&[
0x09, 0x01, 0x29, 0x01, 0x3E, 0x0C, 0x02, 0x3F, 0xFF, 0xFF, 0x1C, 0x08, 0x7F, 0xFF, 0xFF, 0x3F, 0x49, 0x01, ],
);
}