use std::ops::{Deref, DerefMut};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Mutex, MutexGuard, PoisonError};
use bacnet_router_core::router::{ConfigError, Counters, Limits, Options, PortConfig};
use bacnet_router_core::{Duration, Instant, Router, Storage};
use bacnet_transport::port::NpduLimits;
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_router_core::MAX_MAC_LEN;
type HostedStorage = Storage<PORTS, ROUTE_CAP, HOLD_RECORDS, SEARCHES, SCRATCH>;
pub(super) type HostedRouter =
Router<Box<HostedStorage>, PORTS, ROUTE_CAP, HOLD_RECORDS, SEARCHES, SCRATCH>;
const OPTIONS: Options = Options::HOSTED;
const _: () = assert!(
OPTIONS.route_cap as usize == ROUTE_CAP,
"the hosted route cap is ROUTE_CAP"
);
#[derive(Debug, Clone, Copy)]
pub(super) struct RouterClock {
origin: tokio::time::Instant,
}
impl RouterClock {
pub(super) fn start() -> Self {
Self {
origin: tokio::time::Instant::now(),
}
}
pub(super) fn now(&self) -> Instant {
let elapsed = tokio::time::Instant::now().saturating_duration_since(self.origin);
Instant::ZERO + Duration::saturating_from_core(elapsed)
}
pub(super) fn instant(&self, at: Instant) -> tokio::time::Instant {
let offset = std::time::Duration::from_nanos(at.as_nanos());
self.origin
.checked_add(offset)
.unwrap_or_else(|| self.origin + std::time::Duration::from_secs(86_400 * 365))
}
}
pub(super) struct Engine {
router: Mutex<HostedRouter>,
clock: RouterClock,
published: Published,
}
impl Engine {
pub(super) fn new(ports: &[PortConfig], clock: RouterClock) -> 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_router_core::Mac,
max_npdu: NpduLimits,
) -> PortConfig {
PortConfig {
network,
mac,
max_npdu: Limits {
unicast: max_npdu.unicast.max(Limits::MIN),
broadcast: max_npdu.broadcast.max(Limits::MIN),
},
}
}
pub(super) fn lock(&self) -> EngineGuard<'_> {
let router = self.router.lock().unwrap_or_else(PoisonError::into_inner);
EngineGuard {
before: router.counters(),
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) -> RouterClock {
self.clock
}
pub(super) fn now(&self) -> Instant {
self.clock.now()
}
}
pub(super) struct EngineGuard<'a> {
router: MutexGuard<'a, HostedRouter>,
published: &'a Published,
before: Counters,
}
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 counters = self.router.counters();
if counters != self.before {
self.published.store(&counters);
}
}
}
macro_rules! published {
($($field:ident),+ $(,)?) => {
#[derive(Debug, Default)]
struct Published {
$($field: AtomicU64,)+
}
impl Published {
const FIELDS: usize = [$(stringify!($field)),+].len();
fn store(&self, counters: &Counters) {
$(self.$field.store(counters.$field, Ordering::Release);)+
}
fn load(&self) -> Counters {
let mut counters = Counters::default();
$(counters.$field = self.$field.load(Ordering::Acquire);)+
counters
}
}
};
}
published!(
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,
);
const _: () = assert!(
std::mem::size_of::<Counters>() == Published::FIELDS * std::mem::size_of::<u64>(),
"publish every count in the engine's Counters"
);
#[inline(never)]
fn boxed_storage() -> Box<HostedStorage> {
Box::new(Storage::new())
}