| // 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; |