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(AccessEventReport {
time: Some(stamp(9)),
credential: Some(credential()),
..AccessEventReport::new(AccessEvent::GRANTED, tag)
})
.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_counts_sequence_numbers() {
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(1), true)
);
write_out_of_service(&mut point, PropertyValue::Boolean(false)).unwrap();
assert_eq!(
served(&point),
event(
AccessEvent::OUT_OF_SERVICE_RELINQUISHED,
9,
sequence(2),
false
)
);
}
#[test]
fn access_point_sequence_number_wraps_back_to_one() {
let mut point = point();
point.bind_clock_internal(None);
point
.set_access_event(AccessEventReport {
time: Some(BACnetTimeStamp::SequenceNumber(65_534)),
..AccessEventReport::new(AccessEvent::GRANTED, 3)
})
.unwrap();
write_out_of_service(&mut point, PropertyValue::Boolean(true)).unwrap();
assert_eq!(
served(&point),
event(AccessEvent::OUT_OF_SERVICE, 4, sequence(65_535), true)
);
write_out_of_service(&mut point, PropertyValue::Boolean(false)).unwrap();
assert_eq!(
served(&point),
event(
AccessEvent::OUT_OF_SERVICE_RELINQUISHED,
5,
sequence(1),
false
)
);
}
#[test]
fn access_point_events_of_one_transaction_each_move_the_time() {
let read_then_grant = |point: &mut AccessPointObject| {
let mut times = Vec::new();
for event in [
AccessEvent::AUTHENTICATION_FACTOR_READ,
AccessEvent::GRANTED,
] {
point
.set_access_event(AccessEventReport::new(event, 20))
.unwrap();
times.push(point.read_property(P::ACCESS_EVENT_TIME, None).unwrap());
}
times
};
let mut clockless = point();
clockless.bind_clock_internal(None);
assert_eq!(read_then_grant(&mut clockless), [sequence(1), sequence(2)]);
let mut clocked = point();
let later = |hundredths: u8| {
PropertyValue::ApplicationData(vec![
0x2E, 0xA4, 126, 10, 2, 5, 0xB4, 11, 30, 0, hundredths, 0x2F,
])
};
assert_eq!(read_then_grant(&mut clocked), [stamped(11), later(1)]);
clocked.bind_clock_internal(Some(Arc::new(FixedClock(10))));
clocked
.set_access_event(AccessEventReport::new(AccessEvent::GRANTED, 21))
.unwrap();
assert_eq!(
clocked.read_property(P::ACCESS_EVENT_TIME, None).unwrap(),
later(2)
);
}
#[test]
fn access_point_stamp_after_the_last_hundredth_of_a_day_carries_into_the_next() {
use bacnet_types::primitives::{Date, Time};
let mut point = point();
let at = |year: u8, month: u8, day: u8, day_of_week: u8, hour: u8| BACnetTimeStamp::DateTime {
date: Date {
year,
month,
day,
day_of_week,
},
time: Time {
hour,
minute: 59,
second: 59,
hundredths: 99,
},
};
let midnight = |year: u8, month: u8, day: u8, day_of_week: u8| {
PropertyValue::ApplicationData(vec![
0x2E,
0xA4,
year,
month,
day,
day_of_week,
0xB4,
0,
0,
0,
0,
0x2F,
])
};
for (served, expected) in [
(at(126, 10, 2, 5, 22), {
PropertyValue::ApplicationData(vec![0x2E, 0xA4, 126, 10, 2, 5, 0xB4, 23, 0, 0, 0, 0x2F])
}),
(at(126, 10, 2, 5, 23), midnight(126, 10, 3, 6)),
(at(126, 10, 31, 6, 23), midnight(126, 11, 1, 7)),
(at(126, 12, 31, 4, 23), midnight(127, 1, 1, 5)),
] {
point
.set_access_event(AccessEventReport {
time: Some(served),
..AccessEventReport::new(AccessEvent::GRANTED, 1)
})
.unwrap();
point
.set_access_event(AccessEventReport::new(AccessEvent::GRANTED, 1))
.unwrap();
assert_eq!(
point.read_property(P::ACCESS_EVENT_TIME, None).unwrap(),
expected
);
}
}
#[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)
);
}