use super::*;
use crate::discovery::{DiscoveredDevice, RoutedDeviceConfig};
use bacnet_types::enums::{ObjectType, PropertyIdentifier, Segmentation, UnconfirmedServiceChoice};
use bacnet_types::primitives::ObjectIdentifier;
const ROUTER: [u8; 6] = [2; 6];
const DADR: [u8; 6] = [3; 6];
const ROUTED_DEVICE: u32 = 7;
fn not_one_device() -> [(u16, Vec<u8>); 3] {
[
(0, DADR.to_vec()),
(u16::MAX, DADR.to_vec()),
(DNET, Vec::new()),
]
}
async fn client() -> (
BACnetClient<CaptureTransport>,
mpsc::Sender<ReceivedNpdu>,
mpsc::UnboundedReceiver<SentFrame>,
) {
let (transport, inbound, outbound) = harness(&[1; 6], 1490);
let config = ClientConfig {
apdu_timeout_ms: 20,
apdu_retries: 0,
..ClientConfig::default()
};
let client = BACnetClient::start(config, transport).await.unwrap();
(client, inbound, outbound)
}
fn object() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::ANALOG_INPUT, 1).unwrap()
}
fn routed_row(dnet: u16, dadr: &[u8]) -> DiscoveredDevice {
DiscoveredDevice {
object_identifier: ObjectIdentifier::new(ObjectType::DEVICE, ROUTED_DEVICE).unwrap(),
mac_address: MacAddr::from_slice(&ROUTER),
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
max_segments_accepted: None,
vendor_id: 0,
last_seen: std::time::Instant::now(),
source_network: Some(dnet),
source_address: Some(MacAddr::from_slice(dadr)),
}
}
#[tokio::test]
async fn routed_confirmed_request_to_no_single_device_fails_before_any_state() {
let (mut client, _inbound, mut outbound) = client().await;
for (dnet, dadr) in not_one_device() {
client
.device_table
.lock()
.await
.upsert(routed_row(dnet, &dadr));
let pv = PropertyIdentifier::PRESENT_VALUE;
let results = [
client
.confirmed_request_routed(
&ROUTER,
dnet,
&dadr,
ConfirmedServiceChoice::READ_PROPERTY,
&[0x0c],
)
.await
.map(drop),
client
.read_property_routed(&ROUTER, dnet, &dadr, object(), pv, None)
.await
.map(drop),
client
.read_property_from_device(ROUTED_DEVICE, object(), pv, None)
.await
.map(drop),
client
.subscribe_cov_to_device(ROUTED_DEVICE, 1, object(), false, Some(60))
.await,
];
for result in results {
assert!(
matches!(result, Err(Error::Encoding(_))),
"DNET {dnet}, DADR {dadr:?}: {result:?}"
);
}
assert!(
outbound.try_recv().is_err(),
"DNET {dnet}: a frame went out"
);
assert_eq!(client.tsm.lock().await.pending_count(), 0);
assert_eq!(client.routed_path_limits.entry_count(), 0);
}
client
.broadcast_network_unconfirmed(UnconfirmedServiceChoice::WHO_IS, &[], DNET)
.await
.unwrap();
let frame = outbound.try_recv().unwrap();
let destination = decode_npdu(frame.npdu).unwrap().destination.unwrap();
assert_eq!(destination.network, DNET);
assert!(destination.mac_address.is_empty());
client.stop().await.unwrap();
}
#[tokio::test]
async fn path_apis_and_routed_requests_share_the_remote_dnet_bound() {
let (client, inbound, mut outbound) = client().await;
for dnet in [0, u16::MAX] {
let refused = [
client
.configure_routed_path_max_npdu(&ROUTER, dnet, 1497)
.await,
client.clear_routed_path_limit(&ROUTER, dnet).await,
];
for result in refused {
assert!(
matches!(&result, Err(Error::Encoding(m)) if m == "routed DNET must be in 1..=65534"),
"DNET {dnet}: {result:?}"
);
}
}
assert_eq!(client.routed_path_limits.entry_count(), 0);
for dnet in [1, u16::MAX - 1] {
client
.configure_routed_path_max_npdu(&ROUTER, dnet, 1497)
.await
.unwrap();
client.clear_routed_path_limit(&ROUTER, dnet).await.unwrap();
}
let client = Arc::new(client);
let highest = u16::MAX - 1;
let task = routed_request(
Arc::clone(&client),
ROUTER.to_vec(),
highest,
DADR.to_vec(),
vec![0x0c],
);
let frame = outbound.recv().await.unwrap();
let destination = decode_npdu(frame.npdu.clone())
.unwrap()
.destination
.unwrap();
assert_eq!(destination.network, highest);
let request = confirmed_request(frame, &ROUTER);
inject_simple_ack(&inbound, &ROUTER, highest, &DADR, request.invoke_id).await;
assert!(task.await.unwrap().unwrap().is_empty());
let mut client = Arc::try_unwrap(client).ok().unwrap();
client.stop().await.unwrap();
}
#[tokio::test]
async fn add_routed_device_refuses_a_destination_no_request_could_reach() {
let (mut client, _inbound, _outbound) = client().await;
let config = |remote_network, remote_mac: Vec<u8>| RoutedDeviceConfig {
instance: ROUTED_DEVICE,
router_mac: ROUTER.to_vec(),
remote_network,
remote_mac,
max_apdu_length: 1476,
segmentation_supported: Segmentation::NONE,
max_segments_accepted: None,
};
let too_long = vec![3; bacnet_npdu::MAX_MAC_LEN + 1];
for (dnet, dadr) in [
(0, DADR.to_vec()),
(u16::MAX, DADR.to_vec()),
(DNET, too_long),
] {
let result = client.add_routed_device(config(dnet, dadr)).await;
assert!(matches!(result, Err(Error::Encoding(_))), "{result:?}");
assert!(client.get_device(ROUTED_DEVICE).await.is_none());
}
let longest = vec![3; bacnet_npdu::MAX_MAC_LEN];
client
.add_routed_device(config(u16::MAX - 1, longest))
.await
.unwrap();
assert!(client.get_device(ROUTED_DEVICE).await.is_some());
client.stop().await.unwrap();
}