use std::sync::Arc;
use bacnet_router_core::npdu::{AddressField, Malformed};
use bacnet_router_core::router::{Discard, Frame, Received};
use bacnet_router_core::PortId;
use bacnet_transport::port::{ReceivedNpdu, TransportProvenance};
use tokio::sync::mpsc;
use tracing::{debug, warn};
use super::actions::drain;
use super::control_policy::ControlGate;
use super::engine::Engine;
use super::hosted_host::{EngineWarnings, HostedHost, PortGroups};
use super::local_control::LocalControl;
use super::local_delivery::Delivery;
use super::SendRequest;
use crate::layer::{AdmissionSender, ReceivedApdu};
pub(super) struct Shared {
pub(super) engine: Engine,
pub(super) gate: Arc<ControlGate>,
pub(super) groups: Vec<PortGroups>,
pub(super) networks: Vec<u16>,
pub(super) warnings: EngineWarnings,
}
impl Shared {
pub(super) fn announce(&self, send_txs: &[mpsc::Sender<SendRequest>]) {
let mut router = self.engine.lock();
router.start();
drain(&mut router, &[], send_txs);
}
pub(super) fn sweep(&self) -> Option<bacnet_router_core::Instant> {
let mut host = HostedHost::new(
&self.groups,
&self.gate,
&self.warnings,
TransportProvenance::unverified(),
);
let mut router = self.engine.lock();
router.on_deadline(self.engine.now(), &mut host);
drain(&mut router, &[], &[]);
router.next_deadline()
}
}
pub(super) struct Outputs {
pub(super) send_txs: Vec<mpsc::Sender<SendRequest>>,
pub(super) local_tx: AdmissionSender<ReceivedApdu>,
pub(super) local_control: LocalControl,
}
enum Outcome {
Local(Delivery),
ToRouter,
Discarded(Discard),
Handled,
}
pub(super) struct PortDispatch {
pub(super) shared: Arc<Shared>,
pub(super) outputs: Arc<Outputs>,
pub(super) port: u8,
}
impl PortDispatch {
pub(super) async fn run(self, mut rx: mpsc::Receiver<ReceivedNpdu>) {
while let Some(received) = rx.recv().await {
self.dispatch(received);
}
}
pub(super) fn dispatch(&self, received: ReceivedNpdu) {
let (shared, outputs) = (&*self.shared, &*self.outputs);
let port = PortId(self.port);
let frame = Frame {
port,
source: &received.source_mac,
link_group: received.link_layer_group,
npdu: &received.npdu,
};
let mut host = HostedHost::new(
&shared.groups,
&shared.gate,
&shared.warnings,
received.provenance,
);
let outcome = {
let mut router = shared.engine.lock();
let failures = router.counters().write_failures;
let outcome = match router.receive(frame, shared.engine.now(), &mut host) {
Received::Local(local) => Outcome::Local(Delivery::of(&local)),
Received::ToRouter(_) => Outcome::ToRouter,
Received::Discarded(reason) => Outcome::Discarded(reason),
Received::Handled => Outcome::Handled,
};
if router.counters().write_failures != failures
&& !matches!(outcome, Outcome::Discarded(Discard::Unwritable))
{
warn!(port = self.port, "Failed to encode forwarded NPDU");
}
drain(&mut router, &received.data_attributes, &outputs.send_txs);
outcome
};
let network = shared
.networks
.get(usize::from(self.port))
.copied()
.unwrap_or_default();
match outcome {
Outcome::Local(delivery) => {
let _ = outputs
.local_tx
.try_send_apdu(delivery.application(received, network));
}
Outcome::ToRouter => outputs.local_control.deliver(&received),
Outcome::Discarded(reason) => {
log_discard(reason, self.port, received.source_mac.len());
}
Outcome::Handled => {}
}
}
}
fn log_discard(reason: Discard, port: u8, source_len: usize) {
match reason {
Discard::SourceTooLong { .. } => warn!(
octets = source_len,
limit = bacnet_router_core::MAX_MAC_LEN,
"Dropping a frame whose link-layer source MAC is over the limit"
),
Discard::AddressTooLong { field, length } => {
let octet = match field {
AddressField::Destination => "DLEN",
AddressField::Source => "SLEN",
};
warn!(
error = %format_args!(
"NPDU {octet}={length} exceeds the {}-octet address limit",
bacnet_router_core::MAX_MAC_LEN
),
port,
"Router refused an NPDU"
);
}
Discard::Malformed(fault) => {
let error = match fault {
Malformed::Truncated => "NPDU truncated",
Malformed::Version => "unsupported BACnet protocol version",
Malformed::ReservedNetwork => "NPDU names a reserved network number",
Malformed::EmptySource => "NPDU source SLEN=0 is invalid",
_ => "NPDU malformed",
};
warn!(error, port, "Router decode failed");
}
Discard::GlobalWithDadr => {
warn!("Dropping a global broadcast NPDU that also names a DADR")
}
Discard::BroadcastNotUnconfirmed => {
warn!("Dropping a broadcast NPDU whose APDU isn't an Unconfirmed-Request")
}
Discard::GroupDadr => warn!(
port,
"Dropping a routed NPDU for a group DADR its port's broadcast doesn't stand for, \
or that isn't an Unconfirmed-Request"
),
Discard::Unwritable => warn!(port, "Failed to encode forwarded NPDU"),
Discard::UnknownPort
| Discard::HopCount
| Discard::Denied
| Discard::BadMessage
| Discard::Unroutable => {}
_ => debug!(port, ?reason, "Router dropped a frame"),
}
}