use std::cell::RefCell;
use std::collections::{BTreeMap, BTreeSet};
use std::net::SocketAddr;
use std::rc::Rc;
use std::task::{Context, Waker};
use tokio::sync::{mpsc, oneshot};
use crate::core::{Connection as CoreConnection, ConnectionId, IntroId, StreamRef, Timestamp};
use crate::error::{AcceptError, AuthError, ConnectError, ConnectionLost, IntroError};
use crate::identity::{Identity, PublicKeyOf};
use crate::packet::Handshake;
pub(crate) fn now() -> std::time::Instant {
tokio::time::Instant::now().into_std()
}
#[derive(Debug, Default)]
pub(crate) struct Wakers {
next: u64,
parked: BTreeMap<u64, Waker>,
}
impl Wakers {
pub(crate) fn key(&mut self) -> u64 {
let key = self.next;
self.next += 1;
key
}
pub(crate) fn park(&mut self, key: u64, cx: &Context<'_>) {
match self.parked.get_mut(&key) {
Some(existing) if existing.will_wake(cx.waker()) => {}
slot => {
let waker = cx.waker().clone();
match slot {
Some(existing) => *existing = waker,
None => {
self.parked.insert(key, waker);
}
}
}
}
}
pub(crate) fn unpark(&mut self, key: u64) {
self.parked.remove(&key);
}
pub(crate) fn is_empty(&self) -> bool {
self.parked.is_empty()
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.parked.len()
}
#[must_use = "the wakers must be woken after the cell borrow ends (§16.8)"]
pub(crate) fn take_all(&mut self) -> Vec<Waker> {
std::mem::take(&mut self.parked).into_values().collect()
}
}
pub(crate) struct WakerSlot<F: FnMut(u64)> {
key: u64,
release: F,
}
impl<F: FnMut(u64)> WakerSlot<F> {
pub(crate) fn new(key: u64, release: F) -> Self {
Self { key, release }
}
pub(crate) fn key(&self) -> u64 {
self.key
}
}
impl<F: FnMut(u64)> Drop for WakerSlot<F> {
fn drop(&mut self) {
(self.release)(self.key);
}
}
pub(crate) struct ConnCell<S: Handshake> {
pub(crate) core: Option<CoreConnection<S>>,
pub(crate) remote_address: SocketAddr,
pub(crate) closed: Option<ConnectionLost>,
pub(crate) closed_wakers: Wakers,
pub(crate) notifications: NotificationSlots,
pub(crate) notification_wakers: Wakers,
pub(crate) dirty: bool,
pub(crate) handles: usize,
pub(crate) blocked_readers: BTreeMap<StreamRef, Wakers>,
pub(crate) blocked_writers: BTreeMap<StreamRef, Wakers>,
pub(crate) blocked_ackers: BTreeMap<StreamRef, Wakers>,
pub(crate) finished_senders: BTreeSet<StreamRef>,
pub(crate) peer_resets: BTreeMap<StreamRef, u64>,
pub(crate) settled_wakers: Wakers,
pub(crate) stream_openers: [Wakers; 2],
pub(crate) stream_acceptors: [Wakers; 2],
pub(crate) message_readers: Wakers,
pub(crate) datagram_readers: Wakers,
pub(crate) message_senders: Wakers,
}
impl<S: Handshake> ConnCell<S> {
pub(crate) fn new(core: CoreConnection<S>, remote_address: SocketAddr) -> Self {
Self {
core: Some(core),
remote_address,
closed: None,
closed_wakers: Wakers::default(),
notifications: NotificationSlots::default(),
notification_wakers: Wakers::default(),
dirty: true,
handles: 0,
blocked_readers: BTreeMap::new(),
blocked_writers: BTreeMap::new(),
peer_resets: BTreeMap::new(),
blocked_ackers: BTreeMap::new(),
finished_senders: BTreeSet::new(),
settled_wakers: Wakers::default(),
stream_openers: Default::default(),
stream_acceptors: Default::default(),
message_readers: Wakers::default(),
datagram_readers: Wakers::default(),
message_senders: Wakers::default(),
}
}
#[must_use = "the wakers must be woken after the cell borrow ends (§16.8)"]
pub(crate) fn note_send_finished(&mut self, r: StreamRef) -> Vec<Waker> {
let Some(wakers) = self.blocked_ackers.get_mut(&r) else {
return Vec::new();
};
let woken = wakers.take_all();
self.finished_senders.insert(r);
woken
}
pub(crate) fn is_established(&self) -> bool {
self.core
.as_ref()
.is_some_and(CoreConnection::is_established)
}
#[must_use = "the wakers must be woken after the cell borrow ends (§16.8)"]
pub(crate) fn take_all_stream_wakers(&mut self) -> Vec<Waker> {
let mut woken = Vec::new();
for wakers in self.blocked_readers.values_mut() {
woken.extend(wakers.take_all());
}
for wakers in self.blocked_writers.values_mut() {
woken.extend(wakers.take_all());
}
for wakers in self.blocked_ackers.values_mut() {
woken.extend(wakers.take_all());
}
woken.extend(self.settled_wakers.take_all());
for wakers in &mut self.stream_openers {
woken.extend(wakers.take_all());
}
for wakers in &mut self.stream_acceptors {
woken.extend(wakers.take_all());
}
woken.extend(self.message_readers.take_all());
woken.extend(self.datagram_readers.take_all());
woken.extend(self.message_senders.take_all());
woken
}
#[cfg(test)]
pub(crate) fn stream_waker_entries(&self) -> (usize, usize) {
(self.blocked_readers.len(), self.blocked_writers.len())
}
#[cfg(test)]
pub(crate) fn sugar_waker_entries(&self) -> (usize, usize, usize) {
(
self.message_readers.len(),
self.datagram_readers.len(),
self.message_senders.len(),
)
}
}
pub(crate) fn release_waker_slot(map: &mut BTreeMap<StreamRef, Wakers>, r: StreamRef, key: u64) {
let std::collections::btree_map::Entry::Occupied(mut entry) = map.entry(r) else {
return;
};
entry.get_mut().unpark(key);
if entry.get().is_empty() {
entry.remove();
}
}
pub(crate) fn wake_settled<S: Handshake>(cell: &RefCell<ConnCell<S>>) {
let woken = cell.borrow_mut().settled_wakers.take_all();
for waker in woken {
waker.wake();
}
}
pub(crate) fn close_now<S: Handshake>(
shell: &Rc<dyn ShellLink>,
cell: &RefCell<ConnCell<S>>,
id: ConnectionId,
code: u64,
reason: &[u8],
) {
let mutated = {
let mut cell = cell.borrow_mut();
match cell.core.as_mut() {
Some(core) => {
core.close(now(), code, reason);
cell.dirty = true;
true
}
None => false,
}
};
if mutated {
shell.mark_dirty(id);
}
}
pub(crate) trait ShellLink {
fn release(&self) -> bool;
fn mark_dirty(&self, id: ConnectionId);
fn acquire(&self);
}
impl<I: Identity> ShellLink for Shell<I> {
fn release(&self) -> bool {
Shell::release(self)
}
fn mark_dirty(&self, id: ConnectionId) {
self.send(Command::Dirty(id));
}
fn acquire(&self) {
Shell::acquire(self);
}
}
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Notification {
AddressMoved {
from: SocketAddr,
to: SocketAddr,
},
Contested,
ContestCleared,
}
#[derive(Debug, Default)]
pub(crate) struct NotificationSlots {
address_moved: Option<(SocketAddr, SocketAddr)>,
address_moved_gen: u64,
contested: Option<u64>,
contest_cleared: Option<u64>,
next_gen: u64,
}
impl NotificationSlots {
fn bump(&mut self) -> u64 {
let generation = self.next_gen;
self.next_gen = self.next_gen.saturating_add(1);
generation
}
pub(crate) fn address_moved(&mut self, from: SocketAddr, to: SocketAddr) {
let generation = self.bump();
match &mut self.address_moved {
Some((_, unclaimed_to)) => *unclaimed_to = to,
None => self.address_moved = Some((from, to)),
}
self.address_moved_gen = generation;
}
pub(crate) fn contested(&mut self) {
self.contested = Some(self.bump());
}
pub(crate) fn contest_cleared(&mut self) {
self.contest_cleared = Some(self.bump());
}
pub(crate) fn take_oldest(&mut self) -> Option<Notification> {
let candidates = [
self.address_moved.map(|_| self.address_moved_gen),
self.contested,
self.contest_cleared,
];
let (slot, _) = candidates
.iter()
.enumerate()
.filter_map(|(slot, generation)| generation.map(|g| (slot, g)))
.min_by_key(|(_, generation)| *generation)?;
match slot {
0 => self
.address_moved
.take()
.map(|(from, to)| Notification::AddressMoved { from, to }),
1 => {
self.contested = None;
Some(Notification::Contested)
}
_ => {
self.contest_cleared = None;
Some(Notification::ContestCleared)
}
}
}
}
pub(crate) enum PendingOutcome<I: Identity> {
Waiting,
Ready(super::connection::Connection<I::Suite>),
Failed(ConnectError),
}
pub(crate) struct PendingSlot<I: Identity> {
pub(crate) outcome: PendingOutcome<I>,
pub(crate) waker: Option<Waker>,
}
impl<I: Identity> PendingSlot<I> {
pub(crate) fn new() -> Self {
Self {
outcome: PendingOutcome::Waiting,
waker: None,
}
}
#[must_use = "the previous outcome and the waker must be disposed of outside the borrow"]
fn resolve(&mut self, outcome: PendingOutcome<I>) -> (PendingOutcome<I>, Option<Waker>) {
let previous = std::mem::replace(&mut self.outcome, outcome);
(previous, self.waker.take())
}
}
pub(crate) fn resolve_slot<I: Identity>(
slot: &Rc<RefCell<PendingSlot<I>>>,
outcome: PendingOutcome<I>,
) {
let (previous, waker) = slot.borrow_mut().resolve(outcome);
drop(previous);
if let Some(waker) = waker {
waker.wake();
}
}
pub(crate) struct ShellState<I: Identity> {
pub(crate) endpoint: crate::core::Endpoint<I>,
pub(crate) handles: usize,
pub(crate) driver_stopped: bool,
}
const HANDLE_DRAIN_BOUND: usize = 100_000;
impl<I: Identity> ShellState<I> {
pub(crate) fn drain_endpoint(&mut self) {
for _ in 0..HANDLE_DRAIN_BOUND {
match self.endpoint.poll_output() {
crate::core::EndpointOutput::Timeout(_) => return,
other => {
debug_assert!(
false,
"a handle-side endpoint verb queued an output (§16.4, ruling 90): \
{}",
match other {
crate::core::EndpointOutput::Transmit(_) => "Transmit",
crate::core::EndpointOutput::IntroReady(..) => "IntroReady",
crate::core::EndpointOutput::ToConnection(..) => "ToConnection",
crate::core::EndpointOutput::HandshakeFailed(..) => "HandshakeFailed",
crate::core::EndpointOutput::Replaced(_) => "Replaced",
crate::core::EndpointOutput::Contested(_) => "Contested",
crate::core::EndpointOutput::Timeout(_) => unreachable!(),
}
);
}
}
}
panic!(
"core::Endpoint::poll_output did not reach Timeout in {HANDLE_DRAIN_BOUND} outputs (§16.4)"
);
}
}
pub(crate) struct Shell<I: Identity> {
pub(crate) state: Rc<RefCell<ShellState<I>>>,
pub(crate) commands: mpsc::UnboundedSender<Command<I>>,
}
impl<I: Identity> Clone for Shell<I> {
fn clone(&self) -> Self {
Self {
state: Rc::clone(&self.state),
commands: self.commands.clone(),
}
}
}
impl<I: Identity> Shell<I> {
pub(crate) fn new(
endpoint: crate::core::Endpoint<I>,
) -> (Self, mpsc::UnboundedReceiver<Command<I>>) {
let (tx, rx) = mpsc::unbounded_channel();
(
Self {
state: Rc::new(RefCell::new(ShellState {
endpoint,
handles: 0,
driver_stopped: false,
})),
commands: tx,
},
rx,
)
}
pub(crate) fn send(&self, command: Command<I>) {
let _ = self.commands.send(command);
}
pub(crate) fn acquire(&self) {
self.state.borrow_mut().handles += 1;
}
pub(crate) fn release(&self) -> bool {
let last = {
let mut state = self.state.borrow_mut();
debug_assert!(state.handles > 0, "a shell handle was released twice");
state.handles = state.handles.saturating_sub(1);
state.handles == 0
};
if last {
self.send(Command::HandlesGone);
}
last
}
pub(crate) fn driver_stopped(&self) -> bool {
self.state.borrow().driver_stopped
}
}
pub(crate) enum Command<I: Identity> {
Connect {
id: ConnectionId,
core: Box<CoreConnection<I::Suite>>,
remote: SocketAddr,
remote_static: PublicKeyOf<I>,
slot: Rc<RefCell<PendingSlot<I>>>,
},
Cancel(ConnectionId),
Accept(oneshot::Sender<super::staged::Intro<I>>),
ReadIdentity(IntroId, oneshot::Sender<Result<PublicKeyOf<I>, IntroError>>),
Authenticate(
IntroId,
crate::identity::PskOf<I>,
oneshot::Sender<Result<(PublicKeyOf<I>, Timestamp), AuthError>>,
),
AcceptChain(
IntroId,
PublicKeyOf<I>,
oneshot::Sender<Result<super::connection::Connection<I::Suite>, AcceptError>>,
),
Reject(IntroId),
Dirty(ConnectionId),
HandlesGone,
}
#[cfg(test)]
mod notification_slots_tests {
use super::*;
fn addr(last: u8) -> SocketAddr {
SocketAddr::from(([203, 0, 113, last], 41_000))
}
#[test]
fn two_unclaimed_roams_merge_into_the_net_move() {
let mut slots = NotificationSlots::default();
slots.address_moved(addr(1), addr(2));
slots.address_moved(addr(2), addr(3));
assert_eq!(
slots.take_oldest(),
Some(Notification::AddressMoved {
from: addr(1),
to: addr(3),
})
);
assert_eq!(slots.take_oldest(), None, "one slot, not a queue");
}
#[test]
fn a_rewritten_slot_takes_the_new_generation() {
let mut slots = NotificationSlots::default();
slots.contested();
slots.contest_cleared();
slots.contested();
assert_eq!(slots.take_oldest(), Some(Notification::ContestCleared));
assert_eq!(slots.take_oldest(), Some(Notification::Contested));
assert_eq!(slots.take_oldest(), None);
}
#[test]
fn kinds_are_handed_over_oldest_first() {
let mut slots = NotificationSlots::default();
slots.contested();
slots.address_moved(addr(1), addr(2));
slots.contest_cleared();
assert_eq!(slots.take_oldest(), Some(Notification::Contested));
assert_eq!(
slots.take_oldest(),
Some(Notification::AddressMoved {
from: addr(1),
to: addr(2),
})
);
assert_eq!(slots.take_oldest(), Some(Notification::ContestCleared));
assert_eq!(slots.take_oldest(), None);
}
#[test]
fn a_storm_of_marks_occupies_one_slot() {
let mut slots = NotificationSlots::default();
for _ in 0..10_000 {
slots.contested();
slots.contest_cleared();
}
assert_eq!(slots.take_oldest(), Some(Notification::Contested));
assert_eq!(slots.take_oldest(), Some(Notification::ContestCleared));
assert_eq!(slots.take_oldest(), None);
}
}