use bacnet_encoding::npdu::{Npdu, NpduAddress, decode_npdu, encode_npdu};
use bacnet_npdu::NetworkMessageType;
use bacnet_npdu::NpduLimits;
use bacnet_types::MacAddr;
use bytes::{Bytes, BytesMut};
use std::sync::atomic::Ordering;
use tokio::sync::watch;
use tokio::time::{Duration, timeout};
use super::driven_port::{Driven, Handle, Sent};
use super::{BACnetRouter, RouterOptions, RouterPort, StartedRouter};
const NODE: &[u8] = &[7];
const PEER: &[u8] = &[9];
fn to_peer(apdu: usize) -> Vec<u8> {
let mut payload = vec![0x10, 0x08];
payload.resize(apdu, 0);
let npdu = Npdu {
destination: Some(NpduAddress {
network: 2000,
mac_address: MacAddr::from_slice(PEER),
}),
hop_count: 255,
payload: Bytes::from(payload),
..Npdu::default()
};
let mut buf = BytesMut::new();
encode_npdu(&mut buf, &npdu).unwrap();
buf.to_vec()
}
async fn start(ports: [Driven; 2]) -> BACnetRouter {
let [first, second] = ports;
let ports = vec![
RouterPort {
transport: first,
network_number: 1000,
},
RouterPort {
transport: second,
network_number: 2000,
},
];
let StartedRouter { router, .. } = BACnetRouter::start(ports, RouterOptions::new())
.await
.unwrap();
router
}
async fn next_send(port: &mut Handle, pick: impl Fn(&Sent) -> bool) -> Sent {
let wanted = async {
loop {
let sent = port.sent.recv().await.expect("the port is open");
if pick(&sent) {
return sent;
}
}
};
timeout(Duration::from_secs(5), wanted)
.await
.expect("the send comes")
}
async fn until(what: &str, done: impl Fn() -> bool) {
for _ in 0..10_000 {
if done() {
return;
}
tokio::task::yield_now().await;
}
panic!("{what}: not reached");
}
fn is_reason_4(sent: &Sent) -> bool {
let npdu = decode_npdu(Bytes::from(sent.npdu.clone())).unwrap();
sent.to.as_deref() == Some(NODE)
&& npdu.message_type == Some(NetworkMessageType::REJECT_MESSAGE_TO_NETWORK)
&& npdu.payload.as_ref() == [4, 0x07, 0xD0]
}
#[tokio::test(start_paused = true)]
async fn a_published_limit_routes_every_later_frame() {
let (changes_tx, changes) = watch::channel(NpduLimits::STANDARD);
let (first, mut node) = Driven::new(1, 1497);
let (second, mut peer) = Driven::new(2, 1497);
let router = start([first, second.publishing(changes)]).await;
let limit = |router: &BACnetRouter| router.shared.engine.lock().ports()[1].max_npdu;
changes_tx.send_replace(NpduLimits::both(480));
until("the router has 480", || {
limit(&router) == NpduLimits::both(480)
})
.await;
node.receive(to_peer(594), NODE).await;
next_send(&mut node, is_reason_4).await;
assert_eq!(router.counters().egress_too_long, 1);
changes_tx.send_replace(NpduLimits::STANDARD);
until("the router has 1497", || {
limit(&router) == NpduLimits::both(1497)
})
.await;
node.receive(to_peer(594), NODE).await;
let relayed = next_send(&mut peer, |sent| sent.to.as_deref() == Some(PEER)).await;
assert_eq!((relayed.npdu.len(), relayed.taken), (600, true));
assert_eq!(router.counters().egress_too_long, 1);
}
#[tokio::test(start_paused = true)]
async fn a_send_the_port_refuses_as_too_long_returns_reason_4() {
let (first, mut node) = Driven::new(1, 1497);
let (second, mut peer) = Driven::new(2, 1497);
let router = start([first, second]).await;
assert!(node.started.load(Ordering::SeqCst) && peer.started.load(Ordering::SeqCst));
peer.down.store(true, Ordering::SeqCst);
node.receive(to_peer(10), NODE).await;
let refused = next_send(&mut peer, |sent| sent.to.as_deref() == Some(PEER)).await;
assert!(!refused.taken);
peer.down.store(false, Ordering::SeqCst);
peer.limit.store(480, Ordering::SeqCst);
node.receive(to_peer(594), NODE).await;
let refused = next_send(&mut peer, |sent| sent.to.as_deref() == Some(PEER)).await;
assert_eq!((refused.npdu.len(), refused.taken), (600, false));
until("the refusal is counted", || {
router.counters().egress_too_long == 1
})
.await;
let counters = router.counters();
assert_eq!((counters.refused, counters.rejects), (1, 1));
let rejected = next_send(&mut node, is_reason_4).await;
assert!(rejected.taken);
}
#[tokio::test(start_paused = true)]
async fn an_application_port_keeps_its_declaration_when_a_path_shrinks() {
use bacnet_transport::port::TransportPort;
use bacnet_types::error::Error;
let (changes_tx, changes) = watch::channel(NpduLimits::STANDARD);
let (first, mut node) = Driven::new(1, 1497);
let (second, _peer) = Driven::new(2, 1497);
let ports = vec![
RouterPort {
transport: first,
network_number: 1000,
},
RouterPort {
transport: second.publishing(changes),
network_number: 2000,
},
];
let started = BACnetRouter::start(ports, RouterOptions::new())
.await
.unwrap();
let (router, mut port, _controls) = started.into_application_port();
let _apdus = port.start().await.unwrap();
assert_eq!(port.local_receive_apdu_capacity(), 1476);
assert_eq!(port.egress_apdu_limit(), 1476);
let limit = |router: &BACnetRouter| router.shared.engine.lock().ports()[1].max_npdu;
changes_tx.send_replace(NpduLimits::both(480));
until("the router has 480", || {
limit(&router) == NpduLimits::both(480)
})
.await;
assert_eq!(port.local_receive_apdu_capacity(), 1476);
assert!(port.egress_apdu_limit() < 480);
node.receive(to_peer(594), NODE).await;
next_send(&mut node, is_reason_4).await;
let mut npdu = vec![1, 0x20, 0x07, 0xD0, 1, PEER[0], 255, 0x10, 0x08];
npdu.resize(600, 0);
let error = port.send_unicast(&npdu, &[0xEE]).await.unwrap_err();
assert!(matches!(error, Error::SendTooLong { .. }), "{error:?}");
assert_eq!(router.counters().egress_too_long, 2);
}