#![deny(missing_docs)]
#![cfg_attr(docsrs, feature(doc_cfg))]
use futures_util::stream::{FuturesUnordered, Stream, StreamExt, TryStreamExt};
use pin_project_lite::pin_project;
#[cfg(feature = "rt")]
pub use spawning_handshake::SpawningHandshakes;
use std::fmt::Debug;
use std::future::{poll_fn, Future};
use std::num::NonZeroUsize;
use std::pin::Pin;
use std::task::{ready, Context, Poll};
use std::time::Duration;
use thiserror::Error;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio::time::{timeout, Timeout};
#[cfg(feature = "native-tls")]
pub use tokio_native_tls as native_tls;
#[cfg(feature = "openssl")]
pub use tokio_openssl as openssl;
#[cfg(feature = "rustls-core")]
pub use tokio_rustls as rustls;
mod accept;
#[cfg(feature = "tokio-net")]
mod net;
#[cfg(feature = "rt")]
mod spawning_handshake;
pub use accept::*;
#[cfg(feature = "axum")]
mod axum;
pub const DEFAULT_ACCEPT_BATCH_SIZE: NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(64) };
pub const DEFAULT_HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
pub trait AsyncTls<C: AsyncRead + AsyncWrite>: Clone {
type Stream;
type Error: std::error::Error;
type AcceptFuture: Future<Output = Result<Self::Stream, Self::Error>>;
fn accept(&self, stream: C) -> Self::AcceptFuture;
}
pin_project! {
pub struct TlsListener<A: AsyncAccept, T: AsyncTls<A::Connection>> {
#[pin]
listener: A,
tls: T,
waiting: FuturesUnordered<Waiting<A, T>>,
accept_batch_size: NonZeroUsize,
timeout: Duration,
}
}
#[derive(Clone)]
pub struct Builder<T> {
tls: T,
accept_batch_size: NonZeroUsize,
handshake_timeout: Duration,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum Error<LE: std::error::Error, TE: std::error::Error, Addr> {
#[error("{0}")]
ListenerError(#[source] LE),
#[error("{error}")]
#[non_exhaustive]
TlsAcceptError {
#[source]
error: TE,
peer_addr: Addr,
},
#[error("Timeout during TLS handshake")]
#[non_exhaustive]
HandshakeTimeout {
peer_addr: Addr,
},
}
impl<A: AsyncAccept, T> TlsListener<A, T>
where
T: AsyncTls<A::Connection>,
{
pub fn new(tls: T, listener: A) -> Self {
builder(tls).listen(listener)
}
}
type TlsListenerError<A, T> = Error<
<A as AsyncAccept>::Error,
<T as AsyncTls<<A as AsyncAccept>::Connection>>::Error,
<A as AsyncAccept>::Address,
>;
impl<A, T> TlsListener<A, T>
where
A: AsyncAccept,
T: AsyncTls<A::Connection>,
{
pub fn poll_accept(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<<Self as Stream>::Item> {
let mut this = self.project();
loop {
let mut empty_listener = false;
for _ in 0..this.accept_batch_size.get() {
match this.listener.as_mut().poll_accept(cx) {
Poll::Pending => {
empty_listener = true;
break;
}
Poll::Ready(Ok((conn, addr))) => {
this.waiting.push(Waiting {
inner: timeout(*this.timeout, this.tls.accept(conn)),
peer_addr: Some(addr),
});
}
Poll::Ready(Err(e)) => {
return Poll::Ready(Err(Error::ListenerError(e)));
}
}
}
match this.waiting.poll_next_unpin(cx) {
Poll::Ready(Some(result)) => return Poll::Ready(result),
Poll::Ready(None) | Poll::Pending => {
if empty_listener {
return Poll::Pending;
}
}
}
}
}
pub fn accept(&mut self) -> impl Future<Output = <Self as Stream>::Item> + '_
where
Self: Unpin,
{
let mut pinned = Pin::new(self);
poll_fn(move |cx| pinned.as_mut().poll_accept(cx))
}
pub fn replace_acceptor(&mut self, acceptor: T) {
self.tls = acceptor;
}
pub fn replace_acceptor_pin(self: Pin<&mut Self>, acceptor: T) {
*self.project().tls = acceptor;
}
pub fn connections(self) -> impl Stream<Item = Result<T::Stream, TlsListenerError<A, T>>> {
self.map_ok(|(conn, _addr)| conn)
}
pub fn listener(&self) -> &A {
&self.listener
}
pub fn local_addr(&self) -> Result<A::Address, A::Error>
where
A: AsyncListener,
{
self.listener.local_addr()
}
}
impl<A, T> Stream for TlsListener<A, T>
where
A: AsyncAccept,
T: AsyncTls<A::Connection>,
{
type Item = Result<(T::Stream, A::Address), TlsListenerError<A, T>>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
self.poll_accept(cx).map(Some)
}
}
#[cfg(feature = "rustls-core")]
#[cfg_attr(docsrs, doc(cfg(feature = "rustls-core")))]
impl<C: AsyncRead + AsyncWrite + Unpin> AsyncTls<C> for tokio_rustls::TlsAcceptor {
type Stream = tokio_rustls::server::TlsStream<C>;
type Error = std::io::Error;
type AcceptFuture = tokio_rustls::Accept<C>;
fn accept(&self, conn: C) -> Self::AcceptFuture {
tokio_rustls::TlsAcceptor::accept(self, conn)
}
}
#[cfg(feature = "native-tls")]
#[cfg_attr(docsrs, doc(cfg(feature = "native-tls")))]
impl<C> AsyncTls<C> for tokio_native_tls::TlsAcceptor
where
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Stream = tokio_native_tls::TlsStream<C>;
type Error = tokio_native_tls::native_tls::Error;
type AcceptFuture = Pin<Box<dyn Future<Output = Result<Self::Stream, Self::Error>> + Send>>;
fn accept(&self, conn: C) -> Self::AcceptFuture {
let tls = self.clone();
Box::pin(async move { tokio_native_tls::TlsAcceptor::accept(&tls, conn).await })
}
}
#[cfg(feature = "openssl")]
#[cfg_attr(docsrs, doc(cfg(feature = "openssl")))]
impl<C> AsyncTls<C> for openssl_impl::ssl::SslContext
where
C: AsyncRead + AsyncWrite + Unpin + Send + 'static,
{
type Stream = tokio_openssl::SslStream<C>;
type Error = openssl_impl::ssl::Error;
type AcceptFuture = Pin<Box<dyn Future<Output = Result<Self::Stream, Self::Error>> + Send>>;
fn accept(&self, conn: C) -> Self::AcceptFuture {
let ssl = match openssl_impl::ssl::Ssl::new(self) {
Ok(s) => s,
Err(e) => {
return Box::pin(futures_util::future::err(e.into()));
}
};
let mut stream = match tokio_openssl::SslStream::new(ssl, conn) {
Ok(s) => s,
Err(e) => {
return Box::pin(futures_util::future::err(e.into()));
}
};
Box::pin(async move {
Pin::new(&mut stream).accept().await?;
Ok(stream)
})
}
}
impl<T> Builder<T> {
pub fn accept_batch_size(&mut self, size: NonZeroUsize) -> &mut Self {
self.accept_batch_size = size;
self
}
pub fn handshake_timeout(&mut self, timeout: Duration) -> &mut Self {
self.handshake_timeout = timeout;
self
}
pub fn listen<A: AsyncAccept>(&self, listener: A) -> TlsListener<A, T>
where
T: AsyncTls<A::Connection>,
{
TlsListener {
listener,
tls: self.tls.clone(),
waiting: FuturesUnordered::new(),
accept_batch_size: self.accept_batch_size,
timeout: self.handshake_timeout,
}
}
}
impl<LE: std::error::Error, TE: std::error::Error, A> Error<LE, TE, A> {
pub fn peer_addr(&self) -> Option<&A> {
match self {
Error::TlsAcceptError { peer_addr, .. } | Self::HandshakeTimeout { peer_addr, .. } => {
Some(peer_addr)
}
_ => None,
}
}
}
pub fn builder<T>(tls: T) -> Builder<T> {
Builder {
tls,
accept_batch_size: DEFAULT_ACCEPT_BATCH_SIZE,
handshake_timeout: DEFAULT_HANDSHAKE_TIMEOUT,
}
}
pin_project! {
struct Waiting<A, T>
where
A: AsyncAccept,
T: AsyncTls<A::Connection>
{
#[pin]
inner: Timeout<T::AcceptFuture>,
peer_addr: Option<A::Address>,
}
}
impl<A, T> Future for Waiting<A, T>
where
A: AsyncAccept,
T: AsyncTls<A::Connection>,
{
type Output = Result<(T::Stream, A::Address), TlsListenerError<A, T>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut this = self.project();
let res = ready!(this.inner.as_mut().poll(cx));
let addr = this
.peer_addr
.take()
.expect("this future has already been polled to completion");
match res {
Ok(Ok(conn)) => Poll::Ready(Ok((conn, addr))),
Ok(Err(e)) => Poll::Ready(Err(Error::TlsAcceptError {
error: e,
peer_addr: addr,
})),
Err(_) => Poll::Ready(Err(Error::HandshakeTimeout { peer_addr: addr })),
}
}
}