use super::*;
use bacnet_objects::life_safety::{LifeSafetyPointObject, LifeSafetyZoneObject};
use bacnet_services::common::BACnetPropertyValue;
use bacnet_services::wpm::{WriteAccessSpecification, WritePropertyMultipleRequest};
use bacnet_types::enums::{LifeSafetyState, Reliability};
use bacnet_types::error::RemoteError;
fn life_safety_db() -> (ObjectDatabase, [ObjectIdentifier; 2]) {
let mut point = LifeSafetyPointObject::new(1, "LSP-1").unwrap();
point.set_tracking_value(LifeSafetyState::PRE_ALARM);
let mut zone = LifeSafetyZoneObject::new(1, "LSZ-1").unwrap();
zone.set_tracking_value(LifeSafetyState::PRE_ALARM);
let oids = [point.object_identifier(), zone.object_identifier()];
let mut db = ObjectDatabase::new();
db.add(Box::new(point)).unwrap();
db.add(Box::new(zone)).unwrap();
(db, oids)
}
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 enumerated(raw: u32) -> PropertyValue {
PropertyValue::Enumerated(raw)
}
fn write_property(
db: &mut ObjectDatabase,
oid: ObjectIdentifier,
property: PropertyIdentifier,
value: PropertyValue,
) -> Result<(), Error> {
let mut request = BytesMut::new();
WritePropertyRequest {
object_identifier: oid,
property_identifier: property,
property_array_index: None,
property_value: encode(&value),
priority: None,
}
.encode(&mut request)
.unwrap();
handle_write_property(db, &request).map(|_| ())
}
fn write_property_multiple(
db: &mut ObjectDatabase,
oid: ObjectIdentifier,
writes: &[(PropertyIdentifier, PropertyValue)],
) -> 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: encode(value),
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
}
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:?}"
);
}
const PRE_ALARM: [u8; 2] = [0x91, 1];
const ALARM: [u8; 2] = [0x91, 2];
const NO_FAULT_DETECTED: [u8; 2] = [0x91, 0];
const NO_SENSOR: [u8; 2] = [0x91, 1];
const OUT_OF_SERVICE_FLAGS: [u8; 3] = [0x82, 0x04, 0x10];
const FAULT_FLAGS: [u8; 3] = [0x82, 0x04, 0x50];
#[test]
fn write_property_takes_tracking_value_and_reliability_only_out_of_service() {
let (mut db, oids) = life_safety_db();
for oid in oids {
for (property, value) in [
(
PropertyIdentifier::TRACKING_VALUE,
LifeSafetyState::ALARM.to_raw(),
),
(
PropertyIdentifier::RELIABILITY,
Reliability::NO_SENSOR.to_raw(),
),
] {
assert_property_error(
write_property(&mut db, oid, property, enumerated(value)),
ErrorCode::WRITE_ACCESS_DENIED,
);
}
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::TRACKING_VALUE),
PRE_ALARM
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::RELIABILITY),
NO_FAULT_DETECTED
);
write_property(
&mut db,
oid,
PropertyIdentifier::OUT_OF_SERVICE,
PropertyValue::Boolean(true),
)
.unwrap();
write_property(
&mut db,
oid,
PropertyIdentifier::TRACKING_VALUE,
enumerated(LifeSafetyState::ALARM.to_raw()),
)
.unwrap();
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::TRACKING_VALUE),
ALARM
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::STATUS_FLAGS),
OUT_OF_SERVICE_FLAGS
);
write_property(
&mut db,
oid,
PropertyIdentifier::RELIABILITY,
enumerated(Reliability::NO_SENSOR.to_raw()),
)
.unwrap();
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::RELIABILITY),
NO_SENSOR
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::STATUS_FLAGS),
FAULT_FLAGS
);
}
}
#[test]
fn write_property_multiple_takes_tracking_value_and_reliability_only_out_of_service() {
let (mut db, oids) = life_safety_db();
let simulation = [
(
PropertyIdentifier::TRACKING_VALUE,
enumerated(LifeSafetyState::ALARM.to_raw()),
),
(
PropertyIdentifier::RELIABILITY,
enumerated(Reliability::NO_SENSOR.to_raw()),
),
];
for oid in oids {
assert_property_error(
write_property_multiple(&mut db, oid, &simulation),
ErrorCode::WRITE_ACCESS_DENIED,
);
assert_property_error(
write_property_multiple(&mut db, oid, &simulation[1..]),
ErrorCode::WRITE_ACCESS_DENIED,
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::TRACKING_VALUE),
PRE_ALARM
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::RELIABILITY),
NO_FAULT_DETECTED
);
let mut writes = vec![(
PropertyIdentifier::OUT_OF_SERVICE,
PropertyValue::Boolean(true),
)];
writes.extend(simulation.iter().cloned());
write_property_multiple(&mut db, oid, &writes).unwrap();
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::TRACKING_VALUE),
ALARM
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::RELIABILITY),
NO_SENSOR
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::STATUS_FLAGS),
FAULT_FLAGS
);
write_property_multiple(
&mut db,
oid,
&[(
PropertyIdentifier::OUT_OF_SERVICE,
PropertyValue::Boolean(false),
)],
)
.unwrap();
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::TRACKING_VALUE),
PRE_ALARM
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::RELIABILITY),
NO_FAULT_DETECTED
);
assert_property_error(
write_property_multiple(&mut db, oid, &simulation),
ErrorCode::WRITE_ACCESS_DENIED,
);
}
}
#[test]
fn out_of_service_writes_outside_the_datatypes_are_refused_unchanged() {
let (mut db, oids) = life_safety_db();
for oid in oids {
write_property(
&mut db,
oid,
PropertyIdentifier::OUT_OF_SERVICE,
PropertyValue::Boolean(true),
)
.unwrap();
for (property, value, code) in [
(
PropertyIdentifier::TRACKING_VALUE,
enumerated(100),
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
PropertyIdentifier::TRACKING_VALUE,
enumerated(65_536),
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
PropertyIdentifier::TRACKING_VALUE,
PropertyValue::Unsigned(2),
ErrorCode::INVALID_DATA_TYPE,
),
(
PropertyIdentifier::RELIABILITY,
enumerated(11),
ErrorCode::VALUE_OUT_OF_RANGE,
),
(
PropertyIdentifier::RELIABILITY,
PropertyValue::Boolean(true),
ErrorCode::INVALID_DATA_TYPE,
),
] {
assert_property_error(write_property(&mut db, oid, property, value.clone()), code);
assert_property_error(
write_property_multiple(
&mut db,
oid,
&[
(
PropertyIdentifier::TRACKING_VALUE,
enumerated(LifeSafetyState::ALARM.to_raw()),
),
(property, value),
],
),
code,
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::TRACKING_VALUE),
ALARM
);
assert_eq!(
read_bytes(&db, oid, PropertyIdentifier::RELIABILITY),
NO_FAULT_DETECTED
);
write_property(
&mut db,
oid,
PropertyIdentifier::TRACKING_VALUE,
enumerated(LifeSafetyState::PRE_ALARM.to_raw()),
)
.unwrap();
}
}
}