use super::*;
use crate::server::test_transport::TestTransport;
use bacnet_client::client::BACnetClient;
use bacnet_objects::file::FileObject;
use bacnet_services::file::{AtomicReadFileRequest, FileAccessMethod};
use bacnet_types::error::RemoteError;
#[tokio::test]
async fn atomic_read_file_all_builders_validate_before_start_or_dial() {
let default = AtomicReadFileBudget::default();
assert_eq!(
(
default.max_requested_stream_octets,
default.max_requested_records,
default.max_service_ack_bytes
),
(16384, 256, 16384)
);
assert_eq!(ServerConfig::default().atomic_read_file_budget, default);
assert!(format!("{:?}", ServerConfig::default()).contains("atomic_read_file_budget"));
AtomicReadFileBudget {
max_requested_stream_octets: usize::MAX,
max_requested_records: usize::MAX,
max_service_ack_bytes: usize::MAX,
}
.validate()
.unwrap();
for budget in [
AtomicReadFileBudget {
max_requested_stream_octets: 0,
..default
},
AtomicReadFileBudget {
max_requested_records: 0,
..default
},
AtomicReadFileBudget {
max_service_ack_bytes: 0,
..default
},
] {
let direct = BACnetServer::start(
ServerConfig {
atomic_read_file_budget: budget,
..Default::default()
},
ObjectDatabase::new(),
TestTransport::never_start(),
)
.await
.err();
let generic = BACnetServer::generic_builder()
.transport(TestTransport::never_start())
.atomic_read_file_budget(budget)
.build()
.await
.err();
let bip = BACnetServer::bip_builder()
.port(0)
.atomic_read_file_budget(budget)
.build()
.await
.err();
for error in [direct, generic, bip] {
assert!(
matches!(error, Some(Error::Encoding(m)) if m.contains("atomic_read_file_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)
.atomic_read_file_budget(budget)
.build()
.await
.err();
assert!(
matches!(result, Some(Error::Encoding(m)) if m.contains("atomic_read_file_max_"))
);
}
}
}
#[tokio::test]
async fn atomic_read_file_within_budget_segmented_complete_ack() {
use bacnet_services::file::{AtomicReadFileAck, FileReadAckMethod};
for record in [false, true] {
for over in [false, true] {
let mut file = FileObject::new(1, "file", "binary").unwrap();
if record {
file.set_file_access_method(
bacnet_types::enums::FileAccessMethod::RECORD_ACCESS.to_raw(),
);
file.set_records(vec![vec![], vec![42; 300]]);
} else {
file.set_data(vec![42; 300]);
}
let mut database = ObjectDatabase::new();
database.add(Box::new(file)).unwrap();
let size = if record { 312 } else { 309 };
let mut server = BACnetServer::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.database(database)
.device(
bacnet_objects::DeviceIdentity::default().with_segmentation(Segmentation::BOTH),
)
.atomic_read_file_budget(AtomicReadFileBudget {
max_service_ack_bytes: size - usize::from(over),
..Default::default()
})
.build()
.await
.unwrap();
let mut client = BACnetClient::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.max_apdu_length(50)
.build()
.await
.unwrap();
let mut request = BytesMut::new();
AtomicReadFileRequest {
file_identifier: ObjectIdentifier::new(ObjectType::FILE, 1).unwrap(),
access: if record {
FileAccessMethod::Record {
file_start_record: 0,
requested_record_count: 2,
}
} else {
FileAccessMethod::Stream {
file_start_position: 0,
requested_octet_count: 300,
}
},
}
.encode(&mut request);
let result = client
.confirmed_request(
server.local_mac(),
ConfirmedServiceChoice::ATOMIC_READ_FILE,
&request,
)
.await;
client.stop().await.unwrap();
server.stop().await.unwrap();
if over {
assert!(
matches!(result, Err(Error::Remote(RemoteError::Abort { reason })) if reason == AbortReason::BUFFER_OVERFLOW)
);
} else {
let bytes = result.unwrap();
assert_eq!(bytes.len(), size);
let ack = AtomicReadFileAck::decode(&bytes).unwrap();
assert!(ack.end_of_file);
match ack.access {
FileReadAckMethod::Stream {
file_start_position,
file_data,
} => {
assert_eq!(file_start_position, 0);
assert_eq!(file_data, vec![42; 300]);
}
FileReadAckMethod::Record {
file_start_record,
returned_record_count,
file_record_data,
} => {
assert_eq!(file_start_record, 0);
assert_eq!(returned_record_count, 2);
assert_eq!(file_record_data, vec![vec![], vec![42; 300]]);
}
}
}
}
}
}
#[tokio::test]
async fn atomic_read_file_default_stream_raw_count_budget_wire() {
default_count_wire(false).await;
}
#[tokio::test]
async fn atomic_read_file_default_record_raw_count_budget_wire() {
default_count_wire(true).await;
}
async fn default_count_wire(record: bool) {
let mut file = FileObject::new(1, "file", "binary").unwrap();
if record {
file.set_file_access_method(bacnet_types::enums::FileAccessMethod::RECORD_ACCESS.to_raw());
}
let mut database = ObjectDatabase::new();
database.add(Box::new(file)).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 mut wire = BytesMut::new();
AtomicReadFileRequest {
file_identifier: ObjectIdentifier::new(ObjectType::FILE, 1).unwrap(),
access: if record {
FileAccessMethod::Record {
file_start_record: 0,
requested_record_count: 257,
}
} else {
FileAccessMethod::Stream {
file_start_position: 0,
requested_octet_count: 16385,
}
},
}
.encode(&mut wire);
let result = client
.confirmed_request(
server.local_mac(),
ConfirmedServiceChoice::ATOMIC_READ_FILE,
&wire,
)
.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:?}"
);
}