use bacnet_router_core::router::{Group, GroupClassifier};
use bacnet_transport::port::GroupDestinations;
#[derive(Debug, Clone, Default)]
pub(super) struct PortGroups {
pub(super) groups: GroupDestinations,
pub(super) local_broadcasts: GroupDestinations,
}
impl GroupClassifier for PortGroups {
const NONE: Self = Self {
groups: GroupDestinations::unknown(),
local_broadcasts: GroupDestinations::unknown(),
};
fn classify(&self, mac: &[u8]) -> Group {
if !self.groups.contains(mac) {
Group::No
} else if self.local_broadcasts.contains(mac) {
Group::Broadcast
} else {
Group::Refuse
}
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicU8, Ordering};
use bacnet_npdu::Mac;
use bacnet_npdu::NpduLimits;
use bacnet_router_core::PortId;
use bacnet_router_core::router::{Action, Dest, Discard, Frame, Permissive, Received};
use super::*;
use crate::router::engine::{Engine, tokio_clock};
#[tokio::test]
async fn engine_keeps_custom_transport_predicates_live_after_installation() {
let state = Arc::new(AtomicU8::new(0));
let groups = Arc::clone(&state);
let local = Arc::clone(&state);
let rules = PortGroups {
groups: GroupDestinations::new(move |mac| {
mac == [42] && groups.load(Ordering::SeqCst) != 0
}),
local_broadcasts: GroupDestinations::new(move |mac| {
mac == [42] && local.load(Ordering::SeqCst) == 2
}),
};
let engine = Engine::new(
&[
Engine::port(1, Mac::from_array([1]), NpduLimits::default(), rules),
Engine::port(
2,
Mac::from_array([2]),
NpduLimits::default(),
PortGroups::default(),
),
],
tokio_clock(),
super::super::engine::OPTIONS,
)
.unwrap();
let npdu = [1, 0x20, 0, 1, 1, 42, 255, 0x10, 8];
let mut drops = 0;
for value in [0, 1, 2, 1, 0] {
state.store(value, Ordering::SeqCst);
let mut router = engine.lock();
let outcome = router.receive(
Frame {
port: PortId(1),
source: &[7],
link_group: false,
npdu: &npdu,
},
engine.now(),
&mut Permissive,
);
if value == 1 {
drops += 1;
assert_eq!(outcome, Received::Discarded(Discard::GroupDadr));
} else {
assert_eq!(outcome, Received::Handled);
let Action::Send(send) = router.poll_action() else {
panic!("missing relay")
};
assert_eq!(send.port, PortId(0));
assert_eq!(
send.to,
if value == 2 {
Dest::Broadcast
} else {
Dest::Unicast(&[42])
}
);
}
assert!(matches!(router.poll_action(), Action::Idle));
drop(router);
let totals = engine.counters();
assert_eq!(totals.group_dadr_drops, drops);
assert_eq!(totals.refused, 0);
}
}
}