use super::counters::bump;
use super::host::{Event, Host};
use super::tasks::{Task, TaskKind};
use super::Storage;
use crate::message::{MessageType, RejectReason};
use crate::npdu::Npdu;
use crate::{swap_apart, Mac, PortId};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[must_use = "carry out each send, and poll until Idle"]
pub enum Action<'a> {
Send(Send<'a>),
Idle,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct Send<'a> {
pub port: PortId,
pub to: Dest<'a>,
pub npdu: &'a [u8],
pub priority: u8,
pub kind: Kind,
pub cause: Cause,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Dest<'a> {
Unicast(&'a [u8]),
Broadcast,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Kind {
Forward,
Global,
Relay(MessageType),
Reject(RejectReason),
Answer(MessageType),
Announce(MessageType),
Solicit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Cause {
Ingress,
Local,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum SendRefusal {
Full,
TooLong,
PortDown,
Other,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Tag {
Forward,
Global,
Relay,
Reject,
Answer,
Announce,
Solicit,
}
#[derive(Debug, Clone, Copy)]
pub(super) enum Target<'m> {
Toward(PortId, &'m [u8]),
Unicast(PortId, &'m [u8]),
Broadcast(PortId),
AllBut(PortId),
}
#[derive(Debug, Clone, Copy)]
pub(super) struct Current {
active: bool,
len: usize,
tag: Tag,
code: u8,
local: bool,
priority: u8,
fan_out: bool,
port: PortId,
next: u16,
broadcast: bool,
to: Mac,
pub(super) table_ack: bool,
}
impl Current {
const NONE: Self = Self {
active: false,
len: 0,
tag: Tag::Forward,
code: 0,
local: false,
priority: 0,
fan_out: false,
port: PortId(0),
next: 0,
broadcast: false,
to: Mac::EMPTY,
table_ack: false,
};
pub(super) fn new(
len: usize,
kind: Kind,
cause: Cause,
priority: u8,
target: Target<'_>,
) -> Self {
let (tag, code) = match kind {
Kind::Forward => (Tag::Forward, 0),
Kind::Global => (Tag::Global, 0),
Kind::Relay(code) => (Tag::Relay, code.to_raw()),
Kind::Reject(reason) => (Tag::Reject, reason.to_raw()),
Kind::Answer(code) => (Tag::Answer, code.to_raw()),
Kind::Announce(code) => (Tag::Announce, code.to_raw()),
Kind::Solicit => (Tag::Solicit, 0),
};
let mut current = Self {
active: true,
len,
tag,
code,
local: cause == Cause::Local,
priority,
..Self::NONE
};
let mac = |octets: &[u8]| Mac::from_slice(octets).unwrap_or(Mac::EMPTY);
match target {
Target::Toward(port, to) => {
current.port = port;
current.broadcast = to.is_empty();
current.to = mac(to);
}
Target::Unicast(port, to) => {
current.port = port;
current.to = mac(to);
}
Target::Broadcast(port) => {
current.port = port;
current.broadcast = true;
}
Target::AllBut(port) => {
current.port = port;
current.fan_out = true;
}
}
current
}
fn has_more(&self, ports: usize) -> bool {
self.active
&& (!self.fan_out
|| (usize::from(self.next)..ports).any(|port| port != usize::from(self.port.0)))
}
fn kind(&self) -> Kind {
match self.tag {
Tag::Forward => Kind::Forward,
Tag::Global => Kind::Global,
Tag::Relay => Kind::Relay(MessageType::from_raw(self.code)),
Tag::Reject => Kind::Reject(RejectReason::from_raw(self.code)),
Tag::Answer => Kind::Answer(MessageType::from_raw(self.code)),
Tag::Announce => Kind::Announce(MessageType::from_raw(self.code)),
Tag::Solicit => Kind::Solicit,
}
}
}
const QUEUE: usize = 4;
#[derive(Debug, Clone, Copy)]
pub(super) struct Outputs {
pub(super) current: Current,
tasks: [Task; QUEUE],
len: usize,
last_table_ack: bool,
}
impl Outputs {
pub(super) const NEW: Self = Self {
current: Current::NONE,
tasks: [Task::EMPTY; QUEUE],
len: 0,
last_table_ack: false,
};
pub(super) fn pending(&self, ports: usize) -> usize {
usize::from(self.current.has_more(ports)) + self.len
}
pub(super) fn push(&mut self, task: Task) {
debug_assert!(self.len < QUEUE, "a call queues at most two tasks");
if let Some(slot) = self.tasks.get_mut(self.len) {
*slot = task;
self.len += 1;
}
}
pub(super) fn front(&self) -> Option<Task> {
self.tasks[..self.len].first().copied()
}
pub(super) fn advance(&mut self, cursor: u32) {
if let Some(task) = self.tasks[..self.len].first_mut() {
task.cursor = cursor;
}
}
pub(super) fn pop(&mut self) {
if self.len > 0 {
for at in 1..self.len {
swap_apart(&mut self.tasks, at - 1, at);
}
self.len -= 1;
self.tasks[self.len] = Task::EMPTY;
}
}
}
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 drop_undrained(&mut self) {
let pending = self.out.pending(self.port_count);
if pending > 0 {
self.counters.undrained = self.counters.undrained.saturating_add(pending as u64);
self.out = Outputs::NEW;
}
}
pub(super) fn write_now(&mut self, npdu: &Npdu<'_>, kind: Kind, target: Target<'_>) -> bool {
debug_assert_eq!(
self.out.pending(self.port_count),
0,
"a frame's output comes first"
);
match npdu.write(&mut self.scratch) {
Ok(len) => {
self.out.current = Current::new(len, kind, Cause::Ingress, npdu.priority, target);
true
}
Err(_) => {
bump(&mut self.counters.write_failures);
false
}
}
}
pub(super) fn write_fan_out(
&mut self,
npdu: &Npdu<'_>,
kind: Kind,
except: PortId,
host: &mut impl Host,
) -> bool {
if !self.write_now(npdu, kind, Target::AllBut(except)) {
return false;
}
let octets = self.out.current.len;
let ports = &self.ports[..self.port_count];
for (index, config) in ports.iter().enumerate() {
let limit = config.max_npdu.get(true);
if index != usize::from(except.0) && octets > limit {
host.observe(Event::TooLong {
port: PortId(index as u8),
octets,
limit,
rejected: None,
});
}
}
true
}
pub(super) fn poll(&mut self) -> Action<'_> {
loop {
if let Some((port, broadcast)) = self.next_target() {
self.out.last_table_ack = self.out.current.table_ack;
let current = &self.out.current;
return Action::Send(Send {
port,
to: if broadcast {
Dest::Broadcast
} else {
Dest::Unicast(current.to.as_slice())
},
npdu: &self.scratch[..current.len],
priority: current.priority,
kind: current.kind(),
cause: if current.local {
Cause::Local
} else {
Cause::Ingress
},
});
}
if !self.write_task() {
return Action::Idle;
}
}
}
fn next_target(&mut self) -> Option<(PortId, bool)> {
let current = &mut self.out.current;
if !current.active {
return None;
}
let ports = &self.ports[..self.port_count];
let len = current.len;
let fits = |port: PortId, broadcast: bool| {
ports
.get(usize::from(port.0))
.is_some_and(|config| len <= config.max_npdu.get(broadcast))
};
if !current.fan_out {
current.active = false;
if fits(current.port, current.broadcast) {
return Some((current.port, current.broadcast));
}
bump(&mut self.counters.egress_too_long);
return None;
}
while usize::from(current.next) < ports.len() {
let port = PortId(current.next as u8);
current.next += 1;
if port == current.port {
continue;
}
if fits(port, true) {
return Some((port, true));
}
bump(&mut self.counters.egress_too_long);
}
current.active = false;
None
}
pub(super) fn refused(&mut self) {
bump(&mut self.counters.refused);
if core::mem::take(&mut self.out.last_table_ack)
&& self
.out
.front()
.is_some_and(|task| task.kind == TaskKind::TableAck)
{
self.out.pop();
}
}
}