use super::*;
use crate::server::test_transport::TestTransport;
use bacnet_types::error::RemoteError;
#[tokio::test]
async fn enrollment_summary_defaults_and_all_builders_validate_before_start() {
let default = GetEnrollmentSummaryBudget::default();
assert_eq!(
(default.max_objects, default.max_service_ack_bytes),
(4096, 16384)
);
assert_eq!(
ServerConfig::default().get_enrollment_summary_budget,
default
);
assert!(format!("{:?}", ServerConfig::default()).contains("get_enrollment_summary_budget"));
GetEnrollmentSummaryBudget {
max_objects: usize::MAX,
max_service_ack_bytes: usize::MAX,
}
.validate()
.unwrap();
for budget in [
GetEnrollmentSummaryBudget {
max_objects: 0,
..default
},
GetEnrollmentSummaryBudget {
max_service_ack_bytes: 0,
..default
},
] {
let direct = BACnetServer::start(
ServerConfig {
get_enrollment_summary_budget: budget,
..Default::default()
},
ObjectDatabase::new(),
TestTransport::never_start(),
)
.await
.err();
let generic = BACnetServer::generic_builder()
.transport(TestTransport::never_start())
.get_enrollment_summary_budget(budget)
.build()
.await
.err();
let bip = BACnetServer::bip_builder()
.port(0)
.get_enrollment_summary_budget(budget)
.build()
.await
.err();
for error in [direct, generic, bip] {
assert!(
matches!(error, Some(Error::Encoding(m)) if m.contains("enrollment_summary_max_"))
);
}
#[cfg(feature = "sc-tls")]
{
let result = BACnetServer::sc_builder()
.hub_url("not-a-websocket-url")
.tls_config(crate::server::sc_builder::test_tls_config())
.device_uuid(crate::server::sc_builder::TEST_DEVICE_UUID)
.get_enrollment_summary_budget(budget)
.build()
.await
.err();
assert!(
matches!(result, Some(Error::Encoding(m)) if m.contains("enrollment_summary_max_"))
);
}
}
}
#[tokio::test]
async fn enrollment_summary_segmented_complete_ack_or_whole_abort() {
use bacnet_client::client::BACnetClient;
use bacnet_objects::{analog::AnalogValueObject, notification_class::NotificationClass};
use bacnet_types::enums::EngineeringUnits;
for (objects, bytes, expected) in [
(21, 260, None),
(21, 259, Some(AbortReason::BUFFER_OVERFLOW)),
(20, 260, Some(AbortReason::OUT_OF_RESOURCES)),
] {
let mut database = ObjectDatabase::new();
for instance in 1..=20 {
database
.add(Box::new(
AnalogValueObject::new(
instance,
format!("summary-{instance}"),
EngineeringUnits::DEGREES_CELSIUS,
)
.unwrap(),
))
.unwrap();
}
database
.add(Box::new(NotificationClass::new(0, "NC").unwrap()))
.unwrap();
let mut server = BACnetServer::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.database(database)
.device(bacnet_objects::DeviceIdentity::default().with_segmentation(Segmentation::BOTH))
.get_enrollment_summary_budget(GetEnrollmentSummaryBudget {
max_objects: objects,
max_service_ack_bytes: bytes,
})
.build()
.await
.unwrap();
let mut client = BACnetClient::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.max_apdu_length(50)
.build()
.await
.unwrap();
let result = client
.confirmed_request(
server.local_mac(),
ConfirmedServiceChoice::GET_ENROLLMENT_SUMMARY,
&[9, 0],
)
.await;
client.stop().await.unwrap();
server.stop().await.unwrap();
match expected {
Some(expected) => assert!(
matches!(result, Err(Error::Remote(RemoteError::Abort { reason })) if reason == expected),
"{result:?}"
),
None => {
let ack = result.unwrap();
assert_eq!(ack.len(), 260);
assert_eq!(
bacnet_services::enrollment_summary::GetEnrollmentSummaryAck::decode(&ack)
.unwrap()
.entries
.len(),
20
);
}
}
}
}
#[tokio::test]
async fn enrollment_summary_default_object_budget_wire() {
default_object_budget_wire(true).await;
}
#[tokio::test]
async fn enrollment_summary_default_noncandidate_budget_wire() {
default_object_budget_wire(false).await;
}
async fn default_object_budget_wire(candidate: bool) {
use bacnet_client::client::BACnetClient;
use bacnet_objects::analog::AnalogValueObject;
use bacnet_types::enums::EngineeringUnits;
let mut database = ObjectDatabase::new();
for instance in 1..=4097 {
if candidate {
database
.add(Box::new(
AnalogValueObject::new(
instance,
format!("summary-{instance}"),
EngineeringUnits::DEGREES_CELSIUS,
)
.unwrap(),
))
.unwrap();
} else {
database
.add(Box::new(
bacnet_objects::notification_class::NotificationClass::new(
instance,
format!("class-{instance}"),
)
.unwrap(),
))
.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 result = client
.confirmed_request(
server.local_mac(),
ConfirmedServiceChoice::GET_ENROLLMENT_SUMMARY,
&[0x09, 0],
)
.await;
client.stop().await.unwrap();
server.stop().await.unwrap();
assert!(
matches!(result, Err(Error::Remote(RemoteError::Abort { reason }))
if reason == AbortReason::OUT_OF_RESOURCES),
"{result:?}"
);
}