use std::net::Ipv4Addr;
use std::sync::{Arc, Mutex};
use bacnet_encoding::npdu::decode_npdu;
use bacnet_npdu::NpduLimits;
use bacnet_transport::bip::BipTransport;
use bacnet_transport::mstp::{LoopbackSerial, MstpConfig, MstpTransport};
use bacnet_transport::port::{GroupDestinations, ReceivedNpdu, TransportPort};
use bacnet_types::error::Error;
use tokio::sync::mpsc;
use tokio::time::{Duration, timeout};
use super::{BACnetRouter, RouterOptions, RouterPort, StartedRouter};
const MSTP_NET: u16 = 100;
const BIP_NET: u16 = 300;
#[derive(Clone, Debug, PartialEq, Eq)]
enum Sent {
Unicast(Vec<u8>),
Broadcast(Vec<u8>),
}
struct Recorded {
inner: BipTransport,
sent: Arc<Mutex<Vec<Sent>>>,
}
impl TransportPort for Recorded {
async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedNpdu>, Error> {
self.inner.start().await
}
async fn stop(&mut self) -> Result<(), Error> {
self.inner.stop().await
}
async fn send_unicast(&self, npdu: &[u8], mac: &[u8]) -> Result<(), Error> {
self.sent.lock().unwrap().push(Sent::Unicast(mac.to_vec()));
self.inner.send_unicast(npdu, mac).await
}
async fn send_broadcast(&self, npdu: &[u8]) -> Result<(), Error> {
self.sent
.lock()
.unwrap()
.push(Sent::Broadcast(npdu.to_vec()));
Ok(())
}
fn local_mac(&self) -> &[u8] {
self.inner.local_mac()
}
fn local_receive_apdu_capacity(&self) -> u16 {
self.inner.local_receive_apdu_capacity()
}
fn max_npdu(&self) -> NpduLimits {
self.inner.max_npdu()
}
fn is_broadcast_mac(&self, mac: &[u8]) -> bool {
self.inner.is_broadcast_mac(mac)
}
fn group_destinations(&self) -> GroupDestinations {
self.inner.group_destinations()
}
fn local_broadcast_destinations(&self) -> GroupDestinations {
self.inner.local_broadcast_destinations()
}
}
enum Port {
Mstp(MstpTransport<LoopbackSerial>),
Bip(Recorded),
}
impl TransportPort for Port {
fn prepare_for_routing(&mut self) {
if let Self::Mstp(mstp) = self {
mstp.prepare_for_routing();
}
}
async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedNpdu>, Error> {
match self {
Self::Mstp(mstp) => mstp.start().await,
Self::Bip(bip) => bip.start().await,
}
}
async fn stop(&mut self) -> Result<(), Error> {
match self {
Self::Mstp(mstp) => mstp.stop().await,
Self::Bip(bip) => bip.stop().await,
}
}
async fn send_unicast(&self, npdu: &[u8], mac: &[u8]) -> Result<(), Error> {
match self {
Self::Mstp(mstp) => mstp.send_unicast(npdu, mac).await,
Self::Bip(bip) => bip.send_unicast(npdu, mac).await,
}
}
async fn send_broadcast(&self, npdu: &[u8]) -> Result<(), Error> {
match self {
Self::Mstp(mstp) => mstp.send_broadcast(npdu).await,
Self::Bip(bip) => bip.send_broadcast(npdu).await,
}
}
fn local_mac(&self) -> &[u8] {
match self {
Self::Mstp(mstp) => mstp.local_mac(),
Self::Bip(bip) => bip.local_mac(),
}
}
fn local_receive_apdu_capacity(&self) -> u16 {
match self {
Self::Mstp(mstp) => mstp.local_receive_apdu_capacity(),
Self::Bip(bip) => bip.local_receive_apdu_capacity(),
}
}
fn max_npdu(&self) -> NpduLimits {
match self {
Self::Mstp(mstp) => mstp.max_npdu(),
Self::Bip(bip) => bip.max_npdu(),
}
}
fn is_broadcast_mac(&self, mac: &[u8]) -> bool {
match self {
Self::Mstp(mstp) => mstp.is_broadcast_mac(mac),
Self::Bip(bip) => bip.is_broadcast_mac(mac),
}
}
fn group_destinations(&self) -> GroupDestinations {
match self {
Self::Mstp(mstp) => mstp.group_destinations(),
Self::Bip(bip) => bip.group_destinations(),
}
}
fn local_broadcast_destinations(&self) -> GroupDestinations {
match self {
Self::Mstp(mstp) => mstp.local_broadcast_destinations(),
Self::Bip(bip) => bip.local_broadcast_destinations(),
}
}
}
fn station(station: u8) -> MstpConfig {
MstpConfig {
this_station: station,
max_master: 1,
baud_rate: 38_400,
..MstpConfig::default()
}
}
fn routed(dadr: [u8; 6], apdu: &[u8]) -> Vec<u8> {
let mut npdu = vec![0x01, 0x20];
npdu.extend_from_slice(&BIP_NET.to_be_bytes());
npdu.push(6);
npdu.extend_from_slice(&dadr);
npdu.push(0xFF);
npdu.extend_from_slice(apdu);
npdu
}
#[tokio::test(start_paused = true)]
async fn mstp_six_byte_bip_group_destinations_reach_the_bip_port_only_as_broadcasts() {
let (router_serial, peer_serial) = LoopbackSerial::pair();
let sent = Arc::new(Mutex::new(Vec::new()));
let port = std::net::UdpSocket::bind((Ipv4Addr::LOCALHOST, 0))
.unwrap()
.local_addr()
.unwrap()
.port();
let other = port.checked_add(1).unwrap_or(port - 1);
let bip = BipTransport::new(Ipv4Addr::LOCALHOST, port, Ipv4Addr::new(192, 0, 2, 255));
let ports = vec![
RouterPort {
transport: Port::Mstp(MstpTransport::new(router_serial, station(0))),
network_number: MSTP_NET,
},
RouterPort {
transport: Port::Bip(Recorded {
inner: bip,
sent: Arc::clone(&sent),
}),
network_number: BIP_NET,
},
];
let StartedRouter { mut router, .. } = BACnetRouter::start(ports, RouterOptions::new())
.await
.unwrap();
let mut peer = MstpTransport::new(peer_serial, station(1));
let _from_router = peer.start().await.unwrap();
timeout(Duration::from_secs(10), async {
while sent.lock().unwrap().is_empty() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
.expect("the router announces itself");
sent.lock().unwrap().clear();
let at = |ip: [u8; 4], port: u16| {
let [high, low] = port.to_be_bytes();
[ip[0], ip[1], ip[2], ip[3], high, low]
};
let groups = [
at([192, 0, 2, 255], port),
at([255, 255, 255, 255], port),
at([239, 1, 2, 3], port),
];
let elsewhere = [at([192, 0, 2, 255], other), at([255, 255, 255, 255], other)];
let not_unconfirmed = [0x50, 0x01, 0x0C, 0x91, 0x02, 0x91, 0x1F];
let who_is = [0x10, 0x08];
for apdu in [¬_unconfirmed[..], &who_is[..]] {
for dadr in groups {
peer.send_unicast(&routed(dadr, apdu), &[0]).await.unwrap();
}
}
for dadr in elsewhere {
peer.send_unicast(&routed(dadr, &who_is), &[0])
.await
.unwrap();
}
timeout(Duration::from_secs(10), async {
while sent.lock().unwrap().len() < groups.len() {
tokio::time::sleep(Duration::from_millis(10)).await;
}
})
.await
.expect("the Who-Is requests go out");
tokio::time::sleep(Duration::from_secs(1)).await;
let sent = sent.lock().unwrap().clone();
assert_eq!(sent.len(), groups.len(), "{sent:?}");
for send in sent {
let Sent::Broadcast(npdu) = send else {
panic!("a unicast reached the B/IP port: {send:?}");
};
let npdu = decode_npdu(npdu.into()).unwrap();
assert_eq!(npdu.destination, None, "network 300 is the port's own");
let source = npdu.source.expect("the router names the origin");
assert_eq!(
(source.network, source.mac_address.as_slice()),
(MSTP_NET, &[1][..])
);
assert_eq!(npdu.payload.as_ref(), who_is);
}
assert_eq!(
router.group_dadr_drops(),
(groups.len() + elsewhere.len()) as u64
);
router.stop().await.unwrap();
peer.stop().await.unwrap();
}