use super::config::Options;
use super::counters::bump;
use super::host::{Group, Host};
use super::tasks::Task;
use super::Storage;
use crate::message::RejectReason;
use crate::npdu::{Address, AddressField, Malformed, Npdu, ParseError, GLOBAL_NETWORK};
use crate::table::{Reachability, Route};
use crate::{Instant, Mac, PortId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Frame<'f> {
pub port: PortId,
pub source: &'f [u8],
pub link_group: bool,
pub npdu: &'f [u8],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use = "a frame may be for the router's own application"]
pub enum Received<'f> {
Local(Local<'f>),
ToRouter(Npdu<'f>),
Handled,
Discarded(Discard),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Local<'f> {
pub apdu: &'f [u8],
pub apdu_offset: usize,
pub source: Option<Address<'f>>,
pub group: bool,
pub global: bool,
pub expecting_reply: bool,
pub priority: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Discard {
UnknownPort,
SourceTooLong {
length: usize,
},
AddressTooLong {
field: AddressField,
length: u8,
},
Malformed(Malformed),
GlobalWithDadr,
BroadcastNotUnconfirmed,
GroupDadr,
HopCount,
Denied,
BadMessage,
Unroutable,
Unwritable,
}
#[derive(Debug, Clone, Copy)]
pub(super) struct Arrival<'f> {
pub(super) port: PortId,
pub(super) network: u16,
pub(super) source: &'f [u8],
pub(super) source_mac: Mac,
pub(super) link_group: bool,
pub(super) now: Instant,
}
fn local<'f>(npdu: &Npdu<'f>, group: bool, global: bool) -> Local<'f> {
Local {
apdu: npdu.payload,
apdu_offset: npdu.payload_offset,
source: npdu.source,
group,
global,
expecting_reply: npdu.expecting_reply,
priority: npdu.priority,
}
}
pub(super) fn is_unconfirmed(apdu: &[u8]) -> bool {
apdu.first().is_some_and(|&first| first >> 4 == 1)
}
impl<const P: usize, const R: usize, const H: usize, const D: usize, const N: usize>
Storage<P, R, H, D, N>
{
pub(super) fn receive<'f>(
&mut self,
frame: Frame<'f>,
now: Instant,
options: &Options,
host: &mut impl Host,
) -> Received<'f> {
self.drop_undrained();
let Some(config) = self.ports[..self.port_count].get(usize::from(frame.port.0)) else {
bump(&mut self.counters.unknown_port);
return Received::Discarded(Discard::UnknownPort);
};
let Ok(source_mac) = Mac::from_slice(frame.source) else {
bump(&mut self.counters.address_length_drops);
return Received::Discarded(Discard::SourceTooLong {
length: frame.source.len(),
});
};
let at = Arrival {
port: frame.port,
network: config.network,
source: frame.source,
source_mac,
link_group: frame.link_group,
now,
};
let npdu = match Npdu::parse(frame.npdu) {
Ok(npdu) => npdu,
Err(ParseError::AddressTooLong {
field,
length,
dnet,
source,
}) => {
bump(&mut self.counters.address_length_drops);
if let Some(dnet) = dnet.filter(|&dnet| dnet != GLOBAL_NETWORK) {
self.reject(&at, source, dnet, RejectReason::ADDRESSING_ERROR, host);
}
return Received::Discarded(Discard::AddressTooLong { field, length });
}
Err(ParseError::Malformed(fault)) => {
bump(&mut self.counters.malformed);
return Received::Discarded(Discard::Malformed(fault));
}
};
if let Some(destination) = npdu.destination {
let global = destination.network == GLOBAL_NETWORK;
if global && !destination.mac.is_empty() {
bump(&mut self.counters.global_broadcast_dadr_drops);
return Received::Discarded(Discard::GlobalWithDadr);
}
let broadcast = global || destination.mac.is_empty();
if broadcast && !npdu.is_network_message() && !is_unconfirmed(npdu.payload) {
bump(&mut self.counters.broadcast_pdu_type_drops);
return Received::Discarded(Discard::BroadcastNotUnconfirmed);
}
}
if let Some(kind) = npdu.message_type {
return self.network_message(&at, &npdu, kind, options, host);
}
self.route_apdu(&at, &npdu, options, host)
}
fn route_apdu<'f>(
&mut self,
at: &Arrival<'f>,
npdu: &Npdu<'f>,
options: &Options,
host: &mut impl Host,
) -> Received<'f> {
let Some(destination) = npdu.destination else {
return Received::Local(local(npdu, at.link_group, false));
};
if destination.network == GLOBAL_NETWORK {
self.forward_global(at, npdu, host);
return Received::Local(local(npdu, true, true));
}
if !destination.mac.is_empty() && self.is_own(destination) {
return Received::Local(local(npdu, false, false));
}
let Some(route) = self.find_route(at, npdu, destination.network, options, host) else {
return Received::Handled;
};
let mut delivered = None;
if route.port() == at.port && route.is_direct() {
let own = &self.ports[usize::from(at.port.0)].mac;
if *own == *destination.mac {
return Received::Local(local(npdu, false, false));
}
if destination.mac.is_empty() {
delivered = Some(local(npdu, true, false));
}
}
let mut npdu = *npdu;
match self.group_of(&route, &npdu, host) {
Group::No => {}
Group::Broadcast if is_unconfirmed(npdu.payload) => {
npdu.destination = Some(Address {
network: destination.network,
mac: &[],
});
if route.port() == at.port {
delivered = Some(local(&npdu, true, false));
}
}
Group::Broadcast | Group::Refuse => return self.group_drop(),
}
let forwarded = self.forward(at, &npdu, &route, host);
delivered.map_or(forwarded, Received::Local)
}
pub(super) fn find_route(
&mut self,
at: &Arrival<'_>,
npdu: &Npdu<'_>,
dnet: u16,
options: &Options,
host: &mut impl Host,
) -> Option<Route> {
let route = self.table.lookup(dnet);
let reachability = self.table.effective_reachability(dnet, at.now);
let Some(route) = route else {
if self
.discovery
.should_solicit(dnet, at.now, options.discovery_cooldown)
{
bump(&mut self.counters.solicitations);
self.out.push(Task::solicit(at.port, dnet));
}
self.reject(
at,
npdu.source,
dnet,
RejectReason::NOT_DIRECTLY_CONNECTED,
host,
);
return None;
};
self.table.touch(dnet, at.now);
let refusal = match reachability {
Some(Reachability::Busy) => RejectReason::ROUTER_BUSY,
Some(Reachability::Unreachable) => RejectReason::NOT_DIRECTLY_CONNECTED,
_ => return Some(route),
};
self.reject(at, npdu.source, dnet, refusal, host);
None
}
pub(super) fn group_of(&self, route: &Route, npdu: &Npdu<'_>, host: &impl Host) -> Group {
match npdu.destination {
Some(destination) if route.is_direct() && !destination.mac.is_empty() => {
host.group(route.port(), destination.mac)
}
_ => Group::No,
}
}
pub(super) fn group_drop<'f>(&mut self) -> Received<'f> {
bump(&mut self.counters.group_dadr_drops);
Received::Discarded(Discard::GroupDadr)
}
}