use bacnet_types::constructed::BACnetDeviceObjectReference;
use bacnet_types::enums::{ErrorClass, ErrorCode, PropertyIdentifier as P};
use super::*;
fn oid(object_type: ObjectType, instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(object_type, instance).unwrap()
}
fn remote(object_type: ObjectType, instance: u32) -> BACnetDeviceObjectReference {
BACnetDeviceObjectReference {
device_identifier: Some(oid(ObjectType::DEVICE, 9)),
object_identifier: oid(object_type, instance),
}
}
fn read(user: &AccessUserObject, property: P) -> PropertyValue {
user.read_property(property, None).unwrap()
}
fn assert_value_out_of_range(result: Result<(), Error>, context: &str) {
assert!(
matches!(result, Err(Error::Protocol { class, code })
if class == ErrorClass::PROPERTY.to_raw() as u32
&& code == ErrorCode::VALUE_OUT_OF_RANGE.to_raw() as u32),
"{context}: expected PROPERTY / VALUE_OUT_OF_RANGE, got {result:?}"
);
}
const LISTS: [P; 3] = [P::CREDENTIALS, P::MEMBERS, P::MEMBER_OF];
#[test]
fn access_user_lists_serve_device_object_references() {
let mut user = AccessUserObject::new(1, "USER-1").unwrap();
user.set_credentials([
oid(ObjectType::ACCESS_CREDENTIAL, 1).into(),
remote(ObjectType::ACCESS_CREDENTIAL, 4),
])
.unwrap();
user.set_members([oid(ObjectType::ACCESS_USER, 2)]).unwrap();
user.set_member_of([remote(ObjectType::ACCESS_USER, 5)])
.unwrap();
let device_9 = [0x0C, 0x02, 0x00, 0x00, 0x09];
assert_eq!(
read(&user, P::CREDENTIALS),
PropertyValue::List(vec![
PropertyValue::ApplicationData(vec![0x1C, 0x08, 0x00, 0x00, 0x01]),
PropertyValue::ApplicationData(
[&device_9[..], &[0x1C, 0x08, 0x00, 0x00, 0x04]].concat()
),
])
);
assert_eq!(
read(&user, P::MEMBERS),
PropertyValue::List(vec![PropertyValue::ApplicationData(vec![
0x1C, 0x08, 0xC0, 0x00, 0x02,
])])
);
assert_eq!(
read(&user, P::MEMBER_OF),
PropertyValue::List(vec![PropertyValue::ApplicationData(
[&device_9[..], &[0x1C, 0x08, 0xC0, 0x00, 0x05]].concat()
)])
);
for property in LISTS {
assert!(user.is_list_property(property));
assert!(!user.is_array_property(property));
assert!(!user.is_writable_property(property));
let served = read(&user, property);
let result = user.write_property(property, None, served.clone(), None);
assert!(
matches!(result, Err(Error::Protocol { code, .. })
if code == ErrorCode::WRITE_ACCESS_DENIED.to_raw() as u32),
"{property:?}: {result:?}"
);
assert_eq!(read(&user, property), served);
}
user.set_credentials(Vec::<BACnetDeviceObjectReference>::new())
.unwrap();
user.set_members(Vec::<BACnetDeviceObjectReference>::new())
.unwrap();
user.set_member_of(Vec::<BACnetDeviceObjectReference>::new())
.unwrap();
for property in LISTS {
assert_eq!(read(&user, property), PropertyValue::List(vec![]));
}
}
#[test]
fn access_user_setters_refuse_other_objects_and_non_device_devices() {
type Setter = fn(&mut AccessUserObject, Vec<BACnetDeviceObjectReference>) -> Result<(), Error>;
let setters: [(&str, ObjectType, ObjectType, Setter); 3] = [
(
"set_credentials",
ObjectType::ACCESS_CREDENTIAL,
ObjectType::ACCESS_USER,
|user, list| user.set_credentials(list),
),
(
"set_members",
ObjectType::ACCESS_USER,
ObjectType::ACCESS_CREDENTIAL,
|user, list| user.set_members(list),
),
(
"set_member_of",
ObjectType::ACCESS_USER,
ObjectType::ACCESS_ZONE,
|user, list| user.set_member_of(list),
),
];
for (name, named, other, set) in setters {
let mut user = AccessUserObject::new(1, "USER-1").unwrap();
set(&mut user, vec![remote(named, 4)]).unwrap();
let kept = LISTS.map(|p| read(&user, p));
let wrong_type = [
oid(other, 1).into(),
remote(other, 2),
remote(ObjectType::ANALOG_VALUE, 3),
];
let not_a_device = [
oid(ObjectType::ANALOG_VALUE, 9),
oid(named, 9),
oid(ObjectType::ACCESS_ZONE, ObjectIdentifier::MAX_INSTANCE),
]
.map(|device| BACnetDeviceObjectReference {
device_identifier: Some(device),
object_identifier: oid(named, 1),
});
for bad in wrong_type.into_iter().chain(not_a_device) {
let list = vec![oid(named, 1).into(), bad.clone()];
assert_value_out_of_range(set(&mut user, list), &format!("{name}: {bad:?}"));
assert_eq!(LISTS.map(|p| read(&user, p)), kept, "{name}: {bad:?}");
}
}
}