mod actions;
pub mod control_policy;
mod dispatch;
mod engine;
mod hosted_host;
mod local_control;
mod local_delivery;
mod options;
mod send_warning;
use std::ops::{Deref, DerefMut};
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
pub use bacnet_router_core::router::Counters;
use bacnet_router_core::{Instant, Mac};
use bacnet_transport::port::{DataAttribute, TransportPort};
use bacnet_types::error::Error;
use bacnet_types::MacAddr;
use bytes::Bytes;
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use crate::layer::ReceivedNetworkControl;
use crate::layer::{AdmissionReceiver, AdmissionSender, QueueAdmissionCounters, ReceivedApdu};
pub use control_policy::{ControlAuthContext, ControlAuthorizer, ControlClass};
pub use control_policy::{ControlDecisionCounters, ControlGate, ControlPolicy};
pub use control_policy::{ControlServiceCounters, ControlTrust};
use dispatch::{Outputs, PortDispatch, Shared};
use engine::{Engine, EngineGuard, RouterClock, PORTS};
use hosted_host::PortGroups;
pub use options::{LocalApduReceiver, NetworkControlReceiver, RouterOptions, StartedRouter};
#[cfg(test)]
use {
bacnet_encoding::npdu::{encode_npdu, Npdu},
bacnet_transport::port::TransportProvenance,
bacnet_types::enums::NetworkMessageType,
bytes::BytesMut,
std::time::Duration,
};
#[derive(Debug)]
enum SendRequest {
Unicast {
npdu: Bytes,
mac: MacAddr,
data_attributes: Vec<DataAttribute>,
},
Broadcast {
npdu: Bytes,
data_attributes: Vec<DataAttribute>,
},
}
pub struct RouterPort<T: TransportPort> {
pub transport: T,
pub network_number: u16,
}
pub struct BACnetRouter {
shared: Arc<Shared>,
dispatch_tasks: Vec<JoinHandle<()>>,
sender_tasks: Vec<JoinHandle<()>>,
aging_task: Option<JoinHandle<()>>,
network_control_ingress_sequence: Arc<AtomicU64>,
}
pub struct RouterTableGuard<'a> {
router: EngineGuard<'a>,
}
impl Deref for RouterTableGuard<'_> {
type Target = crate::router_table::RouterTable;
fn deref(&self) -> &Self::Target {
self.router.table()
}
}
impl DerefMut for RouterTableGuard<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.router.table_mut()
}
}
impl std::fmt::Debug for RouterTableGuard<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.router.table().fmt(f)
}
}
impl BACnetRouter {
pub async fn start<T, A, C>(
ports: Vec<RouterPort<T>>,
options: RouterOptions<A, C>,
) -> Result<StartedRouter<A, C>, Error>
where
T: TransportPort + 'static,
A: LocalApduReceiver,
C: NetworkControlReceiver,
{
let (control_tx, network_control) = if options.wants_network_control() {
let (tx, rx, counters) = AdmissionReceiver::channel(C::TRACKED);
(Some(tx), Some(C::from_queue(rx, counters)))
} else {
(None, None)
};
let gate = Arc::new(options.control_gate());
let (router, rx, counters) =
Self::start_dispatch(ports, A::TRACKED, gate, control_tx).await?;
Ok(StartedRouter {
router,
apdus: A::from_queue(rx, counters),
network_control,
})
}
pub fn control_snapshot(&self) -> control_policy::ControlDecisionCounters {
self.shared.gate.snapshot()
}
pub fn counters(&self) -> Counters {
self.shared.engine.counters()
}
pub fn address_length_drops(&self) -> u64 {
self.counters().address_length_drops
}
pub fn global_broadcast_dadr_drops(&self) -> u64 {
self.counters().global_broadcast_dadr_drops
}
pub fn broadcast_pdu_type_drops(&self) -> u64 {
self.counters().broadcast_pdu_type_drops
}
pub fn group_dadr_drops(&self) -> u64 {
self.counters().group_dadr_drops
}
async fn start_dispatch<T: TransportPort + 'static>(
mut ports: Vec<RouterPort<T>>,
track_depth: bool,
control: Arc<control_policy::ControlGate>,
network_control: Option<AdmissionSender<ReceivedNetworkControl>>,
) -> Result<(Self, mpsc::Receiver<ReceivedApdu>, QueueAdmissionCounters), Error> {
if ports.is_empty() || ports.len() > PORTS {
return Err(Error::Encoding(format!(
"A router needs 1 to {PORTS} ports, not {}",
ports.len()
)));
}
{
let mut seen = std::collections::HashSet::new();
for port in &ports {
if !seen.insert(port.network_number) {
return Err(Error::Encoding(format!(
"Duplicate network number {} in router ports",
port.network_number
)));
}
}
}
let (local_tx, local_rx, counters) = AdmissionReceiver::channel(track_depth);
let mut port_receivers = Vec::new();
let mut configs = Vec::new();
let mut groups = Vec::new();
let mut networks = Vec::new();
for port in &mut ports {
port.transport.prepare_for_routing();
let rx = port.transport.start().await?;
port_receivers.push(rx);
let mac = Mac::from_slice(port.transport.local_mac()).map_err(|_| {
Error::Encoding(format!(
"Router port on network {} has a MAC longer than {} octets",
port.network_number,
bacnet_router_core::MAX_MAC_LEN
))
})?;
configs.push(Engine::port(
port.network_number,
mac,
port.transport.max_npdu(),
));
groups.push(PortGroups {
groups: port.transport.group_destinations(),
local_broadcasts: port.transport.local_broadcast_destinations(),
});
networks.push(port.network_number);
}
let engine = Engine::new(&configs, RouterClock::start())
.map_err(|error| Error::Encoding(error.to_string()))?;
let mut send_txs = Vec::new();
let mut sender_tasks = Vec::new();
for port in ports {
let (send_tx, mut send_rx) = mpsc::channel::<SendRequest>(256);
send_txs.push(send_tx);
let transport = port.transport;
let task = tokio::spawn(async move {
let warnings = send_warning::SendWarnings::default();
while let Some(req) = send_rx.recv().await {
match req {
SendRequest::Unicast {
npdu,
mac,
data_attributes,
} => {
if let Err(e) = transport
.send_unicast_with_data_attributes(&npdu, &mac, &data_attributes)
.await
{
warnings.unicast_failed(&e);
}
}
SendRequest::Broadcast {
npdu,
data_attributes,
} => {
if let Err(e) = transport
.send_broadcast_with_data_attributes(&npdu, &data_attributes)
.await
{
warnings.broadcast_failed(&e);
}
}
}
}
});
sender_tasks.push(task);
}
let network_control_ingress_sequence = Arc::new(AtomicU64::new(0));
let shared = Arc::new(Shared {
engine,
gate: control,
groups,
networks,
warnings: Default::default(),
});
shared.announce(&send_txs);
let outputs = Arc::new(Outputs {
send_txs,
local_tx,
local_control: local_control::LocalControl::new(
network_control,
Arc::clone(&network_control_ingress_sequence),
),
});
let dispatch_tasks = port_receivers
.into_iter()
.enumerate()
.map(|(index, rx)| {
let port = PortDispatch {
shared: Arc::clone(&shared),
outputs: Arc::clone(&outputs),
port: index as u8,
};
tokio::spawn(port.run(rx))
})
.collect();
let aging = Arc::clone(&shared);
let aging_task = tokio::spawn(async move {
let clock = aging.engine.clock();
let mut next = aging.engine.lock().next_deadline();
loop {
match next {
Some(deadline) => tokio::time::sleep_until(clock.instant(deadline)).await,
None => std::future::pending().await,
}
next = aging.sweep();
}
});
Ok((
Self {
shared,
dispatch_tasks,
sender_tasks,
aging_task: Some(aging_task),
network_control_ingress_sequence,
},
local_rx,
counters,
))
}
pub fn table(&self) -> RouterTableGuard<'_> {
RouterTableGuard {
router: self.shared.engine.lock(),
}
}
pub fn now(&self) -> Instant {
self.shared.engine.now()
}
pub async fn stop(&mut self) {
for task in self.dispatch_tasks.drain(..) {
task.abort();
let _ = task.await;
}
for task in self.sender_tasks.drain(..) {
task.abort();
let _ = task.await;
}
if let Some(task) = self.aging_task.take() {
task.abort();
let _ = task.await;
}
self.shared.engine.lock().cancel_discovery();
}
#[cfg(test)]
fn searches(&self) -> (usize, bool) {
let router = self.shared.engine.lock();
(router.discovery().len(), router.discovery().is_cancelled())
}
}
#[cfg(test)]
mod admission_tests;
#[cfg(test)]
mod bip_group_tests;
#[cfg(test)]
mod busy_scope_tests;
#[cfg(test)]
mod claim_tests;
#[cfg(test)]
mod corroboration_tests;
#[cfg(test)]
mod cutover_tests;
#[cfg(test)]
mod envelope_control_tests;
#[cfg(test)]
mod envelope_discovery_tests;
#[cfg(test)]
mod envelope_harness;
#[cfg(test)]
mod fixes_tests;
#[cfg(test)]
mod ingress_harness;
#[cfg(test)]
mod init_routing_table_tests;
#[cfg(test)]
mod mstp_extended_tests;
#[cfg(test)]
mod rb06_convergence_tests;
#[cfg(test)]
mod rb09_control_policy_tests;
#[cfg(test)]
mod reject_tests;
#[cfg(test)]
mod tests;