use super::device_bindings::{DeviceBinding, DeviceBindingTable};
use super::event_forwarding::Reception;
use super::event_forwarding_tests::{
Copy, LOCAL_DEVICE, PEER_A, PEER_B, To, copies, database, destination, encoded, notification,
unconfirmed,
};
use super::event_recipient_routing_tests::{
LITERAL_BROADCAST_MAC, address_recipient, distribute_counted_through,
};
use super::test_transport::{BIP_LOCAL_MAC, SendLog, SentFrame, TestTransport};
use super::*;
use bacnet_encoding::apdu::decode_apdu;
use bacnet_encoding::constructed::decode_event_notification;
use bacnet_encoding::npdu::{Npdu, decode_npdu, encode_npdu};
use bacnet_objects::network_port::{BipPortConfig, NetworkPortObject};
use bacnet_objects::notification_class::NotificationClass;
use bacnet_objects::notification_forwarder::NotificationForwarderObject;
use bacnet_transport::port::{ReceivedNpdu, TransportProvenance};
use bacnet_types::constructed::BACnetRecipient;
use std::net::SocketAddrV4;
const THIS_NETWORK: u16 = 7;
const REMOTE_NETWORK: u16 = 5;
const REMOTE_MAC: [u8; 2] = [0x0E, 0x0F];
const PEER_C: [u8; 6] = [10, 0, 0, 4, 0xBA, 0xC0];
const ROUTER: [u8; 6] = [10, 0, 0, 9, 0xBA, 0xC0];
const PORT_IP: [u8; 4] = [127, 0, 0, 1];
const BROADCAST: Reception = Reception {
group: true,
global: false,
};
fn port_oid() -> ObjectIdentifier {
ObjectIdentifier::new(ObjectType::NETWORK_PORT, 1).unwrap()
}
async fn bounded<F: std::future::Future>(f: F) -> F::Output {
tokio::time::timeout(Duration::from_secs(5), f)
.await
.expect("the server made no progress")
}
fn numbered_recipients() -> NotificationForwarderObject {
let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
for (process, recipient) in [
(1, address_recipient(THIS_NETWORK, &[])),
(2, address_recipient(THIS_NETWORK, &PEER_A)),
(3, address_recipient(REMOTE_NETWORK, &REMOTE_MAC)),
] {
nf.add_destination(destination(recipient, process, false))
.unwrap();
}
nf
}
fn remote_copy() -> Copy {
unconfirmed(To::Remote(REMOTE_NETWORK, REMOTE_MAC.to_vec()), 3)
}
struct Started {
server: BACnetServer<TestTransport>,
inbound: mpsc::Sender<ReceivedNpdu>,
sent: SendLog,
}
impl Started {
async fn new(mut db: ObjectDatabase, port_number: Option<u16>) -> Self {
let (inbound, rx) = mpsc::channel(8);
let mut transport = TestTransport::builder()
.local_mac(&BIP_LOCAL_MAC)
.broadcast_mac(LITERAL_BROADCAST_MAC)
.number_controls()
.inbound(rx);
let mut config = ServerConfig::default();
if let Some(network_number) = port_number {
let config_port = BipPortConfig {
network_number,
ip_address: PORT_IP,
..BipPortConfig::default()
};
db.add(Box::new(
NetworkPortObject::new_bip(1, "Port", config_port).unwrap(),
))
.unwrap();
config.registered_network_port = Some(port_oid());
transport = transport.normal_bip(SocketAddrV4::new(PORT_IP.into(), 47808));
}
let transport = transport.build();
let sent = transport.sent();
let server = BACnetServer::start(config, db, transport).await.unwrap();
Self {
server,
inbound,
sent,
}
}
async fn feed(&self, npdu: &[u8], group: bool) {
self.inbound
.send(ReceivedNpdu {
direct_response: None,
npdu: Bytes::copy_from_slice(npdu),
source_mac: MacAddr::from_slice(&PEER_B),
link_layer_group: group,
data_attributes: Vec::new(),
provenance: TransportProvenance::unverified(),
reply_tx: None,
})
.await
.unwrap();
}
async fn announce(&self, number: u16, flag: u8) -> u16 {
let [high, low] = number.to_be_bytes();
self.feed(&[1, 0x80, 0x13, high, low, flag], true).await;
self.feed(&[1, 0x80, 0x12], false).await;
bounded(self.sent.wait_for_len(1)).await;
let frames = self.sent.take();
match &frames[..] {
[
SentFrame {
npdu,
broadcast: true,
..
},
] if npdu.len() == 6 && npdu[..3] == [1, 0x80, 0x13] => {
u16::from_be_bytes([npdu[3], npdu[4]])
}
_ => panic!("expected one Network-Number-Is broadcast, got {frames:?}"),
}
}
async fn receive(&self, request: &EventNotificationRequest, reception: Reception) -> Vec<Copy> {
let mut apdu = BytesMut::new();
encode_apdu(
&mut apdu,
&Apdu::UnconfirmedRequest(UnconfirmedRequestPdu {
service_choice: UnconfirmedServiceChoice::UNCONFIRMED_EVENT_NOTIFICATION,
service_request: encoded(request),
}),
)
.unwrap();
let mut npdu = BytesMut::new();
encode_npdu(
&mut npdu,
&Npdu {
payload: apdu.freeze(),
..Default::default()
},
)
.unwrap();
self.feed(&npdu, reception.group).await;
let remote_sent = |frames: Vec<SentFrame>| {
frames.iter().any(|frame| {
frame
.decode_npdu()
.destination
.is_some_and(|to| to.network == REMOTE_NETWORK)
})
};
bounded(async {
while !remote_sent(self.sent.frames()) {
let len = self.sent.len();
self.sent.wait_for_len(len + 1).await;
}
})
.await;
copies(&self.sent, request)
}
async fn port_number(&self) -> (u16, u8) {
let db = self.server.database().read().await;
let port = db.get(&port_oid()).unwrap();
let read = |property| match port.read_property(property, None) {
Ok(PropertyValue::Unsigned(value)) => value as u16,
Ok(PropertyValue::Enumerated(value)) => value as u16,
other => panic!("unexpected {property:?}: {other:?}"),
};
(
read(PropertyIdentifier::NETWORK_NUMBER),
read(PropertyIdentifier::NETWORK_NUMBER_QUALITY) as u8,
)
}
async fn stop(mut self) {
self.server.stop().await.unwrap();
}
}
#[tokio::test(start_paused = true)]
async fn a_number_learned_without_a_network_port_makes_this_network_local() {
let started = Started::new(database(vec![numbered_recipients()]), None).await;
assert_eq!(started.announce(THIS_NETWORK, 0).await, THIS_NETWORK);
assert_eq!(
started.receive(¬ification(9), BROADCAST).await,
[remote_copy()]
);
assert_eq!(
started.receive(¬ification(9), Reception::UNICAST).await,
[unconfirmed(To::Local(PEER_A.to_vec()), 2), remote_copy()]
);
assert_eq!(started.announce(8, 1).await, 8);
assert_eq!(
started.receive(¬ification(9), BROADCAST).await,
[
unconfirmed(To::RemoteBroadcast(THIS_NETWORK), 1),
unconfirmed(To::Remote(THIS_NETWORK, PEER_A.to_vec()), 2),
remote_copy(),
]
);
started.stop().await;
}
#[tokio::test(start_paused = true)]
async fn a_registered_port_and_the_routes_share_one_network_number() {
let started = Started::new(database(vec![numbered_recipients()]), Some(THIS_NETWORK)).await;
assert_eq!(
started.receive(¬ification(9), Reception::UNICAST).await,
[unconfirmed(To::Local(PEER_A.to_vec()), 2), remote_copy()]
);
assert_eq!(started.announce(8, 1).await, THIS_NETWORK);
assert_eq!(started.port_number().await, (THIS_NETWORK, 3));
assert_eq!(
started.receive(¬ification(9), BROADCAST).await,
[remote_copy()]
);
started.stop().await;
let started = Started::new(database(vec![numbered_recipients()]), Some(0)).await;
assert_eq!(
started.receive(¬ification(9), BROADCAST).await,
[
unconfirmed(To::RemoteBroadcast(THIS_NETWORK), 1),
unconfirmed(To::Remote(THIS_NETWORK, PEER_A.to_vec()), 2),
remote_copy(),
]
);
assert_eq!(started.announce(THIS_NETWORK, 0).await, THIS_NETWORK);
assert_eq!(started.port_number().await, (THIS_NETWORK, 1));
assert_eq!(
started.receive(¬ification(9), Reception::UNICAST).await,
[unconfirmed(To::Local(PEER_A.to_vec()), 2), remote_copy()]
);
started.stop().await;
}
fn routes(broadcasts: Vec<Bytes>, unicasts: Vec<(Vec<u8>, Bytes)>) -> Vec<(u32, To, bool)> {
let read = |npdu: Bytes| {
let npdu = decode_npdu(npdu).unwrap();
let (confirmed, request) = match decode_apdu(npdu.payload).unwrap() {
Apdu::UnconfirmedRequest(request) => (false, request.service_request),
Apdu::ConfirmedRequest(request) => (true, request.service_request),
other => panic!("expected an event notification, got {other:?}"),
};
let process = decode_event_notification(&request)
.unwrap()
.process_identifier;
(npdu.destination, process, confirmed)
};
let mut routes: Vec<_> = unicasts
.into_iter()
.map(|(mac, npdu)| {
let (destination, process, confirmed) = read(npdu);
let to = match destination {
None => To::Local(mac),
Some(to) => To::Remote(to.network, to.mac_address.to_vec()),
};
(process, to, confirmed)
})
.chain(broadcasts.into_iter().map(|npdu| {
let (destination, process, confirmed) = read(npdu);
let to = match destination {
None => To::LocalBroadcast,
Some(to) if to.mac_address.is_empty() => To::RemoteBroadcast(to.network),
Some(to) => To::Remote(to.network, to.mac_address.to_vec()),
};
(process, to, confirmed)
}))
.collect();
routes.sort_by_key(|(process, ..)| *process);
routes
}
#[tokio::test(start_paused = true)]
async fn notification_class_and_forwarded_copies_to_this_network_go_without_a_dnet() {
let started = Started::new(database(Vec::new()), None).await;
assert_eq!(started.announce(THIS_NETWORK, 0).await, THIS_NETWORK);
let bound = ObjectIdentifier::new(ObjectType::DEVICE, 77).unwrap();
let mut bindings = DeviceBindingTable::new();
bindings
.insert_configured(
DeviceBinding::routed(bound, THIS_NETWORK, PEER_C, ROUTER).unwrap(),
|_| false,
)
.unwrap();
let this_device =
BACnetRecipient::Device(ObjectIdentifier::new(ObjectType::DEVICE, LOCAL_DEVICE).unwrap());
let mut class = NotificationClass::new(0, "NC-0").unwrap();
for (process, recipient, confirmed) in [
(1, address_recipient(THIS_NETWORK, &PEER_A), false),
(2, address_recipient(THIS_NETWORK, &[]), false),
(
3,
address_recipient(THIS_NETWORK, LITERAL_BROADCAST_MAC),
false,
),
(4, address_recipient(THIS_NETWORK, &PEER_B), true),
(5, BACnetRecipient::Device(bound), false),
(6, address_recipient(REMOTE_NETWORK, &REMOTE_MAC), false),
(7, this_device, false),
] {
class
.add_destination(destination(recipient, process, confirmed))
.unwrap();
}
let mut nf = NotificationForwarderObject::new(1, "NF").unwrap();
nf.set_process_identifier_filter(Some(7));
for (process, recipient) in [
(8, address_recipient(THIS_NETWORK, &PEER_A)),
(9, address_recipient(THIS_NETWORK, &[])),
] {
nf.add_destination(destination(recipient, process, false))
.unwrap();
}
let mut db = ObjectDatabase::new();
db.add(Box::new(class)).unwrap();
db.add(Box::new(nf)).unwrap();
let (broadcasts, unicasts, counters) = distribute_counted_through(
started.server.test_network(),
&started.sent,
db,
Arc::new(RwLock::new(bindings)),
DccState::Enable,
)
.await;
assert_eq!(
routes(broadcasts, unicasts),
[
(1, To::Local(PEER_A.to_vec()), false),
(2, To::LocalBroadcast, false),
(3, To::LocalBroadcast, false),
(4, To::Local(PEER_B.to_vec()), true),
(5, To::Local(PEER_C.to_vec()), false),
(6, To::Remote(REMOTE_NETWORK, REMOTE_MAC.to_vec()), false),
(8, To::Local(PEER_A.to_vec()), false),
(9, To::LocalBroadcast, false),
]
);
assert_eq!(counters, EventNotificationCounters::default());
started.stop().await;
}
mod localize;