use std::fmt;
use std::sync::Arc;
use crate::error::{CloseReason, KillReason};
use crate::id::ConnectionId;
use crate::info::{ConnectionInfo, GateTag, Lane};
use crate::sys::KillSlot;
pub trait Gate: Send + Sync + 'static {
type Ticket: Send + 'static;
fn admit(
&self,
info: &ConnectionInfo,
stage: AdmitStage,
) -> Result<Admitted<Self::Ticket>, Refusal>;
fn readmit(&self, ticket: &mut Self::Ticket, info: &ConnectionInfo) -> Result<(), Refusal>;
fn check(&self, ticket: &mut Self::Ticket, write: &WriteCheck) -> Result<(), Denial>;
fn closed(&self, ticket: Self::Ticket, info: &ConnectionInfo, reason: CloseReason) {
let _ = (ticket, info, reason);
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AdmitStage {
Accept,
Adopt,
Dialed,
}
#[derive(Debug)]
pub struct Admitted<T> {
pub lane: Lane,
pub ticket: T,
pub tag: GateTag,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Refusal {
Reset {
code: u16,
},
WithBytes {
code: u16,
bytes: &'static [u8],
},
}
impl Refusal {
#[must_use]
pub fn code(&self) -> u16 {
match self {
Refusal::Reset { code } | Refusal::WithBytes { code, .. } => *code,
}
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WriteCheck {
pub id: ConnectionId,
pub lane: Lane,
pub bytes: u32,
pub response_started: bool,
pub response: Option<GateTag>,
}
impl WriteCheck {
#[must_use]
pub fn new(
id: ConnectionId,
lane: Lane,
bytes: u32,
response_started: bool,
response: Option<GateTag>,
) -> WriteCheck {
WriteCheck {
id,
lane,
bytes,
response_started,
response,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Denial {
pub code: u16,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct AllowAll;
impl Gate for AllowAll {
type Ticket = ();
#[inline(always)]
fn admit(&self, _info: &ConnectionInfo, _stage: AdmitStage) -> Result<Admitted<()>, Refusal> {
Ok(Admitted {
lane: Lane::MOST_IMPORTANT,
ticket: (),
tag: GateTag(0),
})
}
#[inline(always)]
fn readmit(&self, _ticket: &mut (), _info: &ConnectionInfo) -> Result<(), Refusal> {
Ok(())
}
#[inline(always)]
fn check(&self, _ticket: &mut (), _write: &WriteCheck) -> Result<(), Denial> {
Ok(())
}
}
pub(crate) fn guarded<R>(f: impl FnOnce() -> R) -> Option<R> {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).ok()
}
pub(crate) trait ErasedGate: Send {
fn check(&mut self, write: &WriteCheck) -> Result<(), Denial>;
fn close(&mut self, info: &ConnectionInfo, reason: CloseReason);
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any + Send>;
}
pub(crate) struct GateCell<G: Gate> {
pub(crate) gate: Arc<G>,
pub(crate) ticket: Option<G::Ticket>,
}
impl<G: Gate> ErasedGate for GateCell<G> {
fn check(&mut self, write: &WriteCheck) -> Result<(), Denial> {
let gate = &self.gate;
match self.ticket.as_mut() {
Some(ticket) => {
guarded(|| gate.check(ticket, write)).unwrap_or(Err(Denial { code: 0 }))
}
None => Err(Denial { code: 0 }),
}
}
fn close(&mut self, info: &ConnectionInfo, reason: CloseReason) {
if let Some(ticket) = self.ticket.take() {
let gate = &self.gate;
let _ = guarded(|| gate.closed(ticket, info, reason));
}
}
fn into_any(self: Box<Self>) -> Box<dyn std::any::Any + Send> {
self
}
}
#[derive(Clone)]
pub struct KillHandle {
pub(crate) slot: Arc<KillSlot>,
pub(crate) generation: u32,
pub(crate) id: ConnectionId,
}
impl KillHandle {
pub fn kill(&self, reason: KillReason) -> bool {
self.slot.kill(self.generation, reason)
}
#[must_use]
pub fn id(&self) -> ConnectionId {
self.id
}
#[must_use]
pub fn is_open(&self) -> bool {
self.slot.is_open(self.generation)
}
}
impl fmt::Debug for KillHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("KillHandle")
.field("id", &self.id)
.field("open", &self.is_open())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Panics;
impl Gate for Panics {
type Ticket = ();
fn admit(&self, _: &ConnectionInfo, _: AdmitStage) -> Result<Admitted<()>, Refusal> {
Err(Refusal::Reset { code: 1 })
}
fn readmit(&self, _: &mut (), _: &ConnectionInfo) -> Result<(), Refusal> {
Ok(())
}
#[allow(clippy::panic)]
fn check(&self, _: &mut (), _: &WriteCheck) -> Result<(), Denial> {
panic!("gate bug")
}
}
#[test]
fn test_gate_panic_in_check_denies() {
let mut cell = GateCell {
gate: Arc::new(Panics),
ticket: Some(()),
};
let w = WriteCheck::new(ConnectionId::new(1).unwrap(), Lane(0), 1, false, None);
let prev = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let r = cell.check(&w);
std::panic::set_hook(prev);
assert_eq!(r, Err(Denial { code: 0 }));
}
#[test]
fn test_allow_all_admits_and_allows() {
let info = ConnectionInfo::for_testing("127.0.0.1:1".parse().unwrap());
assert!(AllowAll.admit(&info, AdmitStage::Accept).is_ok());
let mut cell = GateCell {
gate: Arc::new(AllowAll),
ticket: Some(()),
};
let w = WriteCheck::new(info.id, Lane(0), 10, false, None);
assert!(cell.check(&w).is_ok());
cell.close(&info, CloseReason::SessionClosed);
assert!(cell.check(&w).is_err());
}
#[test]
fn test_refusal_code() {
assert_eq!(Refusal::Reset { code: 9 }.code(), 9);
}
}