use super::cov_wire_test_support::*;
use super::*;
use bacnet_encoding::constructed::encode_device_object_property_reference;
use bacnet_objects::averaging::AveragingObject;
use bacnet_services::wpm::{WriteAccessSpecification, WritePropertyMultipleRequest};
use bacnet_services::write_property::WritePropertyRequest;
use bacnet_types::constructed::BACnetDeviceObjectPropertyReference;
use bacnet_types::enums::ObjectType;
use bacnet_types::error::RemoteError;
const REFERENCE: PropertyIdentifier = PropertyIdentifier::OBJECT_PROPERTY_REFERENCE;
const ATTEMPTED: PropertyIdentifier = PropertyIdentifier::ATTEMPTED_SAMPLES;
fn avg1() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::AVERAGING, 1).unwrap()
}
fn device(instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::DEVICE, instance).unwrap()
}
fn local() -> Option<ObjectIdentifier> {
Some(device(856))
}
fn remote() -> Option<ObjectIdentifier> {
Some(device(9))
}
fn reference(
property: PropertyIdentifier,
index: Option<u32>,
device: Option<ObjectIdentifier>,
) -> Vec<u8> {
let mut bytes = BytesMut::new();
encode_device_object_property_reference(
&mut bytes,
&BACnetDeviceObjectPropertyReference {
object_identifier: av1(),
property_identifier: property.to_raw(),
property_array_index: index,
device_identifier: device,
},
);
bytes.to_vec()
}
fn present_value(device: Option<ObjectIdentifier>) -> Vec<u8> {
reference(PropertyIdentifier::PRESENT_VALUE, None, device)
}
async fn start() -> Harness {
Harness::start_with(ServerConfig::default(), |db| {
db.add(Box::new(AveragingObject::new(1, "AVG-1").unwrap()))
.unwrap();
})
.await
}
async fn write_property(h: &mut Harness, value: Vec<u8>) -> Result<(), ErrorCode> {
let mut body = BytesMut::new();
WritePropertyRequest {
object_identifier: avg1(),
property_identifier: REFERENCE,
property_array_index: None,
property_value: value,
priority: None,
}
.encode(&mut body)
.unwrap();
h.request(ConfirmedServiceChoice::WRITE_PROPERTY, body)
.await;
response(h).await
}
async fn write_property_multiple(h: &mut Harness, value: Vec<u8>) -> Result<(), ErrorCode> {
let mut body = BytesMut::new();
WritePropertyMultipleRequest {
list_of_write_access_specs: vec![WriteAccessSpecification {
object_identifier: avg1(),
list_of_properties: vec![BACnetPropertyValue {
property_identifier: REFERENCE,
property_array_index: None,
value,
priority: None,
}],
}],
}
.encode(&mut body)
.unwrap();
h.request(ConfirmedServiceChoice::WRITE_PROPERTY_MULTIPLE, body)
.await;
response(h).await
}
async fn write_local(h: &Harness, value: PropertyValue) -> Result<(), Error> {
h.server
.write_local(
&avg1(),
REFERENCE,
None,
value,
None,
crate::LocalCommandSource::ServerDevice,
)
.await
}
async fn read(h: &Harness, property: PropertyIdentifier) -> PropertyValue {
h.server
.database()
.read()
.await
.get(&avg1())
.unwrap()
.read_property(property, None)
.unwrap()
}
async fn assert_reference(
h: &Harness,
property: PropertyIdentifier,
index: Option<u32>,
what: &str,
) {
assert_eq!(
read(h, REFERENCE).await,
PropertyValue::ApplicationData(reference(property, index, None)),
"{what}"
);
}
async fn fill_window(h: &Harness, count: u64) {
for _ in 0..count {
h.server
.add_averaging_sample_local(&avg1(), Some(PropertyValue::Real(1.0)))
.await
.unwrap();
}
assert_eq!(read(h, ATTEMPTED).await, PropertyValue::Unsigned(count));
}
async fn advance(seconds: u64) {
tokio::time::advance(Duration::from_secs(seconds)).await;
for _ in 0..32 {
tokio::task::yield_now().await;
}
}
#[tokio::test(start_paused = true)]
async fn write_property_takes_a_reference_naming_this_device_as_a_local_reference() {
let mut h = start().await;
assert_eq!(
read(&h, REFERENCE).await,
PropertyValue::ApplicationData(vec![0x0C, 0x00, 0x3F, 0xFF, 0xFF, 0x19, 0x55])
);
fill_window(&h, 2).await;
write_property(&mut h, present_value(local()))
.await
.unwrap();
assert_reference(&h, PropertyIdentifier::PRESENT_VALUE, None, "Present_Value").await;
assert_eq!(read(&h, ATTEMPTED).await, PropertyValue::Unsigned(0));
advance(60).await;
assert_eq!(
read(&h, PropertyIdentifier::VALID_SAMPLES).await,
PropertyValue::Unsigned(1)
);
assert_eq!(
read(&h, PropertyIdentifier::AVERAGE_VALUE).await,
PropertyValue::Real(0.0)
);
let slot = reference(PropertyIdentifier::PRIORITY_ARRAY, Some(8), local());
write_property(&mut h, slot).await.unwrap();
assert_reference(&h, PropertyIdentifier::PRIORITY_ARRAY, Some(8), "slot 8").await;
}
#[tokio::test(start_paused = true)]
async fn write_property_multiple_and_write_local_take_a_reference_naming_this_device() {
let mut h = start().await;
fill_window(&h, 2).await;
write_property_multiple(&mut h, present_value(local()))
.await
.unwrap();
assert_reference(
&h,
PropertyIdentifier::PRESENT_VALUE,
None,
"WritePropertyMultiple",
)
.await;
assert_eq!(read(&h, ATTEMPTED).await, PropertyValue::Unsigned(0));
fill_window(&h, 2).await;
let slot = reference(PropertyIdentifier::PRIORITY_ARRAY, Some(8), local());
write_local(&h, PropertyValue::ApplicationData(slot))
.await
.unwrap();
assert_reference(
&h,
PropertyIdentifier::PRIORITY_ARRAY,
Some(8),
"write_local",
)
.await;
assert_eq!(read(&h, ATTEMPTED).await, PropertyValue::Unsigned(0));
}
#[tokio::test(start_paused = true)]
async fn a_reference_in_another_device_is_refused_and_changes_nothing() {
let mut h = start().await;
write_property(&mut h, present_value(None)).await.unwrap();
fill_window(&h, 2).await;
let code = Err(ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED);
assert_eq!(write_property(&mut h, present_value(remote())).await, code);
assert_eq!(
write_property_multiple(&mut h, present_value(remote())).await,
code
);
let refused = write_local(&h, PropertyValue::ApplicationData(present_value(remote()))).await;
assert!(
matches!(refused, Err(Error::Remote(RemoteError::Error { code, .. }))
if code == ErrorCode::OPTIONAL_FUNCTIONALITY_NOT_SUPPORTED),
"write_local of a remote reference: {refused:?}"
);
let code = Err(ErrorCode::INVALID_DATA_ENCODING);
let slot = reference(PropertyIdentifier::PRIORITY_ARRAY, Some(8), local());
for (second, what) in [(remote(), "this device, then another"), (local(), "twice")] {
let mixed = [slot.clone(), present_value(second)].concat();
assert_eq!(write_property(&mut h, mixed.clone()).await, code, "{what}");
assert_eq!(write_property_multiple(&mut h, mixed).await, code, "{what}");
}
assert_reference(
&h,
PropertyIdentifier::PRESENT_VALUE,
None,
"after the refusals",
)
.await;
assert_eq!(
read(&h, ATTEMPTED).await,
PropertyValue::Unsigned(2),
"the window keeps its samples"
);
}