use bacnet_encoding::npdu::{Npdu, NpduAddress, decode_npdu, encode_npdu};
use bacnet_npdu::{NetworkMessageType, RejectMessageReason};
use bacnet_transport::port::{DataAttribute, ReceivedNpdu, TransportProvenance};
use bacnet_types::MacAddr;
use bytes::{Bytes, BytesMut};
use tokio::sync::mpsc;
use super::envelope_harness::{Harness, broadcast_data, control_npdu};
use super::{BACnetRouter, RouterOptions, RouterPort, SendRequest, StartedRouter};
use crate::router_table::PortId;
#[tokio::test]
async fn directed_unknown_solicits_once_then_rejects_twice_coalesced() {
let mut h = Harness::two_port();
for _ in 0..2 {
let mut npdu = control_npdu(NetworkMessageType::WHO_IS_ROUTER_TO_NETWORK, &[0x27, 0x0f]);
npdu.destination = Some(NpduAddress {
network: 9999,
mac_address: MacAddr::from_slice(&[9]),
});
npdu.hop_count = 255;
h.dispatch(h.ctx(0, &[7], npdu)).await;
}
let solicited = broadcast_data(h.drain(1));
assert_eq!(solicited.len(), 1);
let who_is = decode_npdu(solicited[0].clone()).unwrap();
assert_eq!(
who_is.message_type,
Some(NetworkMessageType::WHO_IS_ROUTER_TO_NETWORK)
);
assert_eq!(who_is.payload.as_ref(), &[0x27, 0x0f]);
let rejects = h.drain(0);
assert_eq!(rejects.len(), 2);
for req in rejects {
match req {
SendRequest::Unicast { npdu, mac, .. } => {
assert_eq!(mac.as_slice(), &[7]);
let decoded = decode_npdu(npdu).unwrap();
assert_eq!(
decoded.message_type,
Some(NetworkMessageType::REJECT_MESSAGE_TO_NETWORK)
);
assert_eq!(
decoded.payload.as_ref(),
&[
RejectMessageReason::NOT_DIRECTLY_CONNECTED.to_raw(),
0x27,
0x0f
]
);
}
SendRequest::Broadcast { .. } => panic!("expected unicast reject"),
}
}
}
#[tokio::test]
async fn directed_unknown_discovered_via_answer_routes_next_packet() {
let mut h = Harness::two_port();
let mut npdu = control_npdu(NetworkMessageType::WHO_IS_ROUTER_TO_NETWORK, &[0x0b, 0xb8]);
npdu.destination = Some(NpduAddress {
network: 3000,
mac_address: MacAddr::from_slice(&[9]),
});
npdu.hop_count = 255;
h.dispatch(h.ctx(0, &[7], npdu)).await;
assert_eq!(broadcast_data(h.drain(1)).len(), 1);
assert_eq!(h.drain(0).len(), 1);
assert!(h.table().lookup(3000).is_none());
h.handle(h.ctx(
1,
&[2],
control_npdu(NetworkMessageType::I_AM_ROUTER_TO_NETWORK, &[0x0b, 0xb8]),
))
.await;
for port in 0..2 {
for req in h.drain(port) {
match req {
SendRequest::Broadcast { npdu, .. } => {
assert_eq!(
decode_npdu(npdu).unwrap().message_type,
Some(NetworkMessageType::I_AM_ROUTER_TO_NETWORK)
);
}
SendRequest::Unicast { .. } => panic!("expected only I-Am rebroadcast"),
}
}
}
assert!(h.table().lookup(3000).is_some());
assert_eq!(h.table().lookup(3000).unwrap().port(), PortId(1));
let mut npdu = control_npdu(NetworkMessageType::WHO_IS_ROUTER_TO_NETWORK, &[0x0b, 0xb8]);
npdu.destination = Some(NpduAddress {
network: 3000,
mac_address: MacAddr::from_slice(&[9]),
});
npdu.hop_count = 255;
h.dispatch(h.ctx(0, &[7], npdu)).await;
assert!(h.drain(0).is_empty());
match h.drain(1).pop().expect("expected forward") {
SendRequest::Unicast { mac, .. } => assert_eq!(mac.as_slice(), &[2]),
SendRequest::Broadcast { .. } => panic!("expected unicast forward"),
}
}
struct MemTransport {
incoming: Option<mpsc::Receiver<ReceivedNpdu>>,
outgoing: mpsc::Sender<SendRequest>,
mac: [u8; 1],
}
impl bacnet_transport::port::TransportPort for MemTransport {
async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedNpdu>, bacnet_types::error::Error> {
self.incoming
.take()
.ok_or_else(|| bacnet_types::error::Error::Encoding("started".into()))
}
async fn stop(&mut self) -> Result<(), bacnet_types::error::Error> {
Ok(())
}
async fn send_unicast(
&self,
npdu: &[u8],
mac: &[u8],
) -> Result<(), bacnet_types::error::Error> {
self.send_unicast_with_data_attributes(npdu, mac, &[]).await
}
async fn send_broadcast(&self, npdu: &[u8]) -> Result<(), bacnet_types::error::Error> {
self.send_broadcast_with_data_attributes(npdu, &[]).await
}
async fn send_unicast_with_data_attributes(
&self,
npdu: &[u8],
mac: &[u8],
data_attributes: &[DataAttribute],
) -> Result<(), bacnet_types::error::Error> {
self.outgoing
.try_send(SendRequest::Unicast {
npdu: Bytes::copy_from_slice(npdu),
mac: MacAddr::from_slice(mac),
data_attributes: data_attributes.to_vec(),
response: None,
reject_origin: None,
})
.map_err(|_| bacnet_types::error::Error::Encoding("wire full".into()))
}
async fn send_broadcast_with_data_attributes(
&self,
npdu: &[u8],
data_attributes: &[DataAttribute],
) -> Result<(), bacnet_types::error::Error> {
self.outgoing
.try_send(SendRequest::Broadcast {
npdu: Bytes::copy_from_slice(npdu),
data_attributes: data_attributes.to_vec(),
reject_origin: None,
})
.map_err(|_| bacnet_types::error::Error::Encoding("wire full".into()))
}
fn local_receive_apdu_capacity(&self) -> u16 {
1476
}
fn local_mac(&self) -> &[u8] {
&self.mac
}
}
fn apdu_to(net: u16) -> Bytes {
let npdu = Npdu {
destination: Some(NpduAddress {
network: net,
mac_address: MacAddr::from_slice(&[9]),
}),
hop_count: 255,
payload: Bytes::from_static(&[0x10, 0x20]),
..Npdu::default()
};
let mut buf = BytesMut::new();
encode_npdu(&mut buf, &npdu).unwrap();
buf.freeze()
}
fn ingress(npdu: Bytes) -> ReceivedNpdu {
ReceivedNpdu {
direct_response: None,
npdu,
source_mac: MacAddr::from_slice(&[7]),
link_layer_group: true,
data_attributes: Vec::new(),
provenance: TransportProvenance::unverified(),
reply_tx: None,
}
}
#[tokio::test]
async fn apdu_unknown_solicits_rejects_then_forwards_after_answer() {
let (tx0, rx0) = mpsc::channel(16);
let (out0, mut wire0) = mpsc::channel(16);
let (_tx1, _rx1) = mpsc::channel(16);
let (out1, mut wire1) = mpsc::channel(16);
let ports = vec![
RouterPort {
transport: MemTransport {
incoming: Some(rx0),
outgoing: out0,
mac: [1],
},
network_number: 1000,
},
RouterPort {
transport: MemTransport {
incoming: Some(_rx1),
outgoing: out1,
mac: [2],
},
network_number: 2000,
},
];
let StartedRouter {
mut router,
apdus: _local,
..
} = BACnetRouter::start(ports, RouterOptions::new())
.await
.unwrap();
for _ in [wire0.try_recv(), wire1.try_recv()] {}
tx0.send(ingress(apdu_to(3000))).await.unwrap();
let mut saw_who_is = false;
let mut saw_reject = false;
for _ in 0..4 {
tokio::task::yield_now().await;
while let Ok(req) = wire1.try_recv() {
if let SendRequest::Broadcast { npdu, .. } = req {
let decoded = decode_npdu(npdu).unwrap();
if decoded.message_type == Some(NetworkMessageType::WHO_IS_ROUTER_TO_NETWORK)
&& decoded.payload.as_ref() == [0x0b, 0xb8]
{
saw_who_is = true;
}
}
}
while let Ok(req) = wire0.try_recv() {
if let SendRequest::Unicast { npdu, mac, .. } = req {
let decoded = decode_npdu(npdu).unwrap();
if decoded.message_type == Some(NetworkMessageType::REJECT_MESSAGE_TO_NETWORK)
&& decoded.payload.as_ref()
== [
RejectMessageReason::NOT_DIRECTLY_CONNECTED.to_raw(),
0x0b,
0xb8,
]
&& mac.as_slice() == [7]
{
saw_reject = true;
}
}
}
if saw_who_is && saw_reject {
break;
}
}
assert!(saw_who_is, "expected bounded Who-Is solicitation");
assert!(saw_reject, "expected honest retryable reject");
{
let now = router.now();
let mac = bacnet_npdu::Mac::from_array([2]);
let port = crate::router_table::PortId(1);
let _ = router.table().learn(3000, port, mac, now);
}
tx0.send(ingress(apdu_to(3000))).await.unwrap();
let mut forwarded = false;
let mut rejected = false;
for _ in 0..20 {
tokio::task::yield_now().await;
while let Ok(req) = wire1.try_recv() {
if let SendRequest::Unicast { mac, .. } = req
&& mac.as_slice() == [2]
{
forwarded = true;
}
}
while let Ok(req) = wire0.try_recv() {
if let SendRequest::Unicast { npdu, .. } = req
&& decode_npdu(npdu).unwrap().message_type
== Some(NetworkMessageType::REJECT_MESSAGE_TO_NETWORK)
{
rejected = true;
}
}
if forwarded {
break;
}
}
assert!(forwarded, "expected forward after discovery");
assert!(!rejected, "no reject once the route is known");
router.stop().await.unwrap();
}
#[tokio::test]
async fn stop_cancels_pending_discovery() {
let (tx0, rx0) = mpsc::channel(16);
let (out0, mut wire0) = mpsc::channel(16);
let (_tx1, _rx1) = mpsc::channel(16);
let (out1, _wire1) = mpsc::channel(16);
let ports = vec![
RouterPort {
transport: MemTransport {
incoming: Some(rx0),
outgoing: out0,
mac: [1],
},
network_number: 1000,
},
RouterPort {
transport: MemTransport {
incoming: Some(_rx1),
outgoing: out1,
mac: [2],
},
network_number: 2000,
},
];
let StartedRouter {
mut router,
apdus: _local,
..
} = BACnetRouter::start(ports, RouterOptions::new())
.await
.unwrap();
for _ in [wire0.try_recv()] {}
tx0.send(ingress(apdu_to(4000))).await.unwrap();
for _ in 0..10 {
tokio::task::yield_now().await;
if router.searches().0 == 1 {
break;
}
}
assert_eq!(router.searches(), (1, false));
router.stop().await.unwrap();
assert_eq!(router.searches(), (0, true));
}