use super::network_port_mode_tests::{host, port_oid, property_list, rp};
use super::*;
use bacnet_encoding::constructed::{decode_bdt_entry_list, decode_fdt_entry_list};
use bacnet_transport::bbmd::{BdtEntry, ForeignDevicePolicy};
use bacnet_transport::bip::{BbmdOptions, ForeignDeviceConfig};
use bacnet_transport::bvll::BvlcResultCode;
use bacnet_types::constructed::BACnetBDTEntry;
use bacnet_types::enums::PropertyIdentifier as P;
fn database() -> ObjectDatabase {
let mut db = ObjectDatabase::new();
db.add(Box::new(
NetworkPortObject::new_bip(
1,
"selected",
BipPortConfig {
ip_address: [127, 0, 0, 1],
udp_port: 0,
..Default::default()
},
)
.unwrap(),
))
.unwrap();
db
}
fn registered() -> ServerConfig {
ServerConfig {
registered_network_port: Some(port_oid()),
..Default::default()
}
}
#[tokio::test]
async fn a_bbmd_transport_lends_its_own_tables_to_the_registered_port() {
let mut transport =
BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::new(127, 255, 255, 255));
transport.enable_bbmd(Vec::new());
let mut server = BACnetServer::start(registered(), database(), transport)
.await
.unwrap();
let bound = server
.test_network()
.transport()
.bip_port()
.unwrap()
.endpoint
.port();
assert_ne!(bound, 0);
assert_eq!(rp(&server, P::BACNET_IP_MODE).await.unwrap(), [0x91, 2]);
let own = BACnetBDTEntry {
bbmd_address: host([127, 0, 0, 1], bound),
broadcast_mask: Some([0xFF; 4]),
};
let bdt = |bytes: Vec<u8>| decode_bdt_entry_list(&bytes).unwrap();
assert_eq!(
bdt(rp(&server, P::BBMD_BROADCAST_DISTRIBUTION_TABLE)
.await
.unwrap()),
std::slice::from_ref(&own)
);
assert_eq!(
rp(&server, P::BBMD_ACCEPT_FD_REGISTRATIONS).await.unwrap(),
[0x10]
);
assert!(
rp(&server, P::BBMD_FOREIGN_DEVICE_TABLE)
.await
.unwrap()
.is_empty()
);
{
let mut state = server
.test_network()
.transport()
.bbmd_state()
.unwrap()
.lock()
.unwrap();
state
.set_bdt(vec![BdtEntry {
ip: [10, 0, 0, 2],
port: 0xBAC0,
broadcast_mask: [0xFF; 4],
}])
.unwrap();
state.set_foreign_device_policy(Some(ForeignDevicePolicy::default()));
assert_eq!(
state.register_foreign_device([10, 0, 0, 9], 0xBAC0, 60),
BvlcResultCode::SUCCESSFUL_COMPLETION
);
}
assert_eq!(
bdt(rp(&server, P::BBMD_BROADCAST_DISTRIBUTION_TABLE)
.await
.unwrap()),
[
BACnetBDTEntry {
bbmd_address: host([10, 0, 0, 2], 0xBAC0),
broadcast_mask: Some([0xFF; 4]),
},
own,
]
);
assert_eq!(
rp(&server, P::BBMD_ACCEPT_FD_REGISTRATIONS).await.unwrap(),
[0x11]
);
let fdt =
decode_fdt_entry_list(&rp(&server, P::BBMD_FOREIGN_DEVICE_TABLE).await.unwrap()).unwrap();
assert_eq!(fdt.len(), 1);
assert_eq!(fdt[0].time_to_live, 60);
assert!(
(89..=90).contains(&fdt[0].remaining_time_to_live),
"{fdt:?}"
);
let listed = property_list(&server).await;
for property in [
P::BBMD_BROADCAST_DISTRIBUTION_TABLE,
P::BBMD_ACCEPT_FD_REGISTRATIONS,
P::BBMD_FOREIGN_DEVICE_TABLE,
] {
assert!(listed.contains(&property), "{property} listed");
}
server.stop().await.unwrap();
}
#[tokio::test]
async fn a_foreign_device_transport_publishes_its_bbmd_and_lifetime() {
let bbmd = tokio::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))
.await
.unwrap();
let bbmd_port = bbmd.local_addr().unwrap().port();
let mut transport =
BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::new(127, 255, 255, 255));
transport.register_as_foreign_device(ForeignDeviceConfig {
bbmd_ip: Ipv4Addr::LOCALHOST,
bbmd_port,
renewal_interval: None,
ttl: 120,
});
let mut server = BACnetServer::start(registered(), database(), transport)
.await
.unwrap();
assert_eq!(rp(&server, P::BACNET_IP_MODE).await.unwrap(), [0x91, 1]);
let mut expected = vec![0x0E, 0x1C, 127, 0, 0, 1, 0x0F, 0x1A];
expected.extend_from_slice(&bbmd_port.to_be_bytes());
assert_eq!(rp(&server, P::FD_BBMD_ADDRESS).await.unwrap(), expected);
assert_eq!(
rp(&server, P::FD_SUBSCRIPTION_LIFETIME).await.unwrap(),
[0x21, 120]
);
let listed = property_list(&server).await;
assert!(listed.contains(&P::FD_BBMD_ADDRESS));
assert!(!listed.contains(&P::BBMD_BROADCAST_DISTRIBUTION_TABLE));
server.stop().await.unwrap();
}
#[tokio::test]
async fn a_bbmd_that_also_registers_as_a_foreign_device_fails_to_start() {
let mut transport =
BipTransport::new(Ipv4Addr::LOCALHOST, 0, Ipv4Addr::new(127, 255, 255, 255));
transport.enable_bbmd(Vec::new());
transport.register_as_foreign_device(ForeignDeviceConfig {
bbmd_ip: Ipv4Addr::LOCALHOST,
bbmd_port: 47808,
renewal_interval: None,
ttl: 120,
});
let error = match BACnetServer::start(registered(), database(), transport).await {
Ok(_) => panic!("a BBMD that is also a foreign device must not register"),
Err(error) => error.to_string(),
};
assert!(
error.contains("a BBMD that also registers as a foreign device"),
"{error}"
);
}
#[tokio::test]
async fn the_builders_bbmd_mode_runs_a_bbmd_the_registered_port_reports() {
let mut server = BACnetServer::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.broadcast_address(Ipv4Addr::new(127, 255, 255, 255))
.database(database())
.registered_network_port(port_oid())
.mode(BipMode::Bbmd(BbmdOptions {
foreign_device_policy: Some(ForeignDevicePolicy::default()),
..BbmdOptions::default()
}))
.build()
.await
.unwrap();
assert_eq!(rp(&server, P::BACNET_IP_MODE).await.unwrap(), [0x91, 2]);
assert_eq!(
rp(&server, P::BBMD_ACCEPT_FD_REGISTRATIONS).await.unwrap(),
[0x11]
);
assert_eq!(server.foreign_registration().unwrap().attempts, 0);
server.stop().await.unwrap();
}
#[tokio::test]
async fn the_builders_foreign_mode_reports_a_refused_registration() {
let bbmd = tokio::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))
.await
.unwrap();
let mut server = BACnetServer::bip_builder()
.interface(Ipv4Addr::LOCALHOST)
.port(0)
.database(database())
.registered_network_port(port_oid())
.mode(BipMode::Foreign(ForeignDeviceConfig {
bbmd_ip: Ipv4Addr::LOCALHOST,
bbmd_port: bbmd.local_addr().unwrap().port(),
renewal_interval: None,
ttl: 120,
}))
.build()
.await
.unwrap();
assert_eq!(rp(&server, P::BACNET_IP_MODE).await.unwrap(), [0x91, 1]);
let mut packet = [0; 64];
let (len, from) = bbmd.recv_from(&mut packet).await.unwrap();
assert_eq!(&packet[..len], &[0x81, 5, 0, 6, 0, 120]);
bbmd.send_to(&[0x81, 0, 0, 6, 0, 0x30], from).await.unwrap();
tokio::time::timeout(std::time::Duration::from_secs(2), async {
while server.foreign_registration().unwrap().refused == 0 {
tokio::time::sleep(std::time::Duration::from_millis(1)).await;
}
})
.await
.expect("the refusal reached the server's status");
let registration = server.foreign_registration().unwrap();
assert!(!registration.registered);
assert_eq!(
registration.last_result,
Some(BvlcResultCode::REGISTER_FOREIGN_DEVICE_NAK)
);
server.stop().await.unwrap();
assert!(server.foreign_registration().is_err());
}