use super::*;
use bacnet_client::client::BACnetClient;
use bacnet_encoding::primitives::decode_application_value;
use bacnet_objects::command::CommandObject;
use bacnet_objects::group::StructuredViewObject;
use bacnet_objects::traits::BACnetObject;
use bacnet_services::rpm::{ReadPropertyMultipleACK, ReadPropertyMultipleRequest};
use bacnet_types::constructed::{
BACnetActionCommand, BACnetActionList, BACnetDeviceObjectReference, PropertyReference,
ReadAccessSpecification,
};
use bacnet_types::error::RemoteError;
use std::net::Ipv4Addr;
fn oid(object_type: ObjectType, instance: u32) -> ObjectIdentifier {
ObjectIdentifier::new(object_type, instance).unwrap()
}
fn structured_view() -> StructuredViewObject {
let mut view = StructuredViewObject::new(7, "SV-7").unwrap();
view.add_subordinate(oid(ObjectType::ANALOG_INPUT, 1), "a")
.unwrap();
view.add_subordinate(
BACnetDeviceObjectReference {
device_identifier: Some(oid(ObjectType::DEVICE, 9)),
object_identifier: oid(ObjectType::BINARY_INPUT, 1),
},
"b",
)
.unwrap();
view.add_subordinate(oid(ObjectType::ANALOG_VALUE, 2), "c")
.unwrap();
view
}
fn command() -> CommandObject {
let write = |instance, value| BACnetActionCommand {
device_identifier: None,
object_identifier: oid(ObjectType::ANALOG_OUTPUT, instance),
property_identifier: PropertyIdentifier::PRESENT_VALUE,
property_array_index: None,
property_value: PropertyValue::Real(value),
priority: Some(8),
post_delay: None,
quit_on_failure: false,
write_successful: true,
};
let mut command = CommandObject::new(7, "CMD-7").unwrap();
command
.set_action(vec![
BACnetActionList {
commands: vec![write(1, 50.0)],
},
BACnetActionList::default(),
BACnetActionList {
commands: vec![write(1, 0.0), write(2, 21.5)],
},
])
.unwrap();
command
}
fn is_invalid_array_index(error: &Error) -> bool {
matches!(error, Error::Remote(RemoteError::Error { class, code })
if *class == ErrorClass::PROPERTY
&& *code == ErrorCode::INVALID_ARRAY_INDEX)
}
#[tokio::test]
async fn array_elements_bip_wire_rp_and_rpm_serve_each_index() {
let view = structured_view();
let command = command();
let targets = [
(
view.object_identifier(),
PropertyIdentifier::SUBORDINATE_LIST,
),
(
view.object_identifier(),
PropertyIdentifier::SUBORDINATE_ANNOTATIONS,
),
(command.object_identifier(), PropertyIdentifier::ACTION),
];
let indexes = [
None,
Some(0),
Some(1),
Some(2),
Some(3),
Some(4),
Some(u32::MAX),
];
let mut database = ObjectDatabase::new();
database.add(Box::new(view)).unwrap();
database.add(Box::new(command)).unwrap();
let mut request = BytesMut::new();
ReadPropertyMultipleRequest {
list_of_read_access_specs: [targets[0].0, targets[2].0]
.into_iter()
.map(|object_identifier| ReadAccessSpecification {
object_identifier,
list_of_property_references: targets
.iter()
.filter(|&&(target, _)| target == object_identifier)
.flat_map(|&(_, property_identifier)| {
indexes
.iter()
.map(move |&property_array_index| PropertyReference {
property_identifier,
property_array_index,
})
})
.collect(),
})
.collect(),
}
.encode(&mut request)
.unwrap();
let mut expected = BytesMut::new();
handlers::handle_read_property_multiple(&database, &request, &mut expected).unwrap();
let mut server = BACnetServer::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.database(database)
.build()
.await
.unwrap();
let mut client = BACnetClient::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.build()
.await
.unwrap();
let mac = server.local_mac().to_vec();
let mut read = Vec::new();
for &(object, property) in &targets {
let mut values = Vec::new();
for &index in &indexes {
let reply = client.read_property(&mac, object, property, index).await;
values.push(reply.map(|ack| {
assert_eq!(ack.property_array_index, index);
ack.property_value.to_vec()
}));
}
let [whole, size, first, second, third, past_end, max] = values.try_into().unwrap();
let [whole, size, first, second, third] =
[whole, size, first, second, third].map(Result::unwrap);
assert_eq!(
decode_application_value(&size, 0).unwrap(),
(PropertyValue::Unsigned(3), size.len()),
"{property:?}[0]"
);
assert_eq!(
whole,
[&first[..], &second[..], &third[..]].concat(),
"{property:?}"
);
for error in [past_end, max] {
assert!(is_invalid_array_index(&error.unwrap_err()), "{property:?}");
}
read.push([whole, size, first, second, third]);
}
let response = client
.confirmed_request(
&mac,
ConfirmedServiceChoice::READ_PROPERTY_MULTIPLE,
&request,
)
.await
.unwrap();
assert_eq!(response.as_ref(), &expected[..]);
let ack = ReadPropertyMultipleACK::decode(&response).unwrap();
let results: Vec<_> = ack
.list_of_read_access_results
.iter()
.flat_map(|access| &access.list_of_results)
.collect();
assert_eq!(results.len(), targets.len() * indexes.len());
for ((&(_, property), values), rows) in
targets.iter().zip(&read).zip(results.chunks(indexes.len()))
{
for (row, &index) in rows.iter().zip(&indexes) {
assert_eq!(row.property_identifier, property);
assert_eq!(row.property_array_index, index);
}
for (row, value) in rows.iter().zip(values) {
assert_eq!(
row.property_value.as_deref(),
Some(&value[..]),
"{property:?}"
);
}
for row in &rows[values.len()..] {
assert_eq!(
row.error,
Some((ErrorClass::PROPERTY, ErrorCode::INVALID_ARRAY_INDEX)),
"{property:?}"
);
}
}
client.stop().await.unwrap();
server.stop().await.unwrap();
}