use std::{fmt::Debug, io};
use compio_io::{
AsyncRead, AsyncWrite,
compat::{AsyncStream, SyncStream},
};
use crate::TlsStream;
#[derive(Clone)]
enum TlsConnectorInner {
#[cfg(feature = "native-tls")]
NativeTls(native_tls::TlsConnector),
#[cfg(feature = "rustls")]
Rustls(futures_rustls::TlsConnector),
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
None(std::convert::Infallible),
}
impl Debug for TlsConnectorInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
#[cfg(feature = "native-tls")]
Self::NativeTls(_) => f.debug_tuple("NativeTls").finish(),
#[cfg(feature = "rustls")]
Self::Rustls(_) => f.debug_tuple("Rustls").finish(),
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
Self::None(f) => match *f {},
}
}
}
#[derive(Debug, Clone)]
pub struct TlsConnector(TlsConnectorInner);
#[cfg(feature = "native-tls")]
impl From<native_tls::TlsConnector> for TlsConnector {
fn from(value: native_tls::TlsConnector) -> Self {
Self(TlsConnectorInner::NativeTls(value))
}
}
#[cfg(feature = "rustls")]
impl From<std::sync::Arc<rustls::ClientConfig>> for TlsConnector {
fn from(value: std::sync::Arc<rustls::ClientConfig>) -> Self {
Self(TlsConnectorInner::Rustls(value.into()))
}
}
impl TlsConnector {
pub async fn connect<S: AsyncRead + AsyncWrite + 'static>(
&self,
domain: &str,
stream: S,
) -> io::Result<TlsStream<S>> {
match &self.0 {
#[cfg(feature = "native-tls")]
TlsConnectorInner::NativeTls(c) => {
handshake_native_tls(c.connect(domain, SyncStream::new(stream))).await
}
#[cfg(feature = "rustls")]
TlsConnectorInner::Rustls(c) => {
let client = c
.connect(
domain.to_string().try_into().map_err(io::Error::other)?,
AsyncStream::new(stream),
)
.await?;
Ok(TlsStream::from(client))
}
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
TlsConnectorInner::None(f) => match *f {},
}
}
}
#[derive(Clone)]
enum TlsAcceptorInner {
#[cfg(feature = "native-tls")]
NativeTls(native_tls::TlsAcceptor),
#[cfg(feature = "rustls")]
Rustls(futures_rustls::TlsAcceptor),
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
None(std::convert::Infallible),
}
#[derive(Clone)]
pub struct TlsAcceptor(TlsAcceptorInner);
#[cfg(feature = "native-tls")]
impl From<native_tls::TlsAcceptor> for TlsAcceptor {
fn from(value: native_tls::TlsAcceptor) -> Self {
Self(TlsAcceptorInner::NativeTls(value))
}
}
#[cfg(feature = "rustls")]
impl From<std::sync::Arc<rustls::ServerConfig>> for TlsAcceptor {
fn from(value: std::sync::Arc<rustls::ServerConfig>) -> Self {
Self(TlsAcceptorInner::Rustls(value.into()))
}
}
impl TlsAcceptor {
pub async fn accept<S: AsyncRead + AsyncWrite + 'static>(
&self,
stream: S,
) -> io::Result<TlsStream<S>> {
match &self.0 {
#[cfg(feature = "native-tls")]
TlsAcceptorInner::NativeTls(c) => {
handshake_native_tls(c.accept(SyncStream::new(stream))).await
}
#[cfg(feature = "rustls")]
TlsAcceptorInner::Rustls(c) => {
let server = c.accept(AsyncStream::new(stream)).await?;
Ok(TlsStream::from(server))
}
#[cfg(not(any(feature = "native-tls", feature = "rustls")))]
TlsAcceptorInner::None(f) => match *f {},
}
}
}
#[cfg(feature = "native-tls")]
async fn handshake_native_tls<S: AsyncRead + AsyncWrite>(
mut res: Result<
native_tls::TlsStream<SyncStream<S>>,
native_tls::HandshakeError<SyncStream<S>>,
>,
) -> io::Result<TlsStream<S>> {
use native_tls::HandshakeError;
loop {
match res {
Ok(mut s) => {
s.get_mut().flush_write_buf().await?;
return Ok(TlsStream::from(s));
}
Err(e) => match e {
HandshakeError::Failure(e) => return Err(io::Error::other(e)),
HandshakeError::WouldBlock(mut mid_stream) => {
if mid_stream.get_mut().flush_write_buf().await? == 0 {
mid_stream.get_mut().fill_read_buf().await?;
}
res = mid_stream.handshake();
}
},
}
}
}