use std::sync::Arc;
use bacnet_types::enums::{ErrorCode, PropertyIdentifier as P};
use super::credential_data_input_out_of_service_tests::{
assert_property_error, sequence, stamp, stamped, FixedClock,
};
use super::*;
pub(super) fn credential() -> BACnetDeviceObjectReference {
ObjectIdentifier::new(ObjectType::ACCESS_CREDENTIAL, 3)
.unwrap()
.into()
}
pub(super) const CREDENTIAL_3: [u8; 5] = [0x1C, 0x08, 0x00, 0x00, 0x03];
pub(super) const NO_CREDENTIAL: [u8; 5] = [0x1C, 0x08, 0x3F, 0xFF, 0xFF];
fn point() -> AccessPointObject {
let mut point = AccessPointObject::new(1, "AP-1").unwrap();
granted_at(&mut point, 7);
point.bind_clock_internal(Some(Arc::new(FixedClock(11))));
point
}
fn granted_at(point: &mut AccessPointObject, tag: u64) {
point
.set_access_event(AccessEvent::GRANTED, tag, stamp(9), Some(credential()))
.unwrap();
}
fn write_out_of_service(point: &mut AccessPointObject, value: PropertyValue) -> Result<(), Error> {
point.write_property(P::OUT_OF_SERVICE, None, value, None)
}
fn served(point: &AccessPointObject) -> [PropertyValue; 5] {
[
P::ACCESS_EVENT,
P::ACCESS_EVENT_TAG,
P::ACCESS_EVENT_TIME,
P::ACCESS_EVENT_CREDENTIAL,
P::STATUS_FLAGS,
]
.map(|property| point.read_property(property, None).unwrap())
}
fn event(
event: AccessEvent,
tag: u64,
time: PropertyValue,
out_of_service: bool,
) -> [PropertyValue; 5] {
let mut flags = StatusFlags::empty();
flags.set(StatusFlags::OUT_OF_SERVICE, out_of_service);
[
PropertyValue::Enumerated(event.to_raw()),
PropertyValue::Unsigned(tag),
time,
PropertyValue::ApplicationData(NO_CREDENTIAL.to_vec()),
PropertyValue::BitString {
unused_bits: 4,
data: vec![flags.bits() << 4],
},
]
}
fn granted(out_of_service: bool) -> [PropertyValue; 5] {
let mut granted = event(AccessEvent::GRANTED, 7, stamped(9), out_of_service);
granted[3] = PropertyValue::ApplicationData(CREDENTIAL_3.to_vec());
granted
}
#[test]
fn access_point_entering_out_of_service_records_out_of_service() {
let mut point = point();
assert_eq!(served(&point), granted(false));
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
assert_eq!(
served(&point),
event(AccessEvent::OUT_OF_SERVICE, 8, stamped(11), true)
);
}
#[test]
fn access_point_return_to_service_records_out_of_service_relinquished() {
let mut point = point();
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
point.bind_clock_internal(Some(Arc::new(FixedClock(12))));
write_out_of_service(&mut point, PropertyValue::Boolean(false)).unwrap();
assert_eq!(
served(&point),
event(
AccessEvent::OUT_OF_SERVICE_RELINQUISHED,
9,
stamped(12),
false
)
);
point.bind_clock_internal(Some(Arc::new(FixedClock(13))));
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
assert_eq!(
served(&point),
event(AccessEvent::OUT_OF_SERVICE, 10, stamped(13), true)
);
}
#[test]
fn access_point_writes_that_keep_out_of_service_record_nothing() {
let mut point = point();
write_out_of_service(&mut point, PropertyValue::Boolean(false)).unwrap();
write_out_of_service(&mut point, PropertyValue::Null).unwrap();
assert_property_error(
write_out_of_service(&mut point, PropertyValue::Unsigned(1)),
ErrorCode::INVALID_DATA_TYPE,
);
assert_eq!(served(&point), granted(false));
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
let entered = served(&point);
point.bind_clock_internal(Some(Arc::new(FixedClock(12))));
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
write_out_of_service(&mut point, PropertyValue::Null).unwrap();
assert_eq!(served(&point), entered);
}
#[test]
fn access_point_out_of_service_event_without_a_clock_stamps_the_tag_as_a_sequence_number() {
let mut point = point();
point.bind_clock_internal(None);
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
assert_eq!(
served(&point),
event(AccessEvent::OUT_OF_SERVICE, 8, sequence(8), true)
);
write_out_of_service(&mut point, PropertyValue::Boolean(false)).unwrap();
assert_eq!(
served(&point),
event(
AccessEvent::OUT_OF_SERVICE_RELINQUISHED,
9,
sequence(9),
false
)
);
}
#[test]
fn access_point_sequence_number_folds_a_tag_past_its_range() {
let mut point = point();
point.bind_clock_internal(None);
granted_at(&mut point, 65_534);
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
assert_eq!(
served(&point),
event(AccessEvent::OUT_OF_SERVICE, 65_535, sequence(65_535), true)
);
write_out_of_service(&mut point, PropertyValue::Boolean(false)).unwrap();
assert_eq!(
served(&point),
event(
AccessEvent::OUT_OF_SERVICE_RELINQUISHED,
65_536,
sequence(1),
false
)
);
granted_at(&mut point, u64::MAX);
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
assert_eq!(
served(&point),
event(AccessEvent::OUT_OF_SERVICE, 0, sequence(1), true)
);
}
#[test]
fn access_point_out_of_service_event_tag_wraps_at_the_top_of_its_range() {
let mut point = point();
granted_at(&mut point, u64::MAX);
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
assert_eq!(
served(&point),
event(AccessEvent::OUT_OF_SERVICE, 0, stamped(11), true)
);
}