use std::pin::Pin;
use iroh_base::EndpointAddr;
use crate::endpoint::{connection::ConnectionInfo, quic::VarInt};
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
#[derive(Debug)]
pub enum BeforeConnectOutcome {
Accept,
Reject,
}
#[derive(Debug)]
pub enum AfterHandshakeOutcome {
Accept,
Reject {
error_code: VarInt,
reason: Vec<u8>,
},
}
impl AfterHandshakeOutcome {
pub fn accept() -> Self {
Self::Accept
}
pub fn reject(&self, error_code: VarInt, reason: &[u8]) -> Self {
Self::Reject {
error_code,
reason: reason.to_vec(),
}
}
}
pub trait EndpointHooks: std::fmt::Debug + Send + Sync {
fn before_connect<'a>(
&'a self,
_remote_addr: &'a EndpointAddr,
_alpn: &'a [u8],
) -> impl Future<Output = BeforeConnectOutcome> + Send + 'a {
async { BeforeConnectOutcome::Accept }
}
fn after_handshake<'a>(
&'a self,
_conn: &'a ConnectionInfo,
) -> impl Future<Output = AfterHandshakeOutcome> + Send + 'a {
async { AfterHandshakeOutcome::accept() }
}
}
pub(crate) trait DynEndpointHooks: std::fmt::Debug + Send + Sync {
fn before_connect<'a>(
&'a self,
remote_addr: &'a EndpointAddr,
alpn: &'a [u8],
) -> BoxFuture<'a, BeforeConnectOutcome>;
fn after_handshake<'a>(
&'a self,
conn: &'a ConnectionInfo,
) -> BoxFuture<'a, AfterHandshakeOutcome>;
}
impl<T: EndpointHooks> DynEndpointHooks for T {
fn before_connect<'a>(
&'a self,
remote_addr: &'a EndpointAddr,
alpn: &'a [u8],
) -> BoxFuture<'a, BeforeConnectOutcome> {
Box::pin(EndpointHooks::before_connect(self, remote_addr, alpn))
}
fn after_handshake<'a>(
&'a self,
conn: &'a ConnectionInfo,
) -> BoxFuture<'a, AfterHandshakeOutcome> {
Box::pin(EndpointHooks::after_handshake(self, conn))
}
}
#[derive(Debug, Default)]
pub(crate) struct EndpointHooksList {
inner: Vec<Box<dyn DynEndpointHooks>>,
}
impl EndpointHooksList {
pub(super) fn push(&mut self, hook: impl EndpointHooks + 'static) {
let hook: Box<dyn DynEndpointHooks> = Box::new(hook);
self.inner.push(hook);
}
pub(super) async fn before_connect(
&self,
remote_addr: &EndpointAddr,
alpn: &[u8],
) -> BeforeConnectOutcome {
for hook in self.inner.iter() {
match hook.before_connect(remote_addr, alpn).await {
BeforeConnectOutcome::Accept => continue,
reject @ BeforeConnectOutcome::Reject => {
return reject;
}
}
}
BeforeConnectOutcome::Accept
}
pub(super) async fn after_handshake(&self, conn: &ConnectionInfo) -> AfterHandshakeOutcome {
for hook in self.inner.iter() {
match hook.after_handshake(conn).await {
AfterHandshakeOutcome::Accept => continue,
reject @ AfterHandshakeOutcome::Reject { .. } => {
return reject;
}
}
}
AfterHandshakeOutcome::Accept
}
}