#[allow(dead_code)]
mod support;
use std::net::Ipv4Addr;
use bacnet_network::router::{BACnetRouter, RouterOptions, RouterPort, StartedRouter};
use bacnet_server::server::BACnetServer;
use bacnet_transport::any::AnyTransport;
use bacnet_transport::bbmd::ForeignDevicePolicy;
use bacnet_transport::bip::{BbmdOptions, BipMode, BipTransport};
use bacnet_transport::loopback::LoopbackTransport;
use bacnet_transport::mstp::NoSerial;
use serde_json::{json, Value};
use support::lab::{lab_database, lab_identity};
use support::{command, failure, run, Process};
const DNET: u16 = 2;
const INSTANCE: u32 = 1234;
async fn site() -> (BACnetRouter, BACnetServer<LoopbackTransport>, u16) {
for _ in 0..8 {
let port = std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))
.unwrap()
.local_addr()
.unwrap()
.port();
let mut bip = BipTransport::new(Ipv4Addr::LOCALHOST, port, Ipv4Addr::LOCALHOST);
bip.set_mode(BipMode::Bbmd(BbmdOptions {
foreign_device_policy: Some(ForeignDevicePolicy::default()),
..BbmdOptions::default()
}));
let (router_end, station) = LoopbackTransport::pair(vec![0x01], vec![0x05]);
let ports = vec![
RouterPort {
transport: AnyTransport::<NoSerial>::Bip(Box::new(bip)),
network_number: 1,
},
RouterPort {
transport: AnyTransport::Loopback(router_end),
network_number: DNET,
},
];
if let Ok(StartedRouter { router, .. }) =
BACnetRouter::start(ports, RouterOptions::new()).await
{
let server = BACnetServer::generic_builder()
.device(lab_identity(INSTANCE))
.transport(station)
.database(lab_database(INSTANCE))
.build()
.await
.unwrap();
return (router, server, port);
}
}
panic!("no loopback UDP port the router could bind");
}
async fn read(port: u16, target: &str, wait_ms: &str) -> std::process::Output {
let mut cmd = command();
cmd.env("BACNET_CLI_ROUTED_WAIT_MS", wait_ms).args([
"--interface",
"127.0.0.1",
"--port",
"0",
"--foreign-bbmd",
&format!("127.0.0.1:{port}"),
"--json",
"read",
target,
"analog-input:2",
"present-value",
]);
Process::spawn(&mut cmd).output().await
}
#[tokio::test]
async fn a_routed_read_reaches_the_device_behind_the_router() {
let (mut router, mut server, port) = site().await;
for target in [format!("{DNET}:{INSTANCE}"), format!("{DNET}:0x05")] {
let output = read(port, &target, "3000").await;
assert!(
output.status.success(),
"{target}: {}",
String::from_utf8_lossy(&output.stderr)
);
let value: Value = serde_json::from_slice(&output.stdout).unwrap();
assert_eq!(
value,
json!({ "object": "ANALOG_INPUT:2", "property": "PRESENT_VALUE", "value": "51" }),
"{target}"
);
}
server.stop().await.unwrap();
router.stop().await;
}
#[tokio::test]
async fn whois_router_lists_the_router_and_its_network() {
let (mut router, mut server, port) = site().await;
let whois_router = |extra: &'static [&'static str]| async move {
let bbmd = format!("127.0.0.1:{port}");
let mut args = vec![
"--interface",
"127.0.0.1",
"--port",
"0",
"--foreign-bbmd",
&bbmd,
"--json",
"whois-router",
"--wait",
"2",
];
args.extend_from_slice(extra);
let output = run(args).await;
assert!(
output.status.success(),
"{}",
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_slice::<Value>(&output.stdout).unwrap()
};
let expected = json!([{ "router": format!("127.0.0.1:{port}"), "networks": [DNET] }]);
assert_eq!(whois_router(&[]).await, expected);
assert_eq!(whois_router(&["--dnet", "2"]).await, expected);
assert_eq!(whois_router(&["--dnet", "77"]).await, json!([]));
server.stop().await.unwrap();
router.stop().await;
}
#[tokio::test]
async fn routed_targets_fail_clearly() {
let (mut router, mut server, port) = site().await;
let output = read(port, &format!("{DNET}:{}", INSTANCE + 1), "300").await;
failure(&output, "did not answer a Who-Is on network 2");
let output = run([
"--interface",
"127.0.0.1",
"--port",
"0",
"--json",
"alarms",
&format!("{DNET}:{INSTANCE}"),
])
.await;
failure(&output, "is a routed target");
server.stop().await.unwrap();
router.stop().await;
}