use bacnet_encoding::apdu::{Apdu, decode_apdu};
use bacnet_encoding::npdu::decode_npdu;
use bacnet_network::network_number::LocalNetworkNumber;
use bacnet_transport::bip::BipTransport;
use bacnet_transport::loopback::LoopbackTransport;
use bacnet_transport::port::{GroupDestinations, ReceivedNpdu, TransportPort};
use bacnet_types::enums::{AbortReason, ConfirmedServiceChoice, UnconfirmedServiceChoice};
use bacnet_types::error::{Error, RemoteError};
use bacnet_types::network_number::NetworkNumber;
use std::net::Ipv4Addr;
use tokio::sync::mpsc;
use super::{BACnetClient, ClientConfig};
const CLIENT: [u8; 1] = [1];
const PEER: [u8; 1] = [2];
const BROADCAST: [u8; 1] = [0xFF];
#[tokio::test(start_paused = true)]
async fn a_confirmed_request_to_the_broadcast_mac_is_refused_before_any_state() {
let (mut transport, mut peer) = LoopbackTransport::pair(CLIENT.to_vec(), PEER.to_vec());
transport.set_broadcast_mac(BROADCAST.to_vec());
let mut frames = peer.start().await.unwrap();
let config = ClientConfig {
apdu_timeout_ms: 20,
apdu_retries: 0,
..ClientConfig::default()
};
let mut client = BACnetClient::start(config, transport).await.unwrap();
let refused = client
.confirmed_request(&BROADCAST, ConfirmedServiceChoice::READ_PROPERTY, &[0x0C])
.await;
assert!(
matches!(&refused, Err(Error::Encoding(message))
if message.contains("not to a broadcast or group address")),
"{refused:?}"
);
assert!(
frames.try_recv().is_err(),
"a refused request reached the link"
);
assert_eq!(client.tsm.lock().await.pending_count(), 0);
assert_eq!(client.tsm.lock().await.coordinated_active_count(), 0);
client
.unconfirmed_request(&BROADCAST, UnconfirmedServiceChoice::WHO_IS, &[])
.await
.unwrap();
let who_is = decode_npdu(frames.try_recv().unwrap().npdu).unwrap();
assert!(matches!(
decode_apdu(who_is.payload),
Ok(Apdu::UnconfirmedRequest(_))
));
let routed = client
.confirmed_request_routed(
&BROADCAST,
5,
&[7],
ConfirmedServiceChoice::READ_PROPERTY,
&[0x0C],
)
.await;
assert!(
matches!(routed, Err(Error::Remote(RemoteError::Abort { reason }))
if reason == AbortReason::TSM_TIMEOUT),
"{routed:?}"
);
let request = decode_npdu(frames.try_recv().unwrap().npdu).unwrap();
assert!(matches!(
decode_apdu(request.payload),
Ok(Apdu::ConfirmedRequest(_))
));
client.stop().await.unwrap();
}
struct GroupRuleLink {
link: LoopbackTransport,
groups: GroupDestinations,
}
impl TransportPort for GroupRuleLink {
async fn start(&mut self) -> Result<mpsc::Receiver<ReceivedNpdu>, Error> {
self.link.start().await
}
async fn stop(&mut self) -> Result<(), Error> {
self.link.stop().await
}
async fn send_unicast(&self, npdu: &[u8], mac: &[u8]) -> Result<(), Error> {
self.link.send_unicast(npdu, mac).await
}
async fn send_broadcast(&self, npdu: &[u8]) -> Result<(), Error> {
self.link.send_broadcast(npdu).await
}
fn local_receive_apdu_capacity(&self) -> u16 {
self.link.local_receive_apdu_capacity()
}
fn local_mac(&self) -> &[u8] {
self.link.local_mac()
}
fn is_group_destination(&self, mac: &[u8]) -> bool {
self.groups.contains(mac)
}
}
async fn refuses_each_group_form(groups: GroupDestinations, forms: &[Vec<u8>], unicast: &[u8]) {
let (link, mut peer) = LoopbackTransport::pair(CLIENT.to_vec(), PEER.to_vec());
let mut frames = peer.start().await.unwrap();
let config = ClientConfig {
apdu_timeout_ms: 20,
apdu_retries: 0,
..ClientConfig::default()
};
let mut client = BACnetClient::start(config, GroupRuleLink { link, groups })
.await
.unwrap();
for mac in forms {
let refused = client
.confirmed_request(mac, ConfirmedServiceChoice::READ_PROPERTY, &[0x0C])
.await;
assert!(
matches!(&refused, Err(Error::Encoding(message))
if message.contains("not to a broadcast or group address")),
"{mac:02X?}: {refused:?}"
);
}
assert!(
frames.try_recv().is_err(),
"a refused request reached the link"
);
assert_eq!(client.tsm.lock().await.pending_count(), 0);
let sent = client
.confirmed_request(unicast, ConfirmedServiceChoice::READ_PROPERTY, &[0x0C])
.await;
assert!(
matches!(sent, Err(Error::Remote(RemoteError::Abort { reason }))
if reason == AbortReason::TSM_TIMEOUT),
"{sent:?}"
);
assert!(frames.try_recv().is_ok());
client.stop().await.unwrap();
}
#[tokio::test(start_paused = true)]
async fn a_confirmed_request_to_any_bip_group_address_is_refused() {
let bip = BipTransport::new(Ipv4Addr::LOCALHOST, 0xBAC0, Ipv4Addr::new(192, 168, 1, 255));
let forms = [
vec![255, 255, 255, 255, 0xBA, 0xC0],
vec![255, 255, 255, 255, 0xBA, 0xC1],
vec![192, 168, 1, 255, 0xBA, 0xC0],
vec![192, 168, 1, 255, 0xBA, 0xC1],
vec![224, 0, 0, 1, 0xBA, 0xC0],
vec![239, 255, 255, 250, 0x07, 0x6C],
];
refuses_each_group_form(
bip.group_destinations(),
&forms,
&[192, 168, 1, 7, 0xBA, 0xC0],
)
.await;
}
#[cfg(feature = "ipv6")]
#[tokio::test]
async fn a_confirmed_request_to_an_18_octet_bip6_address_is_refused() {
use bacnet_transport::bip6::{Bip6ResolutionCounters, Bip6Transport};
use std::net::Ipv6Addr;
let mac = |ip: &str, port: u16| {
let ip: Ipv6Addr = ip.parse().unwrap();
[&ip.octets()[..], &port.to_be_bytes()].concat()
};
let forms = [
mac("ff02::bac0", 0xBAC0),
mac("ff02::1", 0xBAC0),
mac("ff05::1:3", 0x1234),
mac("::1", 0xBAC0),
];
let transport = Bip6Transport::new(Ipv6Addr::LOCALHOST, 0, Some(10));
let config = ClientConfig {
apdu_timeout_ms: 20,
apdu_retries: 0,
..ClientConfig::default()
};
let mut client = BACnetClient::start(config, transport).await.unwrap();
for mac in &forms {
assert!(!client.transport().is_group_destination(mac));
let refused = client
.confirmed_request(mac, ConfirmedServiceChoice::READ_PROPERTY, &[0x0C])
.await;
assert!(
matches!(&refused, Err(Error::Transport(error))
if error.kind() == std::io::ErrorKind::InvalidInput),
"{mac:02X?}: {refused:?}"
);
}
assert_eq!(client.tsm.lock().await.pending_count(), 0);
assert_eq!(
client.transport().resolution_counters(),
Bip6ResolutionCounters::default(),
"no resolution started"
);
client.stop().await.unwrap();
}
#[tokio::test(start_paused = true)]
async fn a_routed_request_to_this_networks_broadcast_mac_is_refused() {
let (mut transport, mut peer) = LoopbackTransport::pair(CLIENT.to_vec(), PEER.to_vec());
transport.set_broadcast_mac(BROADCAST.to_vec());
let mut frames = peer.start().await.unwrap();
let mut client = BACnetClient::start(ClientConfig::default(), transport)
.await
.unwrap();
let number: &LocalNetworkNumber = client.network.local_network_number();
number.publish(NetworkNumber::configured(5).unwrap());
let refused = client
.confirmed_request_routed(
&PEER,
5,
&BROADCAST,
ConfirmedServiceChoice::READ_PROPERTY,
&[0x0C],
)
.await;
assert!(
matches!(&refused, Err(Error::Encoding(message))
if message.contains("not to a broadcast or group address")),
"{refused:?}"
);
assert!(
frames.try_recv().is_err(),
"a refused request reached the link"
);
assert_eq!(client.tsm.lock().await.pending_count(), 0);
assert_eq!(client.tsm.lock().await.coordinated_active_count(), 0);
client.stop().await.unwrap();
}