blob: ec1add04e649c0206fb4b6e7f6e775e4b5373708 [file]
// Copyright 2026 The BoringSSL Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Hyper support
use crate::{
TlsAcceptor,
TlsConnector,
TlsStream,
TokioTlsConnection,
translate_stdio_err, //
};
use std::{
error::Error,
fmt::Debug,
future::Future,
io,
pin::Pin,
sync::Arc,
task::{
Context,
Poll, //
},
};
use bssl_tls::{
ReceiveBuffer,
connection::{
Client,
Server,
lifecycle::ShutdownStatus, //
},
errors::Error as TlsError,
io::{
AbstractReader,
AbstractSocket,
AbstractSocketResult,
AbstractWriter,
IoStatus,
NoAsyncContext, //
},
};
use hyper::{
http,
rt::{
Read,
ReadBufCursor,
Write, //
}, //
};
use tower::Service;
/// A connector for `hyper` using `bssl-tls`.
#[derive(Clone)]
pub struct HyperBsslConnector<Inner> {
inner: Inner,
connector: Arc<TlsConnector>,
}
impl<Inner: Debug> std::fmt::Debug for HyperBsslConnector<Inner> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("HyperBsslConnector")
.field("inner", &self.inner)
.finish()
}
}
impl<Inner> HyperBsslConnector<Inner> {
/// Construct a new `HyperBsslConnector`.
pub fn new(inner: Inner, connector: TlsConnector) -> Self {
Self {
inner,
connector: Arc::new(connector),
}
}
}
impl<Inner> Service<http::Uri> for HyperBsslConnector<Inner>
where
Inner: Service<http::Uri>,
Inner::Response: Read + Write + Unpin + Send + Sync + 'static,
Inner::Future: Send + 'static,
Inner::Error: Into<Box<dyn Error + Send + Sync>>,
{
type Response = TlsStream<Client, Inner::Response>;
type Error = Box<dyn Error + Send + Sync>;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx).map_err(Into::into)
}
fn call(&mut self, uri: http::Uri) -> Self::Future {
let domain = uri
.host()
.unwrap_or("")
.trim_start_matches('[')
.trim_end_matches(']')
.to_string();
if domain.is_empty() {
return Box::pin(std::future::ready(Err("empty domain".into())));
}
let fut = self.inner.call(uri);
let connector = self.connector.clone();
Box::pin(async move {
let stream = fut.await.map_err(Into::into)?;
Ok(connector.hyper_connect(&domain, stream).await?)
})
}
}
impl TlsConnector {
/// Connect to the given domain using the provided stream implementing Hyper I/O traits.
pub async fn hyper_connect<S>(
&self,
domain: &str,
stream: S,
) -> Result<TlsStream<Client, S>, TlsError>
where
S: Read + Write + Unpin + Send + 'static,
{
let mut conn = self.ctx.new_client_connection().build();
conn.in_handshake()
.expect("we have not started handshake")
.set_tlsext_host_name(domain)?
.set_host(domain)?;
conn.set_io(HyperIo(stream))?;
conn.async_handshake().await?;
Ok(TlsStream::new(TokioTlsConnection::new(conn)))
}
}
impl TlsAcceptor {
/// Accept a new connection using the provided stream implementing Hyper I/O traits.
pub async fn hyper_accept<S>(&self, stream: S) -> Result<TlsStream<Server, S>, TlsError>
where
S: Read + Write + Unpin + Send + 'static,
{
let mut conn = self.ctx.new_server_connection().build();
conn.set_io(HyperIo(stream))?;
conn.async_handshake().await?;
Ok(TlsStream::new(TokioTlsConnection::new(conn)))
}
}
/// IO object implementing [`hyper::rt::Read`] or [`hyper::rt::Write`] protocols.
pub struct HyperIo<T>(pub T);
fn hyper_async_read<T: Read>(
mut this: Pin<&mut T>,
ctx: &mut Context<'_>,
buffer: &mut [u8],
) -> AbstractSocketResult {
let buffer_len = buffer.len();
let mut buf = hyper::rt::ReadBuf::new(buffer);
loop {
return match this.as_mut().poll_read(ctx, buf.unfilled()) {
Poll::Ready(Ok(())) => {
if buf.filled().is_empty() && buffer_len > 0 {
AbstractSocketResult::EndOfStream
} else {
AbstractSocketResult::Ok(buf.filled().len())
}
}
Poll::Pending => AbstractSocketResult::Retry,
Poll::Ready(Err(e)) => {
if e.kind() == io::ErrorKind::Interrupted {
continue;
}
translate_stdio_err(e)
}
};
}
}
fn hyper_async_write<T: Write>(
mut this: Pin<&mut T>,
ctx: &mut Context<'_>,
buffer: &[u8],
) -> AbstractSocketResult {
loop {
return match this.as_mut().poll_write(ctx, buffer) {
Poll::Ready(Ok(bytes)) => {
if buffer.is_empty() {
AbstractSocketResult::Ok(0)
} else if bytes == 0 {
AbstractSocketResult::EndOfStream
} else {
AbstractSocketResult::Ok(bytes)
}
}
Poll::Pending => AbstractSocketResult::Retry,
Poll::Ready(Err(e)) => {
if e.kind() == io::ErrorKind::Interrupted {
continue;
}
translate_stdio_err(e)
}
};
}
}
fn hyper_async_flush<T: Write>(
mut this: Pin<&mut T>,
ctx: &mut Context<'_>,
) -> AbstractSocketResult {
loop {
return match this.as_mut().poll_flush(ctx) {
Poll::Ready(Ok(())) => AbstractSocketResult::Ok(0),
Poll::Pending => AbstractSocketResult::Retry,
Poll::Ready(Err(e)) => {
if e.kind() == io::ErrorKind::Interrupted {
continue;
}
translate_stdio_err(e)
}
};
}
}
impl<T: Read + Send + Unpin> AbstractReader for HyperIo<T> {
fn read(
&mut self,
async_ctx: Option<&mut Context<'_>>,
buffer: &mut [u8],
) -> AbstractSocketResult {
let Some(ctx) = async_ctx else {
return AbstractSocketResult::Err(Box::new(NoAsyncContext));
};
hyper_async_read(Pin::new(&mut self.0), ctx, buffer)
}
}
impl<T: Write + Send + Unpin> AbstractWriter for HyperIo<T> {
fn write(
&mut self,
async_ctx: Option<&mut Context<'_>>,
buffer: &[u8],
) -> AbstractSocketResult {
let Some(ctx) = async_ctx else {
return AbstractSocketResult::Err(Box::new(NoAsyncContext));
};
hyper_async_write(Pin::new(&mut self.0), ctx, buffer)
}
fn flush(&mut self, async_ctx: Option<&mut Context<'_>>) -> AbstractSocketResult {
let Some(ctx) = async_ctx else {
return AbstractSocketResult::Err(Box::new(NoAsyncContext));
};
hyper_async_flush(Pin::new(&mut self.0), ctx)
}
}
impl<T: Read + Write + Send + Unpin> AbstractSocket for HyperIo<T> {}
impl<Role, S: Unpin> Read for TlsStream<Role, S> {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
mut buf: ReadBufCursor<'_>,
) -> Poll<Result<(), io::Error>> {
let mut read_buf = ReceiveBuffer::new_uninit(unsafe {
// We will not uninitialize anything outside this buffer.
buf.as_mut()
});
let status = match self
.conn
.inner
.as_pin_mut()
.async_poll_read(&mut read_buf, cx)
{
Ok(Some(status)) => status,
Ok(None) => return Poll::Pending,
Err(e) => return Poll::Ready(Err(io::Error::other(e))),
};
match status {
IoStatus::Ok(bytes) => {
debug_assert_eq!(bytes, read_buf.written());
unsafe {
// Safety: BoringSSL has successfully written and initialized `bytes` in the buffer.
buf.advance(bytes);
}
Poll::Ready(Ok(()))
}
IoStatus::EndOfStream => Poll::Ready(Ok(())),
_ => Poll::Ready(Err(io::Error::other("Unexpected I/O status"))),
}
}
}
impl<Role, S: Unpin> Write for TlsStream<Role, S> {
fn poll_write(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<Result<usize, io::Error>> {
let status = match self.conn.inner.as_pin_mut().async_poll_write(buf, cx) {
Ok(Some(status)) => status,
Ok(None) => return Poll::Pending,
Err(e) => return Poll::Ready(Err(io::Error::other(e))),
};
match status {
IoStatus::Ok(bytes) => Poll::Ready(Ok(bytes)),
IoStatus::EndOfStream => Poll::Ready(Ok(0)),
_ => Poll::Ready(Err(io::Error::other("Unexpected I/O status"))),
}
}
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
let status = match self.conn.inner.as_pin_mut().async_poll_flush(cx) {
Ok(Some(status)) => status,
Ok(None) => return Poll::Pending,
Err(e) => return Poll::Ready(Err(io::Error::other(e))),
};
match status {
IoStatus::Ok(_) => Poll::Ready(Ok(())),
IoStatus::EndOfStream => Poll::Ready(Ok(())),
_ => Poll::Ready(Err(io::Error::other("Unexpected I/O status"))),
}
}
fn poll_shutdown(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), io::Error>> {
match self.conn.inner.as_pin_mut().async_poll_shutdown(cx) {
Ok(Some(ShutdownStatus::CloseNotifyReceived)) => Poll::Ready(Ok(())),
Ok(Some(ShutdownStatus::RemainingApplicationData)) => {
Poll::Ready(Err(io::Error::other(
"caller needs to drain application data before polling on shutdown again",
)))
}
Ok(Some(ShutdownStatus::EndOfStream)) => Poll::Ready(Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"unexpected eof while waiting for peek close_notify",
))),
Ok(Some(ShutdownStatus::CloseNotifyPosted)) => {
unreachable!()
}
Ok(None) => Poll::Pending,
Err(e) => Poll::Ready(Err(io::Error::other(e))),
}
}
}