use super::event_forwarding::Reception;
use super::event_forwarding_bounds_tests::peer;
use super::event_forwarding_tests::{
Forwarding, LOCAL_DEVICE, To, copies, database, destination, notification, unconfirmed,
};
use super::event_recipient_routing_tests::{address_recipient, distribute_counted};
use super::*;
use bacnet_objects::notification_class::NotificationClass;
use bacnet_objects::notification_forwarder::NotificationForwarderObject;
use bacnet_types::constructed::BACnetRecipient;
fn remote(index: usize) -> [u8; 2] {
[0x0E, index as u8]
}
fn capped_forwarders() -> Vec<NotificationForwarderObject> {
(0..4u32)
.map(|n| {
let mut nf = NotificationForwarderObject::new(n + 1, format!("NF-{n}")).unwrap();
let count = if n == 3 { 10 } else { 30 };
for offset in 0..count {
let index = n as usize * 30 + offset;
let recipient = if n == 0 {
address_recipient(0, &peer(index))
} else {
address_recipient(5, &remote(index))
};
nf.add_destination(destination(recipient, index as u32, false))
.unwrap();
}
nf
})
.collect()
}
#[tokio::test]
async fn the_cap_counts_only_copies_the_loop_rules_let_through() {
let forwarding = Forwarding::new(database(capped_forwarders()));
let broadcast = Reception {
group: true,
global: false,
};
let expected: Vec<_> = (30..30 + MAX_FORWARDED_DESTINATIONS)
.map(|index| unconfirmed(To::Remote(5, remote(index).to_vec()), index as u32))
.collect();
assert_eq!(
forwarding.receive(¬ification(5), broadcast).await,
expected
);
assert_eq!(
forwarding.counters(),
EventNotificationCounters {
forwarding_cap_dropped: (70 - MAX_FORWARDED_DESTINATIONS) as u64,
..Default::default()
}
);
}
#[tokio::test]
async fn a_local_notification_under_several_process_identifiers_has_one_cap() {
let this_device =
BACnetRecipient::Device(ObjectIdentifier::new(ObjectType::DEVICE, LOCAL_DEVICE).unwrap());
let mut class = NotificationClass::new(0, "NC-0").unwrap();
for process in [5, 6] {
class
.add_destination(destination(this_device.clone(), process, false))
.unwrap();
}
let node = |index: usize| destination(address_recipient(0, &peer(index)), index as u32, false);
let mut db = ObjectDatabase::new();
db.add(Box::new(class)).unwrap();
for (instance, process, nodes) in [
(1, 5, (0..32).collect::<Vec<_>>()),
(2, 6, std::iter::once(0).chain(32..63).collect()),
(3, 6, (63..73).collect()),
] {
let mut nf = NotificationForwarderObject::new(instance, format!("NF-{instance}")).unwrap();
nf.set_process_identifier_filter(Some(process));
for index in nodes {
nf.add_destination(node(index)).unwrap();
}
db.add(Box::new(nf)).unwrap();
}
let (broadcasts, unicasts, counters) = distribute_counted(
db,
Arc::new(RwLock::new(
super::device_bindings::DeviceBindingTable::new(),
)),
DccState::Enable,
)
.await;
assert!(broadcasts.is_empty());
let reached: Vec<Vec<u8>> = unicasts.into_iter().map(|(mac, _)| mac).collect();
let expected: Vec<Vec<u8>> = (0..MAX_FORWARDED_DESTINATIONS)
.map(|index| peer(index).to_vec())
.collect();
assert_eq!(reached, expected);
assert_eq!(
counters,
EventNotificationCounters {
forwarding_cap_dropped: (73 - MAX_FORWARDED_DESTINATIONS) as u64,
..Default::default()
}
);
}
fn device_9() -> BACnetRecipient {
BACnetRecipient::Device(ObjectIdentifier::new(ObjectType::DEVICE, 9).unwrap())
}
fn who_is(frame: &crate::server::test_transport::SentFrame) -> bool {
super::event_recipient_routing_tests::is_who_is(frame)
}
#[tokio::test(start_paused = true)]
async fn a_copy_that_waited_for_its_device_keeps_to_the_loop_rules() {
for (group, sent) in [(true, false), (false, true)] {
let mut nf = NotificationForwarderObject::new(1, "NF-1").unwrap();
nf.add_destination(destination(device_9(), 40, false))
.unwrap();
let forwarding = Forwarding::new(database(vec![nf]));
let reception = Reception {
group,
global: false,
};
forwarding.deliver(¬ification(5), reception).await;
assert!(forwarding.sent.frames().iter().all(who_is));
forwarding.sent.clear();
forwarding.hear_i_am(9, &peer(9)).await;
let expected = if sent {
vec![unconfirmed(To::Local(peer(9).to_vec()), 40)]
} else {
Vec::new()
};
assert_eq!(copies(&forwarding.sent, ¬ification(5)), expected);
assert_eq!(forwarding.counters(), EventNotificationCounters::default());
}
}
#[tokio::test(start_paused = true)]
async fn a_copy_that_waited_for_its_device_draws_on_the_cap_as_it_goes() {
let forwarders = [(1, 0..21), (2, 21..42), (3, 42..64)]
.into_iter()
.map(|(instance, nodes)| {
let mut nf =
NotificationForwarderObject::new(instance, format!("NF-{instance}")).unwrap();
if instance == 1 {
nf.add_destination(destination(device_9(), 999, false))
.unwrap();
}
for index in nodes {
nf.add_destination(destination(
address_recipient(5, &remote(index)),
index as u32,
false,
))
.unwrap();
}
nf
})
.collect();
let forwarding = Forwarding::new(database(forwarders));
let unicast = Reception {
group: false,
global: false,
};
forwarding.deliver(¬ification(5), unicast).await;
let (who_is, sent): (Vec<_>, Vec<_>) = forwarding.sent.take().into_iter().partition(who_is);
assert_eq!((who_is.len(), sent.len()), (1, MAX_FORWARDED_DESTINATIONS));
forwarding.hear_i_am(9, &peer(9)).await;
assert!(
forwarding.sent.is_empty(),
"Device 9's copy is past the cap"
);
assert_eq!(
forwarding.counters(),
EventNotificationCounters {
forwarding_cap_dropped: 1,
..Default::default()
}
);
}
#[tokio::test(start_paused = true)]
async fn the_cap_warning_goes_out_at_most_once_an_interval() {
use bacnet_transport::warn_throttle::WARNING_INTERVAL;
let forwarding = Forwarding::new(database(capped_forwarders()));
for _ in 0..3 {
let broadcast = Reception {
group: true,
global: false,
};
forwarding.receive(¬ification(5), broadcast).await;
}
let throttle = forwarding.cap_warning();
assert_eq!(
throttle.note(),
None,
"the first capped notification warned"
);
tokio::time::advance(WARNING_INTERVAL).await;
assert_eq!(
throttle.note(),
Some(4),
"two held back, the check above, and this one"
);
}