use std::ops::{Deref, DerefMut};
use std::sync::{Mutex, MutexGuard, PoisonError};
use bacnet_npdu::NpduLimits;
use bacnet_router_core::router::{
ConfigError, Counters as EngineCounters, MIN_PORT_LIMIT, Options, PortConfig,
};
use bacnet_router_core::{Instant, Router, Storage};
use bacnet_time::HostClock;
use super::Counters;
use super::port_groups::PortGroups;
use crate::totals::totals;
use crate::router_table::{HOLD_RECORDS, ROUTE_CAP};
pub(super) const PORTS: usize = 255;
pub(super) const SEARCHES: usize = 256;
pub(super) const SCRATCH: usize = 65_535 + 2 + 1 + bacnet_npdu::MAX_MAC_LEN;
pub(super) const DUPLICATE_RECORD: usize = 512;
type HostedStorage =
Storage<PORTS, ROUTE_CAP, HOLD_RECORDS, SEARCHES, SCRATCH, PortGroups, DUPLICATE_RECORD>;
pub(super) type HostedRouter = Router<
Box<HostedStorage>,
PORTS,
ROUTE_CAP,
HOLD_RECORDS,
SEARCHES,
SCRATCH,
PortGroups,
DUPLICATE_RECORD,
>;
pub(super) const OPTIONS: Options = Options::HOSTED;
const _: () = assert!(
matches!(OPTIONS.route_cap, Some(cap) if cap as usize == ROUTE_CAP),
"the hosted route cap is ROUTE_CAP"
);
pub(super) fn tokio_clock() -> HostClock {
let now = || tokio::time::Instant::now().into_std();
HostClock::new(now()).reading(now)
}
pub(super) struct Engine {
router: Mutex<HostedRouter>,
clock: HostClock,
published: Published,
}
impl Engine {
pub(super) fn new(
ports: &[PortConfig<PortGroups>],
clock: HostClock,
options: Options,
) -> Result<Self, ConfigError> {
let router = Router::new(boxed_storage(), ports, options, clock.now())?;
Ok(Self {
router: Mutex::new(router),
clock,
published: Published::default(),
})
}
pub(super) fn port(
network: u16,
mac: bacnet_npdu::Mac,
max_npdu: NpduLimits,
groups: PortGroups,
) -> PortConfig<PortGroups> {
PortConfig::new(network, mac, Self::limits(max_npdu), groups)
}
pub(super) fn limits(reported: NpduLimits) -> NpduLimits {
NpduLimits {
unicast: reported.unicast.max(MIN_PORT_LIMIT),
broadcast: reported.broadcast.max(MIN_PORT_LIMIT),
}
}
pub(super) fn lock(&self) -> EngineGuard<'_> {
let router = self.router.lock().unwrap_or_else(PoisonError::into_inner);
EngineGuard {
router,
published: &self.published,
}
}
pub(super) fn counters(&self) -> Counters {
self.published.load()
}
#[cfg(test)]
pub(super) fn is_locked(&self) -> bool {
matches!(
self.router.try_lock(),
Err(std::sync::TryLockError::WouldBlock)
)
}
pub(super) fn clock(&self) -> HostClock {
self.clock
}
pub(super) fn now(&self) -> Instant {
self.clock.now()
}
}
pub(super) struct EngineGuard<'a> {
router: MutexGuard<'a, HostedRouter>,
published: &'a Published,
}
impl Deref for EngineGuard<'_> {
type Target = HostedRouter;
fn deref(&self) -> &HostedRouter {
&self.router
}
}
impl DerefMut for EngineGuard<'_> {
fn deref_mut(&mut self) -> &mut HostedRouter {
&mut self.router
}
}
impl Drop for EngineGuard<'_> {
fn drop(&mut self) {
let counts = self.router.take_counters();
if counts != EngineCounters::default() {
self.published.add(&counts);
}
}
}
totals!(
Published: EngineCounters => Counters {
address_length_drops,
global_broadcast_dadr_drops,
broadcast_pdu_type_drops,
group_dadr_drops,
malformed,
bad_messages,
unknown_port,
hop_count_drops,
denied,
unroutable_rejects,
rejects,
solicitations,
write_failures,
egress_too_long,
refused,
undrained,
busy_announced,
available_announced,
duplicate_drops,
own_source_drops,
not_next_hop,
originated,
}
);
#[inline(never)]
fn boxed_storage() -> Box<HostedStorage> {
Box::new(Storage::new())
}
#[cfg(test)]
mod tests {
use bacnet_npdu::Mac;
use bacnet_npdu::NpduLimits;
use bacnet_router_core::PortId;
use bacnet_router_core::router::{Frame, Permissive};
use super::*;
use std::sync::atomic::Ordering;
fn engine() -> Engine {
let port = |network, mac| {
Engine::port(
network,
Mac::from_array([mac]),
NpduLimits::default(),
PortGroups::default(),
)
};
Engine::new(&[port(1, 1), port(2, 2)], tokio_clock(), OPTIONS).unwrap()
}
#[tokio::test]
async fn totals_count_from_start_as_each_guard_drains_the_engine() {
let engine = engine();
let short = [0x01];
let malformed = Frame {
port: PortId(0),
source: &[9],
link_group: false,
npdu: &short,
};
for frames in 1..=3u64 {
let mut router = engine.lock();
assert_eq!(router.counters().malformed, 0, "the last guard took them");
let _ = router.receive(malformed, engine.now(), &mut Permissive);
assert_eq!(router.counters().malformed, 1);
drop(router);
assert_eq!(engine.counters().malformed, frames);
}
}
#[test]
fn totals_saturate() {
let published = Published::default();
published.malformed.store(u64::MAX - 1, Ordering::Relaxed);
let mut counts = EngineCounters::default();
counts.malformed = 5;
counts.rejects = 2;
published.add(&counts);
let totals = published.load();
assert_eq!((totals.malformed, totals.rejects), (u64::MAX, 2));
}
}