use std::{
fmt,
io::{self, Read, Write},
pin::Pin,
ptr::null_mut,
task::{Context, Poll},
};
use futures_io::{AsyncRead, AsyncWrite};
use native_tls::{Error, HandshakeError, MidHandshakeTlsStream};
#[derive(Debug)]
pub struct StdAdapter<S> {
inner: S,
context: *mut (),
}
unsafe impl<S: Send> Send for StdAdapter<S> {}
unsafe impl<S: Sync> Sync for StdAdapter<S> {}
impl<S: Unpin> StdAdapter<S> {
fn with_context<R>(&mut self, f: impl FnOnce(&mut Context<'_>, Pin<&mut S>) -> R) -> R {
assert!(!self.context.is_null());
let context = unsafe { &mut *self.context.cast::<Context<'_>>() };
f(context, Pin::new(&mut self.inner))
}
}
impl<S: AsyncRead + Unpin> Read for StdAdapter<S> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match self.with_context(|cx, stream| stream.poll_read(cx, buf)) {
Poll::Ready(result) => result,
Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
}
}
}
impl<S: AsyncWrite + Unpin> Write for StdAdapter<S> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match self.with_context(|cx, stream| stream.poll_write(cx, buf)) {
Poll::Ready(result) => result,
Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
}
}
fn flush(&mut self) -> io::Result<()> {
match self.with_context(|cx, stream| stream.poll_flush(cx)) {
Poll::Ready(result) => result,
Poll::Pending => Err(io::Error::from(io::ErrorKind::WouldBlock)),
}
}
}
#[derive(Clone)]
pub struct TlsConnector(native_tls::TlsConnector);
impl TlsConnector {
pub const fn new(inner: native_tls::TlsConnector) -> Self {
Self(inner)
}
pub async fn connect<S>(&self, domain: &str, stream: S) -> Result<TlsStream<S>, Error>
where
S: AsyncRead + AsyncWrite + Unpin,
{
Handshake {
state: HandshakeState::Start {
connector: self.0.clone(),
domain: domain.to_owned(),
stream: Some(stream),
},
}
.await
}
}
impl fmt::Debug for TlsConnector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TlsConnector").finish_non_exhaustive()
}
}
#[derive(Debug)]
pub struct TlsStream<S>(native_tls::TlsStream<StdAdapter<S>>);
impl<S> TlsStream<S>
where
StdAdapter<S>: Read + Write,
{
fn with_context<R>(
&mut self,
cx: &mut Context<'_>,
f: impl FnOnce(&mut native_tls::TlsStream<StdAdapter<S>>) -> R,
) -> R {
self.0.get_mut().context = std::ptr::from_mut(cx).cast::<()>();
let guard = ContextGuard(self);
f(&mut (guard.0).0)
}
pub fn negotiated_alpn(&self) -> Result<Option<Vec<u8>>, Error> {
self.0.negotiated_alpn()
}
}
impl<S> AsyncRead for TlsStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut [u8],
) -> Poll<io::Result<usize>> {
self.with_context(cx, |stream| ready(stream.read(buf)))
}
}
impl<S> AsyncWrite for TlsStream<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
self.with_context(cx, |stream| ready(stream.write(buf)))
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.with_context(cx, |stream| ready(stream.flush()))
}
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.with_context(cx, |stream| ready(stream.shutdown()))
}
}
struct ContextGuard<'a, S>(&'a mut TlsStream<S>);
impl<S> Drop for ContextGuard<'_, S> {
fn drop(&mut self) {
self.0.0.get_mut().context = null_mut();
}
}
struct Handshake<S: Unpin> {
state: HandshakeState<S>,
}
enum HandshakeState<S: Unpin> {
Start {
connector: native_tls::TlsConnector,
domain: String,
stream: Option<S>,
},
Mid(MidHandshakeTlsStream<StdAdapter<S>>),
Done,
}
impl<S> std::future::Future for Handshake<S>
where
S: AsyncRead + AsyncWrite + Unpin,
{
type Output = Result<TlsStream<S>, Error>;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match &mut self.state {
HandshakeState::Start {
connector,
domain,
stream,
} => {
let adapter = StdAdapter {
inner: stream.take().expect("future polled after completion"),
context: std::ptr::from_mut(cx).cast::<()>(),
};
match connector.connect(domain, adapter) {
Ok(mut stream) => {
stream.get_mut().context = null_mut();
Poll::Ready(Ok(TlsStream(stream)))
}
Err(HandshakeError::Failure(error)) => Poll::Ready(Err(error)),
Err(HandshakeError::WouldBlock(mut mid)) => {
mid.get_mut().context = null_mut();
self.state = HandshakeState::Mid(mid);
Poll::Pending
}
}
}
HandshakeState::Mid(_) => {
let HandshakeState::Mid(mut mid) =
std::mem::replace(&mut self.state, HandshakeState::Done)
else {
unreachable!()
};
mid.get_mut().context = std::ptr::from_mut(cx).cast::<()>();
match mid.handshake() {
Ok(mut stream) => {
stream.get_mut().context = null_mut();
Poll::Ready(Ok(TlsStream(stream)))
}
Err(HandshakeError::Failure(error)) => Poll::Ready(Err(error)),
Err(HandshakeError::WouldBlock(mut mid)) => {
mid.get_mut().context = null_mut();
self.state = HandshakeState::Mid(mid);
Poll::Pending
}
}
}
HandshakeState::Done => panic!("future polled after completion"),
}
}
}
fn ready<T>(result: io::Result<T>) -> Poll<io::Result<T>> {
match result {
Ok(value) => Poll::Ready(Ok(value)),
Err(ref error) if error.kind() == io::ErrorKind::WouldBlock => Poll::Pending,
Err(error) => Poll::Ready(Err(error)),
}
}