| // 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. |
| |
| use alloc::boxed::Box; |
| use core::{ |
| ffi::{c_int, c_long, c_void}, |
| ptr::{NonNull, null_mut}, |
| task::Waker, |
| }; |
| use std::marker::PhantomData; |
| |
| use once_cell::sync::Lazy; |
| |
| 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 { |
| 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); |
| } |
| } |
| } |
| |
| impl<Mode: HasTlsConnectionMethod> Methods for RustConnectionMethods<Mode> { |
| unsafe extern "C" fn from_ssl<'a>(ssl: *mut bssl_sys::SSL) -> Option<&'a mut Self> { |
| unsafe { |
| // Safety: `ssl` is originated from `TlsConnection::from_ssl`. |
| let methods = bssl_sys::SSL_get_ex_data(ssl, Mode::registration()); |
| debug_assert!( |
| !methods.is_null(), |
| "connection method should have been attached at construction time" |
| ); |
| // Safety: `ctx` is originated from `Box::into_raw` |
| Some(&mut *(methods as *mut RustConnectionMethods<Mode>)) |
| } |
| } |
| } |
| |
| // NOTE(@xfding): the reason that we are not using the `register_ex_data` macro is because |
| // declarative macros today cannot handle generics well enough. |
| fn register_tls_connection_vtable<Mode: HasTlsConnectionMethod>() -> c_int { |
| let ret = unsafe { |
| // Safety: this a one-time registration uses only valid function pointers. |
| bssl_sys::SSL_get_ex_new_index( |
| 0, |
| null_mut(), |
| null_mut(), |
| None, |
| Some(drop_box_rust_methods::<RustConnectionMethods<Mode>>), |
| ) |
| }; |
| if ret < 0 { |
| panic!("Failed to register TLS Connection ex-data") |
| } else { |
| ret |
| } |
| } |
| |
| /// Safety: |
| /// - `ssl` must be a `SSL` object constructed by [`crate::connection::TlsConnection`]. |
| /// - `ssl` must be exclusively owned. |
| #[allow(unused)] // This will be used in the following patch to support async I/O. |
| pub(crate) unsafe fn waker_data_from_ssl(ssl: NonNull<bssl_sys::SSL>) -> Option<Waker> { |
| unsafe { |
| // Safety: `ssl` outlives `'a` and is constructed by `TlsConnection`. |
| <ExDataRegistration as ExData<Option<Waker>>>::get_mut(ssl).clone() |
| } |
| } |
| |
| /// Safety: |
| /// - `ssl` must be a `SSL` object constructed by [`crate::connection::TlsConnection`] and |
| /// outlives `'a`. |
| /// - `ssl` must be exclusively owned. |
| pub(super) unsafe fn waker_data_ref_from_ssl<'a>( |
| ssl: NonNull<bssl_sys::SSL>, |
| ) -> &'a mut Option<Waker> { |
| unsafe { |
| // Safety: `ssl` outlives `'a` and is constructed by `TlsConnection`. |
| <ExDataRegistration as ExData<Option<Waker>>>::get_mut(ssl) |
| } |
| } |
| |
| pub(crate) struct ExDataRegistration; |
| |
| pub(crate) trait ExData<T: Default> { |
| /// Initialise the ex-data slot for type `T` |
| /// |
| /// Safety: |
| /// - Caller must ensure exclusive access to `ssl` handle. |
| /// - Caller must ensure that `ssl` is constructed by [`super::TlsConnection`]. |
| unsafe fn init(ssl: NonNull<bssl_sys::SSL>); |
| /// Take a mutable reference to the ex_data. |
| /// |
| /// Safety: |
| /// - Caller must ensure exclusive access to `ssl` handle. |
| /// - Caller must ensure that `ssl` is constructed by [`super::TlsConnection`]. |
| /// - Caller must ensure that `ssl` outlives `'a`. |
| /// - Caller must ensure that the underlying ex-data shall never be aliased. |
| unsafe fn get_mut<'a>(ssl: NonNull<bssl_sys::SSL>) -> &'a mut T; |
| } |
| |
| // NOTE(@xfding): the reason we have this macro is because Rust `static`s do not support generics. |
| macro_rules! register_ex_data { |
| ($T:ty) => { |
| const _: () = { |
| fn _assert() |
| where |
| $T: Sized, |
| { |
| } |
| unsafe extern "C" fn destructor( |
| _parent: *mut c_void, |
| ptr: *mut c_void, |
| _ad: *mut bssl_sys::CRYPTO_EX_DATA, |
| _index: c_int, |
| _argl: c_long, |
| _argp: *mut c_void, |
| ) { |
| abort_on_panic(|| unsafe { |
| if ptr.is_null() { |
| return; |
| } |
| // Safety: this ex_data must be registered by `TlsConnection`, so the type must have |
| // been `T`. |
| let _ = Box::from_raw(ptr as *mut $T); |
| }); |
| } |
| fn register() -> c_int { |
| let ret = unsafe { |
| // Safety: this a one-time registration uses only valid function pointers. |
| bssl_sys::SSL_get_ex_new_index( |
| 0, |
| null_mut(), |
| null_mut(), |
| None, |
| Some(destructor), |
| ) |
| }; |
| if ret < 0 { |
| panic!("Failed to register TLS Connection waker ex-data") |
| } else { |
| ret |
| } |
| } |
| |
| static REGISTER: Lazy<c_int> = Lazy::new(register); |
| impl ExData<$T> for ExDataRegistration { |
| unsafe fn init(ptr: NonNull<bssl_sys::SSL>) { |
| unsafe { |
| bssl_sys::SSL_set_ex_data( |
| ptr.as_ptr(), |
| *REGISTER, |
| Box::into_raw(Box::new(<$T>::default())) as _, |
| ); |
| } |
| } |
| unsafe fn get_mut<'a>(ssl: NonNull<bssl_sys::SSL>) -> &'a mut $T { |
| if let Some(mut data) = unsafe { |
| // Safety: `ssl` outlives `'a`. |
| NonNull::new(bssl_sys::SSL_get_ex_data(ssl.as_ptr(), *REGISTER) as *mut $T) |
| } { |
| unsafe { |
| // Safety: `ssl` is still alive and constructed by `TlsConnection`. |
| return data.as_mut(); |
| } |
| } |
| unsafe { |
| // Safety: `ssl` is constructed by `TlsConnection`. |
| <Self as ExData<$T>>::init(ssl); |
| } |
| let mut data = unsafe { |
| // Safety: `ssl` outlives `'a`. |
| NonNull::new(bssl_sys::SSL_get_ex_data(ssl.as_ptr(), *REGISTER) as *mut $T) |
| .unwrap() |
| }; |
| unsafe { |
| // Safety: `ssl` is still alive and constructed by `TlsConnection`. |
| data.as_mut() |
| } |
| } |
| } |
| }; |
| }; |
| } |
| |
| register_ex_data!(Option<Waker>); |
| |
| pub(crate) trait HasTlsConnectionMethod { |
| fn registration() -> c_int; |
| } |
| |
| impl HasTlsConnectionMethod for TlsMode { |
| #[inline(always)] |
| fn registration() -> c_int { |
| static TLS_CONTEXT_METHOD: Lazy<c_int> = |
| Lazy::new(register_tls_connection_vtable::<TlsMode>); |
| *TLS_CONTEXT_METHOD |
| } |
| } |
| |
| impl HasTlsConnectionMethod for QuicMode { |
| #[inline(always)] |
| fn registration() -> c_int { |
| static TLS_CONTEXT_METHOD: Lazy<c_int> = |
| Lazy::new(register_tls_connection_vtable::<QuicMode>); |
| *TLS_CONTEXT_METHOD |
| } |
| } |