rust: bssl-tls: Introduce I/O model Bug: 479599893 Signed-off-by: Xiangfei Ding <xfding@google.com> Change-Id: I26f09e3b18a446c7cf7cf180059bcf046a6a6964 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/91407 Reviewed-by: Adam Langley <agl@google.com>
diff --git a/rust/bssl-tls/src/connection.rs b/rust/bssl-tls/src/connection.rs index 39dd454..7944996 100644 --- a/rust/bssl-tls/src/connection.rs +++ b/rust/bssl-tls/src/connection.rs
@@ -16,6 +16,7 @@ use alloc::boxed::Box; use core::{ + ffi::c_int, marker::PhantomData, mem::transmute, ops::{Deref, DerefMut}, @@ -24,12 +25,17 @@ }; use crate::{ - config::ProtocolVersion, connection::methods::waker_data_ref_from_ssl, context::TlsMode, + config::ProtocolVersion, + connection::methods::waker_data_ref_from_ssl, + context::TlsMode, + errors::{Error, TlsRetryReason}, + io::IoStatus, }; mod credentials; pub mod lifecycle; pub(crate) mod methods; +pub mod transport; /// Server role - the connection runs as a server. pub enum Server {} @@ -108,7 +114,26 @@ } } -#[allow(unused)] +impl<R, M> TlsConnectionRef<R, M> { + #[allow(unused)] + pub(crate) fn categorise_error_for_io(&self, rc: c_int) -> Result<IoStatus, Error> { + let reason = unsafe { + // Safety: we only want to extract the last I/O error on an existing valid connection. + bssl_sys::SSL_get_error(self.ptr(), rc) + }; + let res = match TlsRetryReason::try_from(reason) { + Ok(TlsRetryReason::PeerCloseNotify) => Ok(IoStatus::EndOfStream), + Ok(reason) => Ok(IoStatus::Retry(reason)), + Err(_) => Err(Error::extract_lib_err()), + }; + unsafe { + // Safety: we only clear the error on the current thread. + bssl_sys::ERR_clear_error(); + } + res + } +} + impl<R, M> TlsConnectionRef<R, M> where M: methods::HasTlsConnectionMethod, @@ -146,6 +171,8 @@ } else { *waker_data = Some(waker.clone()); } + let methods = self.get_connection_methods(); + methods.set_waker(waker); } }
diff --git a/rust/bssl-tls/src/connection/lifecycle.rs b/rust/bssl-tls/src/connection/lifecycle.rs index 6086290..a0ec5bd 100644 --- a/rust/bssl-tls/src/connection/lifecycle.rs +++ b/rust/bssl-tls/src/connection/lifecycle.rs
@@ -84,6 +84,14 @@ } } +impl<R> TlsConnectionRef<R, TlsMode> { + /// Inspect if the connection is suspended for which reason, after invocation of I/O methods. + pub fn take_pending_reason(&mut self) -> Option<TlsRetryReason> { + let methods = self.get_connection_methods(); + methods.take_pending_reason() + } +} + /// A handle to the connection that is valid only during handshake. #[repr(transparent)] pub struct TlsConnectionInHandshake<'a, R, M>(pub(crate) &'a mut TlsConnectionRef<R, M>);
diff --git a/rust/bssl-tls/src/connection/methods.rs b/rust/bssl-tls/src/connection/methods.rs index 498ee4b..d883d60 100644 --- a/rust/bssl-tls/src/connection/methods.rs +++ b/rust/bssl-tls/src/connection/methods.rs
@@ -25,17 +25,38 @@ use crate::{ Methods, abort_on_panic, context::{QuicMode, TlsMode}, + errors::TlsRetryReason, + io::RustBioHandle, methods::drop_box_rust_methods, }; /// The associated state to the [`super::TlsConnection`]. pub(super) struct RustConnectionMethods<Mode> { + /// A handle to a `BIO` managed by this crate. + pub bio: Option<RustBioHandle>, + /// A mailbox to propagate IO retrying reasons. + pub pending_reason: Option<TlsRetryReason>, _p: PhantomData<fn() -> Mode>, } impl<M> RustConnectionMethods<M> { pub fn new() -> Self { - Self { _p: PhantomData } + Self { + bio: None, + pending_reason: None, + _p: PhantomData, + } + } + + pub fn take_pending_reason(&mut self) -> Option<TlsRetryReason> { + self.pending_reason.take() + } + + /// Propagate waker to the handlers. + pub fn set_waker(&mut self, waker: &Waker) { + if let Some(bio) = &mut self.bio { + bio.set_waker(waker); + } } }
diff --git a/rust/bssl-tls/src/connection/transport.rs b/rust/bssl-tls/src/connection/transport.rs new file mode 100644 index 0000000..0c3a694 --- /dev/null +++ b/rust/bssl-tls/src/connection/transport.rs
@@ -0,0 +1,88 @@ +// 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 Connection transport settings +//! + +use crate::{ + connection::TlsConnectionRef, + context::TlsMode, + errors::Error, + io::{AbstractReader, AbstractSocket, AbstractWriter, RustBio}, +}; + +/// # Transport configurations +/// +/// These are the methods to configure the underlying IO drivers and transport configurations. +impl<R> TlsConnectionRef<R, TlsMode> { + /// Set up underlying transport driver. + pub fn set_io<S: 'static + AbstractSocket>(&mut self, socket: S) -> Result<&mut Self, Error> { + let bio = RustBio::new_duplex(socket)?; + unsafe { + // Safety: the additional ref-count is to compensate for `SSL` taking ownership. + bssl_sys::BIO_up_ref(bio.ptr()); + // Safety: the `bio` pointer has been sanitised and `self.0` is still valid. + bssl_sys::SSL_set_bio(self.ptr(), bio.ptr(), bio.ptr()); + } + let methods = self.get_connection_methods(); + methods.bio = Some(bio); + Ok(self) + } + + /// Set up underlying transport driver, with a pair of read and write ends. + pub fn set_split_io<Reader, Writer>( + &mut self, + read: Reader, + write: Writer, + ) -> Result<&mut Self, Error> + where + Reader: 'static + AbstractReader, + Writer: 'static + AbstractWriter, + { + let bio = RustBio::new_split(read, write)?; + unsafe { + // Safety: the additional ref-count is to compensate for `SSL` taking ownership. + bssl_sys::BIO_up_ref(bio.ptr()); + // Safety: the `bio` pointer has been sanitised and `self.0` is still valid. + bssl_sys::SSL_set_bio(self.ptr(), bio.ptr(), bio.ptr()); + } + let methods = self.get_connection_methods(); + methods.bio = Some(bio); + Ok(self) + } + + /// Check if the underlying **transport** has closed its write end. + pub fn is_write_closed(&self) -> bool { + self.get_connection_methods_ref() + .bio + .as_ref() + .map_or(true, |bio| bio.as_ref().write_eos) + } + + /// Check if the underlying **transport** has closed its read end. + pub fn is_read_closed(&self) -> bool { + self.get_connection_methods_ref() + .bio + .as_ref() + .map_or(true, |bio| bio.as_ref().read_eos) + } + + /// Check if the underlying **transport** has closed either its read end or its write end. + pub fn is_one_side_closed(&self) -> bool { + self.get_connection_methods_ref() + .bio + .as_ref() + .map_or(true, |bio| bio.as_ref().read_eos || bio.as_ref().write_eos) + } +}
diff --git a/rust/bssl-tls/src/ffi.rs b/rust/bssl-tls/src/ffi.rs index 76fd7d8..dfa7d41 100644 --- a/rust/bssl-tls/src/ffi.rs +++ b/rust/bssl-tls/src/ffi.rs
@@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::ptr::null; +use core::{ + ptr::null, + slice::{from_raw_parts, from_raw_parts_mut}, +}; /// We follow the convention in [`bssl_crypto::FfiSlice`] by which we signal empty arrays as `null`. /// An empty Rust slice has a non-zero address value, out of the necessity of enabling pointer-niche @@ -25,3 +28,47 @@ (slice.as_ptr(), slice.len()) } } + +/// Sanitize the data pointer and length and reconstitute the slice. +/// +/// This method returns an empty slice if the length is 0 or the pointer is NULL. +/// # Safety +/// Caller must ensure that `'a` outlives `input`. +#[inline] +pub(crate) unsafe fn sanitize_slice<'a, T>(input: *const T, len: usize) -> Option<&'a [T]> { + if len == 0 || input.is_null() { + return Some(&[]); + } + if !input.is_aligned() || len.checked_mul(size_of::<T>())? >= isize::MAX as usize { + return None; + } + unsafe { + // Safety: the pointer and the size has been sanitised. + Some(from_raw_parts(input, len)) + } +} + +/// Sanitize the data pointer and length and reconstitute the mutable slice. +/// +/// `capacity` counts the number of `T`s that `out` can hold, **not number of bytes**. +/// +/// This method returns an empty slice if the length is 0 or the pointer is NULL. +/// # Safety +/// Caller must ensure that `'a` outlives `input`. +#[inline] +pub(crate) unsafe fn sanitise_mut_slice<'a, T>( + out: *mut T, + capacity: usize, +) -> Option<&'a mut [T]> { + if capacity == 0 || out.is_null() || !out.is_aligned() { + return Some(&mut []); + } + if capacity.checked_mul(size_of::<T>())? >= isize::MAX as usize { + return None; + } + unsafe { + // Safety: `out` is 1-aligned and `0` is a valid pattern for `u8`. + core::ptr::write_bytes(out, 0, capacity); + Some(from_raw_parts_mut(out, capacity)) + } +}
diff --git a/rust/bssl-tls/src/io.rs b/rust/bssl-tls/src/io.rs new file mode 100644 index 0000000..0204ef2 --- /dev/null +++ b/rust/bssl-tls/src/io.rs
@@ -0,0 +1,532 @@ +// 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::{CStr, c_char, c_int, c_long, c_void}, + fmt, + ptr::NonNull, + task::{Context, Waker}, +}; + +use once_cell::sync::Lazy; + +use crate::{ + abort_on_panic, + errors::{Error, TlsRetryReason}, + ffi::{sanitise_mut_slice, sanitize_slice}, +}; + +/// A wrapper around a `dyn AbstractSocket`, delegating BIO methods to the +/// underlying `AbstractSocket` implementations. +/// +/// # Safety +/// +/// [`RustBio`] can only be accessed through exclusive ownership. +// TODO(@xfding): switch to `derive(CoercePointee)` when it stabilises for flattening the layout. +pub(crate) struct RustBio { + // We may need to propagate the waker, but nothing more. + waker: Option<Waker>, + pub read_eos: bool, + pub write_eos: bool, + socket: Option<Box<dyn AbstractSocket>>, + reader: Option<Box<dyn AbstractReader>>, + writer: Option<Box<dyn AbstractWriter>>, + io_err: Option<Box<dyn core::error::Error + Send + Sync>>, +} + +/// Safety: `socket` field is a exclusively owned `Box<dyn AbstractSocket>` pointer, +/// and `AbstractSocket: Send + Sync`. +unsafe impl Send for RustBio {} +unsafe impl Sync for RustBio {} + +fn _assert_rust_bio() +where + RustBio: Send + Unpin, +{ +} + +/// Purely module-internal implementation details +impl RustBio { + fn init() -> Self { + Self { + waker: None, + socket: None, + reader: None, + writer: None, + read_eos: false, + write_eos: false, + io_err: None, + } + } + + pub fn attach_socket(&mut self, socket: Box<dyn AbstractSocket>) { + self.socket = Some(socket); + } + + pub fn attach_reader(&mut self, reader: Box<dyn AbstractReader>) { + self.reader = Some(reader); + } + + pub fn attach_writer(&mut self, writer: Box<dyn AbstractWriter>) { + self.writer = Some(writer); + } + + pub fn get_reader(&mut self) -> Option<&mut dyn AbstractReader> { + if let Some(reader) = &mut self.reader { + Some(&mut **reader) + } else if let Some(socket) = &mut self.socket { + Some(&mut **socket) + } else { + None + } + } + + pub fn get_writer(&mut self) -> Option<&mut dyn AbstractWriter> { + if let Some(writer) = &mut self.writer { + Some(&mut **writer) + } else if let Some(socket) = &mut self.socket { + Some(&mut **socket) + } else { + None + } + } +} + +/// Crate-level `BIO` implementation details +impl RustBio { + pub fn set_waker(&mut self, waker: &Waker) { + if let Some(other_waker) = &self.waker + && waker.will_wake(other_waker) + { + return; + } + self.waker = Some(waker.clone()); + } + + pub fn new_duplex<T: 'static + AbstractSocket + Sized>( + socket: T, + ) -> Result<RustBioHandle, Error> { + let bio = unsafe { + // Safety: the BIO_METH will be valid if this is the first call. + bssl_sys::BIO_new(get_bio_method()) + }; + let bio = NonNull::new(bio).expect("allocation failure"); + unsafe { + // Safety: `bio` was constructed with our own BIO_METH, so the data must be valid + // as a `RustBio` + rust_bio_data_mut(bio.as_ptr()).attach_socket(Box::new(socket)); + } + Ok(RustBioHandle(bio)) + } + + pub fn new_split<R, W>(reader: R, writer: W) -> Result<RustBioHandle, Error> + where + R: 'static + AbstractReader + Sized, + W: 'static + AbstractWriter + Sized, + { + let bio = unsafe { + // Safety: the BIO_METH will be valid if this is the first call. + bssl_sys::BIO_new(get_bio_method()) + }; + let bio = NonNull::new(bio).expect("allocation failure"); + let data = unsafe { + // Safety: `bio` was constructed with our own BIO_METH, so the data must be valid + // as a `RustBio` + rust_bio_data_mut(bio.as_ptr()) + }; + data.attach_reader(Box::new(reader)); + data.attach_writer(Box::new(writer)); + Ok(RustBioHandle(bio)) + } + + fn transform_result( + &mut self, + res: AbstractSocketResult, + reason_on_retry: TlsRetryReason, + ) -> IoStatus { + match res { + AbstractSocketResult::Ok(bytes) => IoStatus::Ok(bytes), + AbstractSocketResult::Retry => IoStatus::Retry(reason_on_retry), + AbstractSocketResult::EndOfStream => IoStatus::EndOfStream, + AbstractSocketResult::Err(e) => { + self.io_err = Some(e); + IoStatus::Err + } + } + } +} + +/// A exclusively owned handle to a BIO constructed by this crate. +pub(crate) struct RustBioHandle(NonNull<bssl_sys::BIO>); + +impl RustBioHandle { + pub fn ptr(&self) -> *mut bssl_sys::BIO { + self.0.as_ptr() + } + + pub fn as_mut(&mut self) -> &mut RustBio { + unsafe { + // Safety: `self` witnesses the validity of the handle. + rust_bio_data_mut(self.ptr()) + } + } + + pub fn as_ref(&self) -> &RustBio { + unsafe { + // Safety: `self` witnesses the validity of the handle. + rust_bio_data(self.ptr()) + } + } + + pub fn set_waker(&mut self, waker: &Waker) { + self.as_mut().set_waker(waker); + } +} + +impl Drop for RustBioHandle { + fn drop(&mut self) { + unsafe { + // Safety: the BIO handle should still be valid and was created by this crate. + bssl_sys::BIO_free(self.0.as_ptr()); + } + } +} + +/// Safety: caller must ensure that `bio` is created with `rust_bio_create` and outlives `'a` +/// for exclusive access. +unsafe fn rust_bio_data_mut<'a>(bio: *mut bssl_sys::BIO) -> &'a mut RustBio { + let data = unsafe { + // Safety: `bio` is still valid + bssl_sys::BIO_get_data(bio) + }; + unsafe { &mut *(data as *mut RustBio) } +} + +/// Safety: caller must ensure that `bio` is created with `rust_bio_create` and outlives `'a` +/// for shared access. +unsafe fn rust_bio_data<'a>(bio: *mut bssl_sys::BIO) -> &'a RustBio { + let data = unsafe { + // Safety: `bio` is still valid + bssl_sys::BIO_get_data(bio) + }; + unsafe { &*(data as *const RustBio) } +} + +/// I/O Status of the possibly pending operation. +#[derive(Debug, Clone, Copy)] +pub enum IoStatus { + /// Successfully performed I/O of bytes at certain size. + Ok(usize), + /// There is no more data to read or write. + EndOfStream, + /// I/O operation should be retried with the exactly same buffers when applicable. + Retry(TlsRetryReason), + /// There is no backing socket. + Empty, + /// I/O operation has failed. + Err, +} + +/// Result of operating an [`AbstractSocket`]. +pub enum AbstractSocketResult { + /// I/O completed by committing some amount of bytes. + Ok(usize), + /// I/O is pending completion; the I/O operation should be invoked again with the same parameter. + Retry, + /// I/O is impossible because the stream has ended. + EndOfStream, + /// I/O operation failed. + Err(Box<dyn core::error::Error + Send + Sync>), +} + +/// Abstract reader. +pub trait AbstractReader: Send + Sync + Unpin { + /// Read data from the socket. + fn read( + &mut self, + async_ctx: Option<&mut Context<'_>>, + buffer: &mut [u8], + ) -> AbstractSocketResult; +} + +/// Abstract writer. +pub trait AbstractWriter: Send + Sync + Unpin { + /// Write data to the socket. + fn write(&mut self, async_ctx: Option<&mut Context<'_>>, buffer: &[u8]) + -> AbstractSocketResult; + /// Flush the socket. + fn flush(&mut self, async_ctx: Option<&mut Context<'_>>) -> AbstractSocketResult; +} + +/// Abstract socket wrapper around Rust types that may support async I/O. +pub trait AbstractSocket: AbstractReader + AbstractWriter {} + +// NOTE: this is not dead code, we are asserting that `dyn AbstractSocket` is a well-formed type, +// or `AbstractSocket` is dyn-compatible specifically. +fn _assert_dyn_compat() +where + dyn AbstractSocket:, +{ +} + +fn get_bio_type() -> c_int { + static BIO_TYPE: Lazy<c_int> = Lazy::new(|| { + // Safety: this call does not have side-effect other than ID assignment + unsafe { bssl_sys::BIO_get_new_index() } + }); + *BIO_TYPE +} + +struct BioMethod(*mut bssl_sys::BIO_METHOD); +/// Safety: once constructed this BIO vtable will stay immutable. +unsafe impl Send for BioMethod {} +/// Safety: once constructed this BIO vtable will stay immutable. +unsafe impl Sync for BioMethod {} + +fn get_bio_method() -> *const bssl_sys::BIO_METHOD { + static BIO_METHOD: Lazy<BioMethod> = Lazy::new(|| { + let cstr = const { + if let Ok(cstr) = CStr::from_bytes_with_nul(b"rust_bio\0") { + cstr + } else { + // Compile-time assertion + unreachable!() + } + }; + let vtable = unsafe { + // Safety: this call does not have side-effect other than allocation. + bssl_sys::BIO_meth_new(get_bio_type(), cstr.as_ptr()) + }; + unsafe { + // Safety: all the following calls are simple assignments to the vtable entries. + bssl_sys::BIO_meth_set_read(vtable, Some(rust_bio_read)); + bssl_sys::BIO_meth_set_write(vtable, Some(rust_bio_write)); + bssl_sys::BIO_meth_set_ctrl(vtable, Some(rust_bio_ctrl)); + bssl_sys::BIO_meth_set_create(vtable, Some(rust_bio_create)); + bssl_sys::BIO_meth_set_destroy(vtable, Some(rust_bio_destroy)); + } + BioMethod(vtable) + }); + BIO_METHOD.0 +} + +unsafe extern "C" fn rust_bio_read( + bio: *mut bssl_sys::BIO, + buffer: *mut c_char, + len: c_int, +) -> c_int { + let rust_bio = unsafe { + // Safety: `bio` is still valid and so is the `RustBio` which we have exclusive access to. + rust_bio_data_mut(bio) + }; + if rust_bio.read_eos { + return 0; + } + let Ok(len) = usize::try_from(len) else { + return -1; + }; + let waker = rust_bio.waker.clone(); + let mut async_ctx = if let Some(waker) = &waker { + Some(Context::from_waker(waker)) + } else { + None + }; + // Zero the buffer now. + // TODO(@xfding): maybe we want to have a buffer wrapper that tracks initialised region. + let Some(buf) = (unsafe { + // Safety: `buffer` is valid for holding `len` bytes by BoringSSL invariants. + buffer.write_bytes(0, len); + // Safety: `buffer` and `len` are sanitised and initialised for the right memory region. + sanitise_mut_slice(buffer as *mut u8, len) + }) else { + return -1; + }; + let work = { + let Some(reader) = rust_bio.get_reader() else { + return -1; + }; + move || reader.read(async_ctx.as_mut(), buf) + }; + let res = abort_on_panic(work); + match rust_bio.transform_result(res, TlsRetryReason::WantRead) { + IoStatus::Ok(bytes) => { + if let Ok(bytes) = c_int::try_from(bytes) { + return bytes; + } + -1 + } + IoStatus::EndOfStream => { + rust_bio.read_eos = true; + -1 + } + IoStatus::Retry(_) => { + unsafe { + // Safety: `bio` is still valid now. + bssl_sys::BIO_set_retry_read(bio); + } + -1 + } + IoStatus::Empty | IoStatus::Err => -1, + } +} + +unsafe extern "C" fn rust_bio_write( + bio: *mut bssl_sys::BIO, + buffer: *const c_char, + len: c_int, +) -> c_int { + let rust_bio = unsafe { + // Safety: `bio` is still valid and so is the `RustBio` which we have exclusive access to. + rust_bio_data_mut(bio) + }; + if rust_bio.write_eos { + return 0; + } + let Ok(len) = usize::try_from(len) else { + return -1; + }; + let waker = rust_bio.waker.clone(); + let mut async_ctx = if let Some(waker) = &waker { + Some(Context::from_waker(waker)) + } else { + None + }; + let Some(buf) = (unsafe { + // Safety: `buffer` and `len` are sanitised and initialised for the right memory region. + sanitize_slice(buffer as *mut u8, len) + }) else { + return -1; + }; + let work = { + let Some(writer) = rust_bio.get_writer() else { + return -1; + }; + move || writer.write(async_ctx.as_mut(), buf) + }; + let res = abort_on_panic(work); + match rust_bio.transform_result(res, TlsRetryReason::WantWrite) { + IoStatus::Ok(bytes) => { + if let Ok(bytes) = c_int::try_from(bytes) { + return bytes; + } + -1 + } + IoStatus::EndOfStream => { + rust_bio.write_eos = true; + 0 + } + IoStatus::Retry(_) => { + unsafe { + // Safety: `bio` is still valid now. + bssl_sys::BIO_set_retry_write(bio); + } + -1 + } + IoStatus::Empty | IoStatus::Err => -1, + } +} + +unsafe fn rust_bio_flush(bio: *mut bssl_sys::BIO) -> c_long { + let rust_bio = unsafe { + // Safety: `bio` is still valid + rust_bio_data_mut(bio) + }; + if rust_bio.write_eos { + return 0; + } + let waker = rust_bio.waker.clone(); + let mut async_ctx = if let Some(waker) = &waker { + Some(Context::from_waker(waker)) + } else { + None + }; + let work = { + let Some(writer) = rust_bio.get_writer() else { + return -1; + }; + move || writer.flush(async_ctx.as_mut()) + }; + let res = abort_on_panic(work); + match rust_bio.transform_result(res, TlsRetryReason::WantWrite) { + IoStatus::Ok(_) | IoStatus::Retry(_) => 1, + IoStatus::EndOfStream => { + rust_bio.write_eos = true; + 0 + } + IoStatus::Empty | IoStatus::Err => 0, + } +} + +unsafe extern "C" fn rust_bio_ctrl( + bio: *mut bssl_sys::BIO, + ctrl: c_int, + _: c_long, + _: *mut c_void, +) -> c_long { + match ctrl { + bssl_sys::BIO_CTRL_FLUSH => unsafe { + // Safety: `bio` is still valid. + rust_bio_flush(bio) + }, + _ => 0, + } +} + +unsafe extern "C" fn rust_bio_create(bio: *mut bssl_sys::BIO) -> c_int { + let data = Box::new(RustBio::init()); + let data = Box::into_raw(data); + unsafe { + // Safety: both `bio` and `data` are still valid and exclusively owned. + bssl_sys::BIO_set_data(bio, data as _); + // Safety: it is now already safe to mark BIO initialised. + bssl_sys::BIO_set_init(bio, 1); + } + 1 +} + +unsafe extern "C" fn rust_bio_destroy(bio: *mut bssl_sys::BIO) -> c_int { + let rust_bio = unsafe { + // Safety: `bio` is still valid + bssl_sys::BIO_get_data(bio) as *mut RustBio + }; + let rust_bio = unsafe { + // Safety: `rust_bio` is created from `rust_bio_create` + Box::from_raw(rust_bio) + }; + // Try to catch unwinding on the FFI boundary. + // NOTE: it is not safe to drop the error value because its destructor can panic again. + abort_on_panic(move || { + let _ = rust_bio; + }); + unsafe { + // Safety: `bio` is still valid, we just need to signal that it is inactive. + bssl_sys::BIO_set_init(bio, 0); + } + 1 +} + +/// Asynchronous methods were invoked outside `async` context +#[derive(Debug)] +pub struct NoAsyncContext; + +impl core::error::Error for NoAsyncContext {} + +impl fmt::Display for NoAsyncContext { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("async method is called outside async context") + } +}
diff --git a/rust/bssl-tls/src/lib.rs b/rust/bssl-tls/src/lib.rs index c3b5a33..bbe1fee 100644 --- a/rust/bssl-tls/src/lib.rs +++ b/rust/bssl-tls/src/lib.rs
@@ -41,6 +41,7 @@ pub mod credentials; pub mod errors; mod ffi; +pub mod io; mod methods; #[macro_use] #[doc(hidden)]