use bacnet_types::error::RemoteError;
use std::sync::Arc;
use super::*;
use bacnet_objects::access_control::CredentialDataInputObject;
use bacnet_objects::clock::{ClockFrame, ClockReader};
use bacnet_services::common::BACnetPropertyValue;
use bacnet_services::wpm::{WriteAccessSpecification, WritePropertyMultipleRequest};
use bacnet_types::constructed::{BACnetAuthenticationFactor, BACnetAuthenticationFactorFormat};
use bacnet_types::enums::{AuthenticationFactorType, Reliability};
use bacnet_types::primitives::{BACnetTimeStamp, Date, Time};
const DATE: Date = Date {
year: 126,
month: 10,
day: 2,
day_of_week: 5,
};
struct FixedClock;
impl ClockReader for FixedClock {
fn read_clock(&self) -> Option<ClockFrame> {
Some(ClockFrame {
local_date: DATE,
local_time: Time {
hour: 11,
minute: 30,
second: 0,
hundredths: 0,
},
utc_offset: 0,
daylight_savings_status: false,
})
}
}
fn reader_db() -> (ObjectDatabase, ObjectIdentifier) {
let mut reader = CredentialDataInputObject::new(1, "CDI-1").unwrap();
reader
.set_supported_formats([
(
BACnetAuthenticationFactorFormat::standard(AuthenticationFactorType::WIEGAND26),
0,
),
(BACnetAuthenticationFactorFormat::custom(260, 7), 3),
])
.unwrap();
reader
.set_present_value(
BACnetAuthenticationFactor {
format_type: AuthenticationFactorType::WIEGAND26,
format_class: 0,
value: vec![0x12, 0x34, 0x56],
},
BACnetTimeStamp::DateTime {
date: DATE,
time: Time {
hour: 9,
minute: 30,
second: 0,
hundredths: 0,
},
},
)
.unwrap();
let oid = reader.object_identifier();
let mut db = ObjectDatabase::new();
db.set_clock_reader(Some(Arc::new(FixedClock)));
db.add(Box::new(reader)).unwrap();
(db, oid)
}
fn encode(value: &PropertyValue) -> Vec<u8> {
let mut buf = BytesMut::new();
bacnet_encoding::primitives::encode_property_value(&mut buf, value).unwrap();
buf.to_vec()
}
fn write_property(
db: &mut ObjectDatabase,
oid: ObjectIdentifier,
property: PropertyIdentifier,
value: Vec<u8>,
) -> Result<(), Error> {
let mut request = BytesMut::new();
WritePropertyRequest {
object_identifier: oid,
property_identifier: property,
property_array_index: None,
property_value: value,
priority: None,
}
.encode(&mut request)
.unwrap();
handle_write_property(db, &request).map(|_| ())
}
fn write_property_multiple(
db: &mut ObjectDatabase,
oid: ObjectIdentifier,
writes: &[(PropertyIdentifier, Vec<u8>)],
) -> Result<(), Error> {
let mut request = BytesMut::new();
WritePropertyMultipleRequest {
list_of_write_access_specs: vec![WriteAccessSpecification {
object_identifier: oid,
list_of_properties: writes
.iter()
.map(|(property, value)| BACnetPropertyValue {
property_identifier: *property,
property_array_index: None,
value: value.clone(),
priority: None,
})
.collect(),
}],
}
.encode(&mut request)
.unwrap();
handle_write_property_multiple(db, &request).map(|_| ())
}
fn read_bytes(db: &ObjectDatabase, oid: ObjectIdentifier, property: PropertyIdentifier) -> Vec<u8> {
let mut request = BytesMut::new();
ReadPropertyRequest {
object_identifier: oid,
property_identifier: property,
property_array_index: None,
}
.encode(&mut request);
let mut response = BytesMut::new();
handle_read_property(db, &request, &mut response).unwrap();
ReadPropertyACK::decode(&response).unwrap().property_value
}
const ROWS: [PropertyIdentifier; 4] = [
PropertyIdentifier::PRESENT_VALUE,
PropertyIdentifier::UPDATE_TIME,
PropertyIdentifier::RELIABILITY,
PropertyIdentifier::STATUS_FLAGS,
];
fn served(db: &ObjectDatabase, oid: ObjectIdentifier) -> [Vec<u8>; 4] {
ROWS.map(|property| read_bytes(db, oid, property))
}
const CARD: [u8; 8] = [0x09, 0x08, 0x19, 0x00, 0x2B, 0x12, 0x34, 0x56];
const CUSTOM: [u8; 6] = [0x09, 0x02, 0x19, 0x03, 0x29, 0xAB];
fn stamp(hour: u8) -> Vec<u8> {
vec![0x2E, 0xA4, 126, 10, 2, 5, 0xB4, hour, 30, 0, 0, 0x2F]
}
fn device(out_of_service: bool) -> [Vec<u8>; 4] {
[
CARD.to_vec(),
stamp(9),
vec![0x91, 0],
vec![0x82, 0x04, if out_of_service { 0x10 } else { 0x00 }],
]
}
fn simulated() -> [Vec<u8>; 4] {
[
CUSTOM.to_vec(),
stamp(11),
vec![0x91, 7],
vec![0x82, 0x04, 0x50],
]
}
fn simulation() -> [(PropertyIdentifier, Vec<u8>); 2] {
[
(PropertyIdentifier::PRESENT_VALUE, CUSTOM.to_vec()),
(
PropertyIdentifier::RELIABILITY,
encode(&PropertyValue::Enumerated(
Reliability::UNRELIABLE_OTHER.to_raw(),
)),
),
]
}
fn out_of_service(value: bool) -> (PropertyIdentifier, Vec<u8>) {
(
PropertyIdentifier::OUT_OF_SERVICE,
encode(&PropertyValue::Boolean(value)),
)
}
fn assert_property_error(result: Result<(), Error>, expected: ErrorCode) {
assert!(
matches!(result, Err(Error::Remote(RemoteError::Error { class, code }))
if class == ErrorClass::PROPERTY
&& code == expected),
"expected PROPERTY / {expected:?}, got {result:?}"
);
}
#[test]
fn write_property_takes_reader_rows_only_out_of_service() {
let (mut db, oid) = reader_db();
for (property, value) in simulation() {
assert_property_error(
write_property(&mut db, oid, property, value),
ErrorCode::WRITE_ACCESS_DENIED,
);
}
assert_eq!(served(&db, oid), device(false));
let (property, value) = out_of_service(true);
write_property(&mut db, oid, property, value).unwrap();
assert_eq!(served(&db, oid), device(true));
for (property, value) in simulation() {
write_property(&mut db, oid, property, value).unwrap();
}
assert_eq!(served(&db, oid), simulated());
let (property, value) = out_of_service(false);
write_property(&mut db, oid, property, value).unwrap();
assert_eq!(served(&db, oid), device(false));
for (property, value) in simulation() {
assert_property_error(
write_property(&mut db, oid, property, value),
ErrorCode::WRITE_ACCESS_DENIED,
);
}
}
#[test]
fn write_property_multiple_takes_reader_rows_only_out_of_service() {
let (mut db, oid) = reader_db();
assert_property_error(
write_property_multiple(&mut db, oid, &simulation()),
ErrorCode::WRITE_ACCESS_DENIED,
);
assert_eq!(served(&db, oid), device(false));
let mut writes = vec![out_of_service(true)];
writes.extend(simulation());
write_property_multiple(&mut db, oid, &writes).unwrap();
assert_eq!(served(&db, oid), simulated());
write_property_multiple(
&mut db,
oid,
&[
(PropertyIdentifier::PRESENT_VALUE, CARD.to_vec()),
out_of_service(false),
],
)
.unwrap();
assert_eq!(served(&db, oid), device(false));
}
#[test]
fn reader_row_writes_outside_their_datatypes_are_refused_unchanged() {
const PV: PropertyIdentifier = PropertyIdentifier::PRESENT_VALUE;
const RELIABILITY: PropertyIdentifier = PropertyIdentifier::RELIABILITY;
let (mut db, oid) = reader_db();
let (property, value) = out_of_service(true);
write_property(&mut db, oid, property, value).unwrap();
for (property, value, code) in [
(
PV,
encode(&PropertyValue::Enumerated(8)),
ErrorCode::INVALID_DATA_TYPE,
),
(
PV,
encode(&PropertyValue::OctetString(vec![0x12])),
ErrorCode::INVALID_DATA_TYPE,
),
(PV, CARD[..4].to_vec(), ErrorCode::INVALID_DATA_TYPE),
(
PV,
vec![0x09, 0x19, 0x19, 0x00, 0x28],
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
PV,
vec![0x09, 0x09, 0x19, 0x00, 0x28],
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
PV,
vec![0x09, 0x08, 0x19, 0x03, 0x28],
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
RELIABILITY,
encode(&PropertyValue::Enumerated(11)),
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
RELIABILITY,
encode(&PropertyValue::Enumerated(65_536)),
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
RELIABILITY,
encode(&PropertyValue::Unsigned(7)),
ErrorCode::INVALID_DATA_TYPE,
),
] {
write_property(&mut db, oid, PV, CARD.to_vec()).unwrap();
let before = served(&db, oid);
assert_property_error(write_property(&mut db, oid, property, value.clone()), code);
assert_eq!(served(&db, oid), before, "{property:?} {value:02X?}");
assert_property_error(
write_property_multiple(
&mut db,
oid,
&[(PV, CUSTOM.to_vec()), (property, value.clone())],
),
code,
);
let [present_value, _, reliability, _] = served(&db, oid);
assert_eq!(present_value, CUSTOM, "{property:?} {value:02X?}");
assert_eq!(reliability, before[2], "{property:?} {value:02X?}");
}
write_property(
&mut db,
oid,
RELIABILITY,
encode(&PropertyValue::Enumerated(64)),
)
.unwrap();
assert_eq!(read_bytes(&db, oid, RELIABILITY), [0x91, 64]);
}