use super::ingress::{ApduReceiver, Ingress};
use super::*;
use std::collections::HashMap;
use std::sync::Mutex;
use std::task::Poll;
use tokio::sync::mpsc;
const QUEUE_CAPACITY: usize = 256;
const APDU_SOURCE_CAPACITY: usize = 16;
type AdmissionSource = (Option<u16>, MacAddr);
fn apdu_source(apdu: &ReceivedApdu) -> AdmissionSource {
(apdu.ingress_network, apdu.source_mac.clone())
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct QueueAdmissionSnapshot {
pub current_depth: usize,
pub high_water: usize,
pub full_drops: u64,
pub fairness_drops: u64,
pub closed_drops: u64,
}
#[derive(Debug, Clone, Default)]
pub struct QueueAdmissionCounters(Arc<Mutex<QueueAdmissionState>>);
#[derive(Debug, Default)]
struct QueueAdmissionState {
snapshot: QueueAdmissionSnapshot,
queued_by_source: HashMap<AdmissionSource, usize>,
}
impl QueueAdmissionState {
fn dequeued(&mut self, source: Option<AdmissionSource>) {
self.snapshot.current_depth -= 1;
if let Some(ref source) = source {
let count = self
.queued_by_source
.get_mut(source)
.expect("queued APDU source must be accounted");
*count -= 1;
if *count == 0 {
self.queued_by_source.remove(source);
}
self.assert_source_depth();
}
}
fn assert_source_depth(&self) {
debug_assert!(self.queued_by_source.len() <= self.snapshot.current_depth);
debug_assert!(self.snapshot.current_depth <= QUEUE_CAPACITY);
debug_assert_eq!(
self.queued_by_source.values().sum::<usize>(),
self.snapshot.current_depth
);
debug_assert!(
self.queued_by_source
.values()
.all(|count| (1..=APDU_SOURCE_CAPACITY).contains(count))
);
}
}
impl QueueAdmissionCounters {
pub fn snapshot(&self) -> QueueAdmissionSnapshot {
self.0
.lock()
.expect("queue admission counters poisoned")
.snapshot
}
}
#[derive(Debug)]
pub struct AdmissionReceiver<T> {
rx: mpsc::Receiver<T>,
counters: QueueAdmissionCounters,
source_key: Option<fn(&T) -> AdmissionSource>,
}
impl<T> AdmissionReceiver<T> {
pub(crate) fn channel(
track_depth: bool,
) -> (
AdmissionSender<T>,
mpsc::Receiver<T>,
QueueAdmissionCounters,
) {
let (tx, rx) = AdmissionSender::channel(track_depth);
let counters = tx.counters.clone();
(tx, rx, counters)
}
pub(crate) fn from_parts(rx: mpsc::Receiver<T>, counters: QueueAdmissionCounters) -> Self {
Self {
rx,
counters,
source_key: None,
}
}
pub fn counters(&self) -> QueueAdmissionCounters {
self.counters.clone()
}
pub async fn recv(&mut self) -> Option<T> {
std::future::poll_fn(|cx| {
let mut state = self
.counters
.0
.lock()
.expect("queue admission counters poisoned");
let result = self.rx.poll_recv(cx);
if let Poll::Ready(Some(item)) = &result {
state.dequeued(self.source_key.map(|source_key| source_key(item)));
}
result
})
.await
}
pub fn try_recv(&mut self) -> Result<T, mpsc::error::TryRecvError> {
let mut state = self
.counters
.0
.lock()
.expect("queue admission counters poisoned");
let result = self.rx.try_recv();
if let Ok(item) = &result {
state.dequeued(self.source_key.map(|source_key| source_key(item)));
}
result
}
pub fn close(&mut self) {
let _snapshot = self
.counters
.0
.lock()
.expect("queue admission counters poisoned");
self.rx.close();
}
}
impl AdmissionReceiver<ReceivedApdu> {
pub(crate) fn from_apdu_parts(
rx: mpsc::Receiver<ReceivedApdu>,
counters: QueueAdmissionCounters,
) -> Self {
Self {
rx,
counters,
source_key: Some(apdu_source),
}
}
}
impl<T> Drop for AdmissionReceiver<T> {
fn drop(&mut self) {
let mut state = self
.counters
.0
.lock()
.expect("queue admission counters poisoned");
self.rx.close();
state.snapshot.current_depth = 0;
state.queued_by_source = HashMap::new();
}
}
pub(crate) struct AdmissionSender<T> {
tx: mpsc::Sender<T>,
counters: QueueAdmissionCounters,
track_depth: bool,
}
impl<T> Clone for AdmissionSender<T> {
fn clone(&self) -> Self {
Self {
tx: self.tx.clone(),
counters: self.counters.clone(),
track_depth: self.track_depth,
}
}
}
impl<T> AdmissionSender<T> {
fn channel(track_depth: bool) -> (Self, mpsc::Receiver<T>) {
let (tx, rx) = mpsc::channel(QUEUE_CAPACITY);
(
Self {
tx,
counters: QueueAdmissionCounters::default(),
track_depth,
},
rx,
)
}
pub(crate) fn try_send(&self, item: T) -> Result<(), mpsc::error::TrySendError<T>> {
self.try_send_from(item, None)
}
fn try_send_from(
&self,
item: T,
source: Option<AdmissionSource>,
) -> Result<(), mpsc::error::TrySendError<T>> {
if !self.track_depth {
let result = self.tx.try_send(item);
if let Err(error) = &result {
let mut state = self
.counters
.0
.lock()
.expect("queue admission counters poisoned");
let drops = match error {
mpsc::error::TrySendError::Full(_) => &mut state.snapshot.full_drops,
mpsc::error::TrySendError::Closed(_) => &mut state.snapshot.closed_drops,
};
*drops = drops.saturating_add(1);
}
return result;
}
let mut state = self
.counters
.0
.lock()
.expect("queue admission counters poisoned");
if !self.tx.is_closed()
&& source.as_ref().is_some_and(|source| {
state.queued_by_source.get(source).copied().unwrap_or(0) >= APDU_SOURCE_CAPACITY
})
{
state.snapshot.fairness_drops = state.snapshot.fairness_drops.saturating_add(1);
return Err(mpsc::error::TrySendError::Full(item));
}
let result = self.tx.try_send(item);
match &result {
Ok(()) if self.track_depth => {
state.snapshot.current_depth += 1;
state.snapshot.high_water =
state.snapshot.high_water.max(state.snapshot.current_depth);
if let Some(source) = source {
*state.queued_by_source.entry(source).or_default() += 1;
state.assert_source_depth();
}
}
Ok(()) => {}
Err(mpsc::error::TrySendError::Full(_)) => {
state.snapshot.full_drops = state.snapshot.full_drops.saturating_add(1);
}
Err(mpsc::error::TrySendError::Closed(_)) => {
state.snapshot.closed_drops = state.snapshot.closed_drops.saturating_add(1);
}
}
result
}
}
impl AdmissionSender<ReceivedApdu> {
pub(crate) fn try_send_apdu(
&self,
apdu: ReceivedApdu,
) -> Result<(), mpsc::error::TrySendError<()>> {
let source = self.track_depth.then(|| apdu_source(&apdu));
self.try_send_from(apdu, source)
.map_err(|error| match error {
mpsc::error::TrySendError::Full(_) => mpsc::error::TrySendError::Full(()),
mpsc::error::TrySendError::Closed(_) => mpsc::error::TrySendError::Closed(()),
})
}
}
impl<T: TransportPort + 'static> NetworkLayer<T> {
pub fn enable_network_control_receiver(
&mut self,
) -> Result<mpsc::Receiver<ReceivedNetworkControl>, Error> {
self.enable_network_control(false).map(|(rx, _)| rx)
}
pub fn enable_network_control_receiver_with_admission(
&mut self,
) -> Result<AdmissionReceiver<ReceivedNetworkControl>, Error> {
let (rx, counters) = self.enable_network_control(true)?;
Ok(AdmissionReceiver::from_parts(rx, counters))
}
fn enable_network_control(
&mut self,
track_depth: bool,
) -> Result<
(
mpsc::Receiver<ReceivedNetworkControl>,
QueueAdmissionCounters,
),
Error,
> {
if self.started {
return Err(Error::Encoding(
"network-control receiver must be enabled before NetworkLayer::start".into(),
));
}
if self.network_control_tx.is_some() {
return Err(Error::Encoding(
"network-control receiver is already enabled".into(),
));
}
let (tx, rx) = AdmissionSender::channel(track_depth);
let counters = tx.counters.clone();
self.network_control_tx = Some(tx);
Ok((rx, counters))
}
pub async fn start(&mut self) -> Result<ApduReceiver, Error> {
if self.started {
return Err(Error::Encoding("NetworkLayer is already started".into()));
}
let npdu_rx = self.transport.start().await?;
self.started = true;
self.local_broadcasts = self.transport.local_broadcast_destinations();
let npdu_rx = Arc::new(Mutex::new(npdu_rx));
self.receiver_queue = Arc::downgrade(&npdu_rx);
Ok(ApduReceiver::new(npdu_rx, self.ingress()))
}
fn ingress(&mut self) -> Ingress {
Ingress::new(
Arc::clone(&self.node_totals),
self.network_control_tx.take(),
Arc::clone(&self.network_control_ingress_sequence),
)
}
pub async fn start_with_admission(&mut self) -> Result<AdmissionReceiver<ReceivedApdu>, Error> {
let (rx, counters) = self.start_dispatch().await?;
Ok(AdmissionReceiver::from_apdu_parts(rx, counters))
}
async fn start_dispatch(
&mut self,
) -> Result<(mpsc::Receiver<ReceivedApdu>, QueueAdmissionCounters), Error> {
if self.started {
return Err(Error::Encoding("NetworkLayer is already started".into()));
}
let mut npdu_rx = self.transport.start().await?;
self.started = true;
self.local_broadcasts = self.transport.local_broadcast_destinations();
let mut ingress = self.ingress();
let (apdu_tx, apdu_rx) = AdmissionSender::channel(true);
let counters = apdu_tx.counters.clone();
let dispatch_task = tokio::spawn(async move {
while let Some(received) = npdu_rx.recv().await {
let Some(apdu) = ingress.take(received) else {
continue;
};
match apdu_tx.try_send_apdu(apdu) {
Ok(()) | Err(mpsc::error::TrySendError::Full(_)) => {}
Err(mpsc::error::TrySendError::Closed(_)) => break,
}
}
});
self.dispatch_task = Some(dispatch_task);
Ok((apdu_rx, counters))
}
}
#[cfg(test)]
#[path = "layer_fairness_tests.rs"]
mod fairness_tests;