blob: b156cabf3148b772a1c5949f0d7b3de2afa3be0e [file] [edit]
// 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.
//! TLS I/O model
use alloc::boxed::Box;
use core::{
ffi::c_int,
future::poll_fn,
pin::Pin,
task::{
Context,
Poll, //
}, //
};
use crate::{
ReceiveBuffer,
connection::{
TlsConnection,
lifecycle::ShutdownStatus,
methods::HasTlsConnectionMethod, //
},
context::{
HasBasicIo,
TlsMode, //
},
errors::{
Error,
IoError,
TlsRetryReason, //
},
ffi::slice_into_ffi_raw_parts,
io::IoStatus, //
};
impl<R, M> TlsConnection<R, M>
where
M: HasTlsConnectionMethod,
{
/// Check if the connection has any buffered data pending reads.
pub fn has_pending_read(&self) -> bool {
unsafe {
// Safety: the validity of the handle `self.ptr()` is witnessed by `self`.
bssl_sys::SSL_has_pending(self.ptr()) == 1
}
}
fn take_io_err(&mut self) -> Option<Box<dyn core::error::Error + Send + Sync>> {
let bio = self.get_connection_methods().bio.as_mut()?;
bio.as_mut().take_io_err()
}
/// Translate I/O error into the right form.
///
/// It is here we translate retry reason into a **soft** error [`IoStatus::Retry`].
fn translate_io_error(&mut self, rc: c_int) -> Result<IoStatus, Error> {
// Pre-emptively extract error and clear the error queue.
let ssl_err = self.categorise_error_for_io(rc);
if let Some(err) = self.take_io_err() {
Err(Error::Io(IoError::Transport(err)))
} else {
ssl_err
}
}
/// Read data from the socket.
///
/// This method reads up to `buffer.len()` bytes from `buffer`.
pub fn sync_read(&mut self, buffer: &mut ReceiveBuffer<'_>) -> Result<IoStatus, Error> {
let buf = unsafe {
// Safety:
// - the use of this pointer is outlived by this function callframe.
// - the access to the buffer region is bounded by `buffer.remaining()` by `SSL_read`
// contract.
// - there are no reads into the buffer region per `SSL_read` contract.
buffer.head()
};
let num = c_int::try_from(buffer.remaining()).unwrap_or(c_int::MAX);
let rc = unsafe {
// Safety: the validity of the handle `self.ptr()` is witnessed by `self`.
bssl_sys::SSL_read(self.ptr(), buf as _, num)
};
if rc > 0 {
let len = rc as usize;
unsafe {
// Safety: BoringSSL will ensure that `len` bytes have been written.
buffer.advance(len);
}
Ok(IoStatus::Ok(len))
} else {
self.translate_io_error(rc)
}
}
/// Peek `buffer.len()` bytes of application data into the `buffer`.
pub fn peek(&mut self, buffer: &mut ReceiveBuffer<'_>) -> Result<IoStatus, Error> {
let buf = unsafe {
// Safety:
// - the use of this pointer is outlived by this function callframe.
// - the access to the buffer region is bounded by `buffer.remaining()` by `SSL_peek`
// contract.
// - there are no reads into the buffer region per `SSL_peek` contract.
buffer.head()
};
let num = c_int::try_from(buffer.remaining()).unwrap_or(c_int::MAX);
let rc = unsafe {
// Safety: the validity of the handle `self.ptr()` is witnessed by `self`
bssl_sys::SSL_peek(self.ptr(), buf as _, num)
};
if rc > 0 {
let len = rc as usize;
unsafe {
// Safety: BoringSSL will ensure that `len` bytes have been written.
buffer.advance(len);
}
Ok(IoStatus::Ok(len))
} else {
self.translate_io_error(rc)
}
}
/// Write data to the socket.
///
/// This method writes up to `buffer.len()` bytes from `buffer`.
pub fn sync_write(&mut self, buffer: &[u8]) -> Result<IoStatus, Error> {
let (ptr, len) = slice_into_ffi_raw_parts(buffer);
let num = c_int::try_from(len).unwrap_or(c_int::MAX);
let rc = unsafe {
// Safety: the validity of the handle `self.ptr()` is witnessed by `self`
bssl_sys::SSL_write(self.ptr(), ptr as _, num)
};
if rc > 0 {
Ok(IoStatus::Ok(rc as usize))
} else {
self.translate_io_error(rc)
}
}
/// Flush the data on the **transport**.
///
/// On success, this method always reports the number of bytes moved as `0`.
pub fn flush(&mut self) -> Result<IoStatus, Error> {
let bio = unsafe {
// Safety: the validity of the handle `self.ptr()` is witnessed by `self`.
bssl_sys::SSL_get_wbio(self.ptr())
};
if bio.is_null() {
return Ok(IoStatus::Empty);
}
let rc = unsafe {
// Safety: `bio` should still be valid by BoringSSL invariant.
bssl_sys::BIO_flush(bio)
};
if rc == 1 {
return Ok(IoStatus::Ok(0));
}
// We do not expect any SSL level error, but there could still be BIO level error.
let bio_retry = unsafe {
// Safety: `bio` should still be valid here.
bssl_sys::BIO_should_retry(bio)
};
if bio_retry != 1 {
return Ok(IoStatus::Retry(TlsRetryReason::WantWrite));
}
// Pre-emptively extract error and clear the error queue.
if let Some(err) = self.take_io_err() {
Err(Error::Io(IoError::Transport(err)))
} else {
Ok(IoStatus::Ok(0))
}
}
}
/// Async I/O
impl<R, M> TlsConnection<R, M>
where
M: HasTlsConnectionMethod,
{
/// For `async` operations, obtain a pinned mutable reference.
pub fn as_pin_mut(&mut self) -> Pin<&mut Self> {
Pin::new(self)
}
/// For `async` operations, obtain a pinned immutable reference.
pub fn as_pin(&self) -> Pin<&Self> {
Pin::new(self)
}
}
impl<R, M> TlsConnection<R, M>
where
M: HasTlsConnectionMethod + HasBasicIo,
{
fn do_async_io(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
sync_op: impl FnOnce(&mut TlsConnection<R, M>) -> Result<IoStatus, Error>,
) -> Result<Option<IoStatus>, Error> {
self.set_waker(cx.waker());
let reason = match sync_op(&mut *self) {
Ok(
status @ (IoStatus::Ok(..)
| IoStatus::EndOfStream
| IoStatus::Empty
| IoStatus::Err),
) => return Ok(Some(status)),
Err(e) => return Err(e),
Ok(IoStatus::Retry(reason)) => reason,
};
self.get_connection_methods().set_pending_reason(reason);
Ok(None)
}
#[doc(hidden)]
pub fn aread_inner(
self: Pin<&mut Self>,
buffer: &mut [u8],
cx: &mut Context<'_>,
) -> Result<Option<IoStatus>, Error> {
let mut buffer = ReceiveBuffer::new(buffer);
self.do_async_io(cx, move |this| this.sync_read(&mut buffer))
}
#[doc(hidden)]
pub fn awrite_inner(
self: Pin<&mut Self>,
buffer: &[u8],
cx: &mut Context<'_>,
) -> Result<Option<IoStatus>, Error> {
self.do_async_io(cx, move |this| this.sync_write(buffer))
}
#[doc(hidden)]
pub fn aflush_inner(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<Option<IoStatus>, Error> {
self.do_async_io(cx, move |this| this.flush())
}
/// Asynchronously read application data from the TLS connection.
///
/// This method will intercept [`IoStatus::Retry`] and suspend the future.
/// The reason can be inspected by invoking [`Self::take_pending_reason`].
pub fn async_read<'a>(
mut self: Pin<&'a mut Self>,
buffer: &'a mut [u8],
) -> impl 'a + Send + Future<Output = Result<IoStatus, Error>> {
poll_fn(move |cx| match self.as_mut().aread_inner(buffer, cx) {
Ok(Some(status)) => Poll::Ready(Ok(status)),
Ok(None) => Poll::Pending,
Err(e) => Poll::Ready(Err(e)),
})
}
/// Asynchronously write application data to the TLS connection.
///
/// This method will intercept [`IoStatus::Retry`] and suspend the future.
/// The reason can be inspected by invoking [`Self::take_pending_reason`].
pub fn async_write<'a>(
mut self: Pin<&'a mut Self>,
buffer: &'a [u8],
) -> impl 'a + Send + Future<Output = Result<IoStatus, Error>> {
poll_fn(move |cx| match self.as_mut().awrite_inner(buffer, cx) {
Ok(Some(status)) => Poll::Ready(Ok(status)),
Ok(None) => Poll::Pending,
Err(e) => Poll::Ready(Err(e)),
})
}
/// Asynchronously flush the underlying transport attached to the TLS connection.
///
/// This method will intercept [`IoStatus::Retry`] and suspend the future.
/// The reason can be inspected by invoking [`Self::take_pending_reason`].
pub fn async_flush<'a>(
mut self: Pin<&'a mut Self>,
) -> impl 'a + Send + Future<Output = Result<IoStatus, Error>> {
poll_fn(move |cx| match self.as_mut().aflush_inner(cx) {
Ok(Some(status)) => Poll::Ready(Ok(status)),
Ok(None) => Poll::Pending,
Err(e) => Poll::Ready(Err(e)),
})
}
#[doc(hidden)]
pub fn ashutdown_inner(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Result<Option<ShutdownStatus>, Error> {
self.set_waker(cx.waker());
let Some(mut conn) = self.established() else {
return Err(Error::Io(IoError::EndOfStream));
};
loop {
match conn.sync_shutdown()? {
Some(ShutdownStatus::CloseNotifyPosted) => {}
status => return Ok(status),
}
}
}
/// Asynchronously shut down the connection.
pub fn async_shutdown<'a>(
mut self: Pin<&'a mut Self>,
) -> impl 'a + Send + Future<Output = Result<(), Error>> {
poll_fn(move |cx| match self.as_mut().ashutdown_inner(cx) {
Ok(Some(ShutdownStatus::CloseNotifyReceived)) => Poll::Ready(Ok(())),
Ok(Some(ShutdownStatus::EndOfStream)) => {
Poll::Ready(Err(Error::Io(IoError::EndOfStream)))
}
Ok(Some(ShutdownStatus::RemainingApplicationData)) => Poll::Ready(Err(
Error::TlsReason(crate::errors::TlsErrorReason::ApplicationDataOnShutdown),
)),
Ok(Some(ShutdownStatus::CloseNotifyPosted)) => unreachable!(),
Ok(None) => Poll::Pending,
Err(e) => Poll::Ready(Err(e)),
})
}
}
#[cfg(feature = "std")]
mod stdio;