use super::network_port_mode_tests::{confirmed, host, port_oid, rp};
use super::*;
use bacnet_encoding::constructed::{decode_bdt_entry_list, encode_bdt_entry_list};
use bacnet_services::device_mgmt::ReinitializeDeviceRequest;
use bacnet_services::write_property::WritePropertyRequest;
use bacnet_transport::bbmd::BdtEntry;
use bacnet_types::constructed::BACnetBDTEntry;
use bacnet_types::enums::{PropertyIdentifier as P, ReinitializedState};
pub(super) const TRUE: [u8; 1] = [0x11];
pub(super) const FALSE: [u8; 1] = [0x10];
async fn bbmd_server() -> BACnetServer<BipTransport> {
bbmd_server_with(true).await
}
async fn bbmd_server_with(handler: bool) -> BACnetServer<BipTransport> {
let mut db = ObjectDatabase::new();
let config = BipPortConfig {
ip_address: [127, 0, 0, 1],
udp_port: 0,
..Default::default()
};
db.add(Box::new(
NetworkPortObject::new_bip(1, "bbmd", config).unwrap(),
))
.unwrap();
let mut transport =
BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::new(127, 255, 255, 255));
transport.enable_bbmd(Vec::new());
let config = ServerConfig {
registered_network_port: Some(port_oid()),
on_reinitialize: handler
.then(|| Arc::new(|_: &_, _: &mut _| Ok(())) as ReinitializeHandler),
..Default::default()
};
BACnetServer::start(config, db, transport).await.unwrap()
}
pub(super) async fn wp<T: TransportPort + 'static>(
server: &BACnetServer<T>,
property: P,
octets: Vec<u8>,
) -> Result<(), (ErrorClass, ErrorCode)> {
let mut request = BytesMut::new();
WritePropertyRequest {
object_identifier: port_oid(),
property_identifier: property,
property_array_index: None,
property_value: octets,
priority: None,
}
.encode(&mut request)
.unwrap();
match confirmed(server, ConfirmedServiceChoice::WRITE_PROPERTY, request).await {
Apdu::SimpleAck(_) => Ok(()),
Apdu::Error(error) => Err((error.error_class, error.error_code)),
other => panic!("unexpected WriteProperty reply {other:?}"),
}
}
pub(super) async fn reinitialize<T: TransportPort + 'static>(
server: &BACnetServer<T>,
state: ReinitializedState,
) -> Apdu {
let mut request = BytesMut::new();
ReinitializeDeviceRequest {
reinitialized_state: state,
password: None,
}
.encode(&mut request)
.unwrap();
confirmed(server, ConfirmedServiceChoice::REINITIALIZE_DEVICE, request).await
}
fn bdt_octets(table: &[BACnetBDTEntry]) -> Vec<u8> {
let mut octets = BytesMut::new();
encode_bdt_entry_list(&mut octets, table).unwrap();
octets.to_vec()
}
async fn read_bdt(server: &BACnetServer<BipTransport>) -> Vec<BdtEntry> {
let mut client = BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::LOCALHOST);
let _rx = client.start().await.unwrap();
let target = server.test_network().transport().local_mac().to_vec();
let bdt = client.read_bdt(&target).await.unwrap();
client.stop().await.unwrap();
bdt
}
fn peer_row(ip: [u8; 4]) -> BACnetBDTEntry {
BACnetBDTEntry {
bbmd_address: host(ip, 0xBAC0),
broadcast_mask: Some([0xFF; 4]),
}
}
#[tokio::test]
async fn a_written_bdt_takes_effect_on_activate_changes() {
let mut server = bbmd_server().await;
let own = server
.test_network()
.transport()
.bip_port()
.unwrap()
.endpoint;
let own_row = BACnetBDTEntry {
bbmd_address: host([127, 0, 0, 1], own.port()),
broadcast_mask: Some([0xFF; 4]),
};
let before = read_bdt(&server).await;
assert_eq!(before.len(), 1, "only its own row: {before:?}");
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
let table = vec![peer_row([10, 0, 0, 2]), own_row.clone()];
wp(
&server,
P::BBMD_BROADCAST_DISTRIBUTION_TABLE,
bdt_octets(&table),
)
.await
.unwrap();
wp(&server, P::BBMD_ACCEPT_FD_REGISTRATIONS, TRUE.to_vec())
.await
.unwrap();
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), TRUE);
let read = rp(&server, P::BBMD_BROADCAST_DISTRIBUTION_TABLE)
.await
.unwrap();
assert_eq!(decode_bdt_entry_list(&read).unwrap(), table);
assert_eq!(
rp(&server, P::BBMD_ACCEPT_FD_REGISTRATIONS).await.unwrap(),
TRUE
);
assert_eq!(read_bdt(&server).await, before);
assert!(server.test_network().transport().fdt_counters().is_some());
let accepting = || {
server
.test_network()
.transport()
.bbmd_state()
.unwrap()
.lock()
.unwrap()
.accepts_foreign_device_registrations()
};
assert!(!accepting());
assert!(matches!(
reinitialize(&server, ReinitializedState::ACTIVATE_CHANGES).await,
Apdu::SimpleAck(_)
));
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
let after = read_bdt(&server).await;
assert_eq!(
after,
[
BdtEntry {
ip: [10, 0, 0, 2],
port: 0xBAC0,
broadcast_mask: [0xFF; 4],
},
before[0].clone(),
]
);
let read = rp(&server, P::BBMD_BROADCAST_DISTRIBUTION_TABLE)
.await
.unwrap();
assert_eq!(decode_bdt_entry_list(&read).unwrap(), table);
assert!(accepting());
server.stop().await.unwrap();
}
#[tokio::test]
async fn a_refused_bdt_leaves_the_property_and_the_bbmd_untouched() {
let mut server = bbmd_server().await;
let before_property = rp(&server, P::BBMD_BROADCAST_DISTRIBUTION_TABLE)
.await
.unwrap();
let before = read_bdt(&server).await;
let refused = wp(
&server,
P::BBMD_BROADCAST_DISTRIBUTION_TABLE,
bdt_octets(&[peer_row([224, 0, 0, 1])]),
)
.await;
assert_eq!(
refused,
Err((ErrorClass::PROPERTY, ErrorCode::VALUE_OUT_OF_RANGE))
);
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
assert_eq!(
rp(&server, P::BBMD_BROADCAST_DISTRIBUTION_TABLE)
.await
.unwrap(),
before_property
);
assert!(matches!(
reinitialize(&server, ReinitializedState::ACTIVATE_CHANGES).await,
Apdu::SimpleAck(_)
));
assert_eq!(read_bdt(&server).await, before);
server.stop().await.unwrap();
}
#[tokio::test]
async fn warmstart_activates_and_coldstart_does_not() {
let mut server = bbmd_server().await;
wp(&server, P::BBMD_ACCEPT_FD_REGISTRATIONS, TRUE.to_vec())
.await
.unwrap();
assert!(matches!(
reinitialize(&server, ReinitializedState::COLDSTART).await,
Apdu::SimpleAck(_)
));
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), TRUE);
assert!(matches!(
reinitialize(&server, ReinitializedState::WARMSTART).await,
Apdu::SimpleAck(_)
));
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
assert_eq!(
rp(&server, P::BBMD_ACCEPT_FD_REGISTRATIONS).await.unwrap(),
TRUE
);
server.stop().await.unwrap();
}
#[tokio::test]
async fn without_a_reinitialize_handler_the_rows_refuse_writes() {
let mut server = bbmd_server_with(false).await;
for (property, octets) in [
(P::BBMD_BROADCAST_DISTRIBUTION_TABLE, Vec::new()),
(P::BBMD_ACCEPT_FD_REGISTRATIONS, TRUE.to_vec()),
] {
assert_eq!(
wp(&server, property, octets).await,
Err((ErrorClass::PROPERTY, ErrorCode::WRITE_ACCESS_DENIED)),
"{property}"
);
}
assert_eq!(rp(&server, P::CHANGES_PENDING).await.unwrap(), FALSE);
server.stop().await.unwrap();
}