use bacnet_encoding::npdu::{Npdu, NpduAddress, encode_npdu};
use bacnet_npdu::RejectMessageToNetwork;
use bacnet_npdu::{NetworkMessageType, RejectMessageReason};
use bacnet_types::MacAddr;
use bytes::{Bytes, BytesMut};
use crate::loopback_fixture::{APDU, REMOTE, RouterFixture, next_from_router, recv, reject_of};
const GROUP: u8 = 0xFF;
const CONFIRMED_REQUEST: &[u8] = &[
0x00, 0x05, 0x01, 0x0C, 0x0C, 0x02, 0x00, 0x00, 0x01, 0x19, 0x4D,
];
const COMPLEX_ACK: &[u8] = &[
0x30, 0x01, 0x0C, 0x0C, 0x02, 0x00, 0x00, 0x01, 0x19, 0x4D, 0x3E, 0x75, 0x02, 0x00, 0x41, 0x3F,
];
fn routed(dnet: u16, dadr: u8, apdu: &[u8]) -> Bytes {
let npdu = Npdu {
destination: Some(NpduAddress {
network: dnet,
mac_address: MacAddr::from_slice(&[dadr]),
}),
hop_count: 255,
payload: Bytes::copy_from_slice(apdu),
..Npdu::default()
};
let mut bytes = BytesMut::new();
encode_npdu(&mut bytes, &npdu).unwrap();
bytes.freeze()
}
#[tokio::test]
async fn router_delivers_only_an_unconfirmed_request_to_a_group_dadr() {
let mut fixture = RouterFixture::start_with_group_on_b(GROUP).await;
fixture
.send_from_a(&routed(2000, GROUP, CONFIRMED_REQUEST))
.await;
fixture.send_from_a(&routed(2000, GROUP, COMPLEX_ACK)).await;
fixture
.send_from_a(&routed(REMOTE, GROUP, CONFIRMED_REQUEST))
.await;
fixture.send_from_a(&routed(2000, GROUP, &APDU)).await;
fixture
.send_from_a(&routed(2000, 0x0B, CONFIRMED_REQUEST))
.await;
fixture.send_from_a(&routed(5000, 0x0B, &APDU)).await;
let (remote, to) = next_from_router(&mut fixture.from_router_b).await;
assert_eq!(to.as_deref(), Some(&[0x0B][..]), "the next hop");
assert_eq!(remote.payload, CONFIRMED_REQUEST);
let dadr = remote.destination.map(|to| (to.network, to.mac_address));
assert_eq!(dadr, Some((REMOTE, MacAddr::from_slice(&[GROUP]))));
let (group, to) = next_from_router(&mut fixture.from_router_b).await;
assert_eq!(to, None, "a local broadcast on port B");
assert_eq!(group.payload, APDU[..]);
assert_eq!(group.destination, None, "network 2000 is port B's own");
let source = group.source.map(|from| (from.network, from.mac_address));
assert_eq!(source, Some((1000, MacAddr::from_slice(&[0x0A]))));
let (one, to) = next_from_router(&mut fixture.from_router_b).await;
assert_eq!(to.as_deref(), Some(&[0x0B][..]));
assert_eq!(one.payload, CONFIRMED_REQUEST);
let (reject, _) = next_from_router(&mut fixture.from_router_a).await;
assert_eq!(
reject_of(&reject),
RejectMessageToNetwork {
reason: RejectMessageReason::NOT_DIRECTLY_CONNECTED,
network: 5000,
}
);
assert_eq!(fixture.router.group_dadr_drops(), 2);
assert_eq!(fixture.router.broadcast_pdu_type_drops(), 0);
fixture.stop().await;
}
#[tokio::test]
async fn router_delivers_back_out_the_arrival_port_only_an_unconfirmed_request() {
let mut fixture = RouterFixture::start_with_group_on_b(GROUP).await;
fixture
.send_from_b(&routed(2000, GROUP, CONFIRMED_REQUEST))
.await;
fixture.send_from_b(&routed(2000, GROUP, &APDU)).await;
let (back, to) = next_from_router(&mut fixture.from_router_b).await;
assert_eq!(to, None, "a local broadcast on port B");
assert_eq!(back.payload, APDU[..], "the refused request went nowhere");
let local = recv(&mut fixture.local).await;
assert_eq!(local.apdu.as_ref(), APDU);
assert!(local.is_group, "a broadcast on network 2000");
assert_eq!(local.ingress_network, Some(2000));
assert!(fixture.local.try_recv().is_err(), "delivered once");
assert_eq!(fixture.router.group_dadr_drops(), 1);
fixture.stop().await;
}
#[tokio::test]
async fn a_group_broadcast_for_another_port_stays_off_the_local_application() {
let mut fixture = RouterFixture::start_with_group_on_b(GROUP).await;
fixture.send_from_a(&routed(2000, GROUP, &APDU)).await;
let (out, to) = next_from_router(&mut fixture.from_router_b).await;
assert_eq!((to, out.payload.as_ref()), (None, &APDU[..]));
fixture.send_from_b(&routed(2000, 0x02, &APDU)).await;
let local = recv(&mut fixture.local).await;
assert!(
!local.is_group,
"the marker, addressed to the router itself"
);
assert!(fixture.local.try_recv().is_err());
fixture.stop().await;
}
#[tokio::test]
async fn a_network_message_for_a_group_dadr_is_dropped_and_counted() {
let mut fixture = RouterFixture::start_with_group_on_b(GROUP).await;
let to_group = Some(NpduAddress {
network: 2000,
mac_address: MacAddr::from_slice(&[GROUP]),
});
let controls = [
Npdu {
message_type: Some(NetworkMessageType::INITIALIZE_ROUTING_TABLE),
destination: to_group.clone(),
hop_count: 255,
payload: Bytes::from_static(&[0x00]),
..Npdu::default()
},
Npdu {
message_type: Some(NetworkMessageType::from_raw(0x80)),
vendor_id: Some(999),
destination: to_group,
hop_count: 255,
payload: Bytes::from_static(&APDU),
..Npdu::default()
},
];
for control in &controls {
let mut bytes = BytesMut::new();
encode_npdu(&mut bytes, control).unwrap();
fixture.send_from_a(&bytes).await;
}
fixture.send_from_a(&routed(2000, 0x0B, &APDU)).await;
let (first, to) = next_from_router(&mut fixture.from_router_b).await;
assert_eq!(
to.as_deref(),
Some(&[0x0B][..]),
"the controls went nowhere"
);
assert!(!first.is_network_message());
assert_eq!(first.payload, APDU[..]);
assert_eq!(fixture.router.group_dadr_drops(), 2);
fixture.stop().await;
}
#[tokio::test]
async fn a_relayed_reject_for_a_group_dadr_is_dropped_and_counted() {
let mut fixture = RouterFixture::start_with_group_on_b(GROUP).await;
let reject = Npdu {
message_type: Some(NetworkMessageType::REJECT_MESSAGE_TO_NETWORK),
destination: Some(NpduAddress {
network: 2000,
mac_address: MacAddr::from_slice(&[GROUP]),
}),
hop_count: 255,
payload: Bytes::from_static(&[0x01, 0x13, 0x88]),
..Npdu::default()
};
let mut bytes = BytesMut::new();
encode_npdu(&mut bytes, &reject).unwrap();
fixture.send_from_a(&bytes).await;
fixture.send_from_a(&routed(2000, 0x0B, &APDU)).await;
let (first, to) = next_from_router(&mut fixture.from_router_b).await;
assert_eq!(to.as_deref(), Some(&[0x0B][..]), "the reject went nowhere");
assert!(!first.is_network_message());
assert_eq!(fixture.router.group_dadr_drops(), 1);
fixture.stop().await;
}