use core::{
pin::Pin,
task::{Context, Poll},
};
use futures::{Future, FutureExt};
use hyper::rt::ReadBufCursor;
use pin_project::pin_project;
#[pin_project(project=ConnStreamProj)]
#[derive(Debug)]
#[allow(private_interfaces)]
pub enum ConnStream {
Tcp {
#[pin]
transport: TokioIo<tokio::net::TcpStream>,
},
Tls {
#[pin]
transport: wrapped::BoxedConn,
},
#[cfg(unix)]
Udp {
#[pin]
transport: TokioIo<tokio::net::UnixStream>,
},
#[cfg(windows)]
NamedPipe {
#[pin]
transport: TokioIo<tokio::net::windows::named_pipe::NamedPipeClient>,
},
}
mod wrapped {
use super::*;
pub trait EstablishedConn:
hyper::rt::Read + hyper::rt::Write + connect::Connection + Send
{
}
impl<T: hyper::rt::Read + hyper::rt::Write + connect::Connection + Send> EstablishedConn for T {}
#[derive(Debug)]
pub struct BoxedConn(Pin<Box<dyn EstablishedConn>>);
impl core::fmt::Debug for dyn EstablishedConn {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("EstablishedConn")
}
}
impl BoxedConn {
pub fn new<T: EstablishedConn + 'static>(inner: T) -> Self {
Self(Box::pin(inner))
}
}
impl hyper::rt::Read for BoxedConn {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: ReadBufCursor<'_>,
) -> Poll<std::io::Result<()>> {
self.get_mut().0.as_mut().poll_read(cx, buf)
}
}
impl hyper::rt::Write for BoxedConn {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
self.get_mut().0.as_mut().poll_write(cx, buf)
}
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
self.get_mut().0.as_mut().poll_flush(cx)
}
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
self.get_mut().0.as_mut().poll_shutdown(cx)
}
}
impl connect::Connection for BoxedConn {
fn connected(&self) -> connect::Connected {
self.0.connected()
}
}
}
pub type ConnStreamError = Box<dyn core::error::Error + Send + Sync>;
use hyper_util::client::legacy::connect::{self, HttpConnector};
use hyper_util::rt::TokioIo;
use tower_service::Service;
impl ConnStream {
pub async fn from_uds_uri(uri: hyper::Uri) -> Result<ConnStream, ConnStreamError> {
#[cfg(unix)]
{
let path = super::uds::socket_path_from_uri(&uri)?;
Ok(ConnStream::Udp {
transport: TokioIo::new(tokio::net::UnixStream::connect(path).await?),
})
}
#[cfg(not(unix))]
{
let _ = uri;
Err(super::errors::Error::UnixSocketUnsupported.into())
}
}
pub async fn from_named_pipe_uri(uri: hyper::Uri) -> Result<ConnStream, ConnStreamError> {
#[cfg(windows)]
{
let path = super::named_pipe::named_pipe_path_from_uri(&uri)?;
Ok(ConnStream::NamedPipe {
transport: TokioIo::new(
tokio::net::windows::named_pipe::ClientOptions::new()
.security_qos_flags(super::named_pipe::ANONYMOUS_IMPERSONATION_QOS)
.open(path)?,
),
})
}
#[cfg(not(windows))]
{
let _ = uri;
Err(super::errors::Error::WindowsNamedPipeUnsupported.into())
}
}
pub fn from_http_connector_with_uri(
c: &mut HttpConnector,
uri: hyper::Uri,
) -> impl Future<Output = Result<ConnStream, ConnStreamError>> + 'static {
c.call(uri).map(|r| match r {
Ok(t) => Ok(ConnStream::Tcp { transport: t }),
Err(e) => Err(e.into()),
})
}
#[cfg(feature = "tls-core")]
pub fn from_https_connector_with_uri<C>(
c: &mut hyper_rustls::HttpsConnector<C>,
uri: hyper::Uri,
require_tls: bool,
) -> impl Future<Output = Result<ConnStream, ConnStreamError>> + 'static
where
C: Service<hyper::Uri> + 'static,
C::Response:
hyper::rt::Read + hyper::rt::Write + connect::Connection + Send + Unpin + 'static,
C::Future: Send + 'static,
C::Error: Into<Box<dyn core::error::Error + Send + Sync>>,
{
#[allow(clippy::unwrap_used)]
let stream_fut = c.call(uri.to_string().parse().unwrap());
async move {
let stream = stream_fut.await?;
if require_tls && matches!(stream, hyper_rustls::MaybeHttpsStream::Http(_)) {
return Err(super::errors::Error::CannotEstablishTlsConnection.into());
}
Ok(ConnStream::Tls {
transport: wrapped::BoxedConn::new(stream),
})
}
}
pub fn from_tunnel_with_uri<C>(
c: &mut C,
uri: hyper::Uri,
) -> impl Future<Output = Result<ConnStream, ConnStreamError>> + 'static
where
C: Service<hyper::Uri> + 'static,
C::Response:
hyper::rt::Read + hyper::rt::Write + connect::Connection + Send + Unpin + 'static,
C::Future: Send + 'static,
C::Error: Into<Box<dyn core::error::Error + Send + Sync>>,
{
let stream_fut = c.call(uri);
async move {
let stream = stream_fut.await.map_err(Into::into)?;
Ok(ConnStream::Tls {
transport: wrapped::BoxedConn::new(stream),
})
}
}
}
impl hyper::rt::Read for ConnStream {
fn poll_read(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: ReadBufCursor<'_>,
) -> Poll<std::io::Result<()>> {
match self.project() {
ConnStreamProj::Tcp { transport } => transport.poll_read(cx, buf),
ConnStreamProj::Tls { transport } => transport.poll_read(cx, buf),
#[cfg(unix)]
ConnStreamProj::Udp { transport } => transport.poll_read(cx, buf),
#[cfg(windows)]
ConnStreamProj::NamedPipe { transport } => transport.poll_read(cx, buf),
}
}
}
impl connect::Connection for ConnStream {
fn connected(&self) -> connect::Connected {
match self {
Self::Tcp { transport } => transport.connected(),
Self::Tls { transport } => transport.connected(),
#[cfg(unix)]
Self::Udp { transport: _ } => connect::Connected::new(),
#[cfg(windows)]
Self::NamedPipe { transport: _ } => connect::Connected::new(),
}
}
}
impl hyper::rt::Write for ConnStream {
fn poll_write(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, std::io::Error>> {
match self.project() {
ConnStreamProj::Tcp { transport } => transport.poll_write(cx, buf),
ConnStreamProj::Tls { transport } => transport.poll_write(cx, buf),
#[cfg(unix)]
ConnStreamProj::Udp { transport } => transport.poll_write(cx, buf),
#[cfg(windows)]
ConnStreamProj::NamedPipe { transport } => transport.poll_write(cx, buf),
}
}
fn poll_shutdown(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), std::io::Error>> {
match self.project() {
ConnStreamProj::Tcp { transport } => transport.poll_shutdown(cx),
ConnStreamProj::Tls { transport } => transport.poll_shutdown(cx),
#[cfg(unix)]
ConnStreamProj::Udp { transport } => transport.poll_shutdown(cx),
#[cfg(windows)]
ConnStreamProj::NamedPipe { transport } => transport.poll_shutdown(cx),
}
}
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
match self.project() {
ConnStreamProj::Tcp { transport } => transport.poll_flush(cx),
ConnStreamProj::Tls { transport } => transport.poll_flush(cx),
#[cfg(unix)]
ConnStreamProj::Udp { transport } => transport.poll_flush(cx),
#[cfg(windows)]
ConnStreamProj::NamedPipe { transport } => transport.poll_flush(cx),
}
}
}
#[cfg(all(test, windows))]
mod windows_named_pipe_tests {
use super::ConnStream;
use crate::connector::named_pipe::named_pipe_path_to_uri;
use std::path::Path;
use tokio::net::windows::named_pipe::ServerOptions;
#[tokio::test]
async fn from_named_pipe_uri_connects_with_anonymous_qos() {
let pipe_name = format!(
r"\\.\pipe\libdd_common_conn_stream_test_{}_{}",
std::process::id(),
rand::random::<u64>()
);
let server = ServerOptions::new()
.first_pipe_instance(true)
.create(&pipe_name)
.expect("failed to create named pipe server");
let server_task = tokio::spawn(async move {
server.connect().await.expect("server failed to accept");
});
let uri = named_pipe_path_to_uri(Path::new(&pipe_name)).expect("failed to build uri");
let conn = ConnStream::from_named_pipe_uri(uri).await;
assert!(
conn.is_ok(),
"expected named pipe client to connect with Anonymous QoS: {:?}",
conn.err()
);
server_task.await.expect("server task panicked");
}
}