use bacnet_router_core::router::{Claim, ControlClass, Event, Host};
use bacnet_transport::port::TransportProvenance;
use bacnet_transport::warn_throttle::WarnThrottle;
use bacnet_types::MacAddr;
use tracing::{debug, info, warn};
use super::control_policy::{ControlAuthContext, ControlGate, ControlTrust};
use crate::router_table::ROUTE_CAP;
#[derive(Debug, Default)]
pub(super) struct EngineWarnings {
hop_count: WarnThrottle,
too_long: WarnThrottle,
}
pub(super) struct HostedHost<'a> {
gate: &'a ControlGate,
warnings: &'a EngineWarnings,
provenance: TransportProvenance,
}
impl<'a> HostedHost<'a> {
pub(super) fn new(
gate: &'a ControlGate,
warnings: &'a EngineWarnings,
provenance: TransportProvenance,
) -> Self {
Self {
gate,
warnings,
provenance,
}
}
}
impl Host for HostedHost<'_> {
fn authorize(&mut self, claim: &Claim<'_>) -> bool {
let ctx = ControlAuthContext {
port_idx: usize::from(claim.port.0),
port_network: claim.port_network,
source_mac: MacAddr::from_slice(claim.source),
provenance: self.provenance.clone(),
trust: ControlTrust::from_provenance(&self.provenance),
message_type: claim.message_type,
vendor_id: claim.vendor_id,
source_net: claim.snet,
dest_net: claim.dnet,
target_nets: claim.targets.iter().collect(),
targets_omitted: claim.targets_omitted,
};
self.gate.decide(&ctx, claim.class)
}
fn observe(&mut self, event: Event) {
match event {
Event::HopCountSpent { network } => match self.warnings.hop_count.note() {
Some(dropped) => warn!(
network,
dropped,
"Router dropped NPDUs whose hop count was spent (since the last warning)"
),
None => debug!(network, "Router dropped an NPDU whose hop count was spent"),
},
Event::TooLong {
port,
octets,
limit,
rejected,
} => {
let port = port.0;
match self.warnings.too_long.note() {
Some(refused) => warn!(
port,
octets,
limit,
?rejected,
refused,
"Router refused NPDUs too long for their egress port (since the last warning)"
),
None => debug!(
port,
octets,
limit,
?rejected,
"Router refused an NPDU too long for its egress port"
),
}
}
event => log(event),
}
}
}
fn log(event: Event) {
match event {
Event::Learned {
network,
port,
class,
} => {
let port = port.0;
if class == ControlClass::InitAck {
debug!(network, port, "Learned route from Init-Routing-Table-Ack");
} else {
debug!(network, port, "Learned route from I-Am-Router-To-Network");
}
}
Event::Flapping { network, flaps } => warn!(
network,
flap_count = flaps,
"Route flapping detected — network changed ports {} times in 60s",
flaps
),
Event::RouteCap { class } => match class {
ControlClass::IAm => warn!(
"Router table learned routes cap ({ROUTE_CAP}) reached, ignoring further networks"
),
ControlClass::InitMgmt => {
warn!("Init-Routing-Table: route cap reached, ignoring further entries")
}
ControlClass::ICouldBe => warn!(
"Router table routes cap ({ROUTE_CAP}) reached, I-Could-Be-Router offer not stored"
),
_ => {}
},
Event::RejectReceived {
network, reason, ..
} => warn!(
network,
reason = reason.to_raw(),
"Received Reject-Message-To-Network"
),
Event::Marked {
network,
busy: true,
} => debug!(
network,
"Router busy — marked network as congested (30s timer)"
),
Event::Marked {
network,
busy: false,
} => debug!(network, "Router available — cleared congestion"),
Event::UpdateApplied { network, port } => debug!(
network,
port = port.0,
"Applied route from Init-Routing-Table management update"
),
Event::UpdateRemoved { network } => {
debug!(network, "Init-Routing-Table purged learned route")
}
Event::UnknownPortId { network, port_id } => warn!(
network,
port_id, "Init-Routing-Table: unknown Port ID, entry skipped"
),
Event::ICouldBe {
network,
performance_index,
stored,
} => {
debug!(
network,
performance_index, "Received I-Could-Be-Router-To-Network"
);
if stored {
debug!(
network,
"Stored potential route from I-Could-Be-Router-To-Network"
);
}
}
Event::EstablishConnection {
network,
termination_time,
} => info!(
network,
termination_time_minutes = termination_time,
"Received Establish-Connection-To-Network (PTP not implemented)"
),
Event::DisconnectIgnored { network } => debug!(
network,
"Received Disconnect-Connection-To-Network (PTP not implemented; route removal ignored)"
),
Event::SecurityMessage { message_type } => debug!(
message_type = message_type.to_raw(),
"Router received security network message (not implemented)"
),
Event::UnknownMessage { message_type } => debug!(
message_type = message_type.to_raw(),
"Router rejecting unknown network message type"
),
Event::NumberConflict {
local,
reported,
configured: true,
..
} => warn!(
local_network = local,
peer_network = reported,
"Network number conflict: port configured as {} but peer reports {} (configured)",
local,
reported
),
Event::NumberConflict {
local, reported, ..
} => debug!(
local_network = local,
peer_network = reported,
"Network-Number-Is from peer (learned, differs from local)"
),
Event::OwnOrigin { network } => debug!(
network,
"Router refused an NPDU it originated itself; no reject sent"
),
Event::Purged { network } => debug!(network, "Purged stale route"),
Event::PendingExpired { network } => {
debug!(network, "Expired hardened pending challenger")
}
Event::DiscoveryExpired { network } => {
debug!(network, "Expired discovery solicitation")
}
_ => debug!(?event, "Router engine event"),
}
}