Skip to main content

compio_py_dynamic_openssl/
bio.rs

1// This module is mostly copied and modified from:
2// https://github.com/rust-openssl/rust-openssl/blob/openssl-v0.10.75/openssl/src/ssl/bio.rs
3//
4// SPDX-License-Identifier: Apache-2.0
5// Copyright 2011-2017 Google Inc.
6//           2013 Jack Lloyd
7//           2013-2014 Steven Fackler
8//
9// SPDX-License-Identifier: Apache-2.0 OR MulanPSL-2.0
10// Copyright 2026 Fantix King
11
12use std::{
13    any::Any,
14    ffi::{c_char, c_int, c_long, c_void},
15    io::{self, Read, Write},
16    panic::{AssertUnwindSafe, catch_unwind},
17    ptr, slice,
18};
19
20use crate::{
21    error::ErrorStack,
22    sys::{self as ffi, BIO},
23};
24
25pub struct StreamState<S> {
26    pub stream: S,
27    pub error: Option<io::Error>,
28    pub panic: Option<Box<dyn Any + Send>>,
29    pub dtls_mtu_size: c_long,
30}
31
32pub fn new<S: Read + Write>(stream: S) -> Result<(*mut BIO, BioMethod), ErrorStack> {
33    let ffi = crate::get();
34    let method = BioMethod::new::<S>()?;
35
36    let state = Box::new(StreamState {
37        stream,
38        error: None,
39        panic: None,
40        dtls_mtu_size: 0,
41    });
42
43    unsafe {
44        let bio = cvt_p((ffi.BIO_new)(method.0))?;
45        (ffi.BIO_set_data)(bio, Box::into_raw(state) as _);
46        (ffi.BIO_set_init)(bio, 1);
47
48        Ok((bio, method))
49    }
50}
51
52pub unsafe fn take_error<S>(bio: *mut BIO) -> Option<io::Error> {
53    let state = unsafe { state::<S>(bio) };
54    state.error.take()
55}
56
57pub unsafe fn take_panic<S>(bio: *mut BIO) -> Option<Box<dyn Any + Send>> {
58    let state = unsafe { state::<S>(bio) };
59    state.panic.take()
60}
61
62pub unsafe fn get_ref<'a, S: 'a>(bio: *mut BIO) -> &'a S {
63    let ffi = crate::get();
64    let state = unsafe { &*((ffi.BIO_get_data)(bio) as *const StreamState<S>) };
65    &state.stream
66}
67
68pub unsafe fn get_mut<'a, S: 'a>(bio: *mut BIO) -> &'a mut S {
69    &mut unsafe { state(bio) }.stream
70}
71
72unsafe fn state<'a, S: 'a>(bio: *mut BIO) -> &'a mut StreamState<S> {
73    let ffi = crate::get();
74    unsafe { &mut *((ffi.BIO_get_data)(bio) as *mut _) }
75}
76
77unsafe extern "C" fn bwrite<S: Write>(bio: *mut BIO, buf: *const c_char, len: c_int) -> c_int {
78    let ffi = crate::get();
79    unsafe { ffi.BIO_clear_retry_flags(bio) };
80
81    let state = unsafe { state::<S>(bio) };
82    let buf = unsafe { from_raw_parts(buf as *const _, len as usize) };
83
84    match catch_unwind(AssertUnwindSafe(|| state.stream.write(buf))) {
85        Ok(Ok(len)) => len as c_int,
86        Ok(Err(err)) => {
87            if retriable_error(&err) {
88                unsafe { ffi.BIO_set_retry_write(bio) };
89            }
90            state.error = Some(err);
91            -1
92        }
93        Err(err) => {
94            state.panic = Some(err);
95            -1
96        }
97    }
98}
99
100unsafe extern "C" fn bread<S: Read>(bio: *mut BIO, buf: *mut c_char, len: c_int) -> c_int {
101    let ffi = crate::get();
102    unsafe { ffi.BIO_clear_retry_flags(bio) };
103
104    let state = unsafe { state::<S>(bio) };
105    let buf = unsafe { from_raw_parts_mut(buf as *mut _, len as usize) };
106
107    match catch_unwind(AssertUnwindSafe(|| state.stream.read(buf))) {
108        Ok(Ok(len)) => len as c_int,
109        Ok(Err(err)) => {
110            if retriable_error(&err) {
111                unsafe { ffi.BIO_set_retry_read(bio) };
112            }
113            state.error = Some(err);
114            -1
115        }
116        Err(err) => {
117            state.panic = Some(err);
118            -1
119        }
120    }
121}
122
123fn retriable_error(err: &io::Error) -> bool {
124    match err.kind() {
125        io::ErrorKind::WouldBlock | io::ErrorKind::NotConnected => true,
126        _ => false,
127    }
128}
129
130unsafe extern "C" fn bputs<S: Write>(bio: *mut BIO, s: *const c_char) -> c_int {
131    unsafe {
132        let mut len = 0;
133        while *s.add(len) != 0 {
134            len += 1;
135        }
136        bwrite::<S>(bio, s, len as c_int)
137    }
138}
139
140unsafe extern "C" fn ctrl<S: Write>(
141    bio: *mut BIO,
142    cmd: c_int,
143    _num: c_long,
144    _ptr: *mut c_void,
145) -> c_long {
146    let state = unsafe { state::<S>(bio) };
147
148    if cmd == ffi::BIO_CTRL_FLUSH {
149        match catch_unwind(AssertUnwindSafe(|| state.stream.flush())) {
150            Ok(Ok(())) => 1,
151            Ok(Err(err)) => {
152                state.error = Some(err);
153                0
154            }
155            Err(err) => {
156                state.panic = Some(err);
157                0
158            }
159        }
160    } else if cmd == ffi::BIO_CTRL_DGRAM_QUERY_MTU {
161        state.dtls_mtu_size
162    } else {
163        0
164    }
165}
166
167unsafe extern "C" fn create(bio: *mut BIO) -> c_int {
168    let ffi = crate::get();
169    unsafe {
170        (ffi.BIO_set_init)(bio, 0);
171        (ffi.BIO_set_data)(bio, ptr::null_mut());
172        (ffi.BIO_set_flags)(bio, 0);
173    }
174    1
175}
176
177unsafe extern "C" fn destroy<S>(bio: *mut BIO) -> c_int {
178    if bio.is_null() {
179        return 0;
180    }
181
182    let ffi = crate::get();
183    unsafe {
184        let data = (ffi.BIO_get_data)(bio);
185        assert!(!data.is_null());
186        let _ = Box::<StreamState<S>>::from_raw(data as *mut _);
187        (ffi.BIO_set_data)(bio, ptr::null_mut());
188        (ffi.BIO_set_init)(bio, 0);
189    }
190    1
191}
192
193pub struct BioMethod(*mut ffi::BIO_METHOD);
194
195impl BioMethod {
196    fn new<S: Read + Write>() -> Result<Self, ErrorStack> {
197        let ffi = crate::get();
198        unsafe {
199            let method = cvt_p((ffi.BIO_meth_new)(
200                ffi::BIO_TYPE_NONE,
201                b"compio\n".as_ptr() as _,
202            ))?;
203            cvt((ffi.BIO_meth_set_write)(method, Some(bwrite::<S>)))?;
204            cvt((ffi.BIO_meth_set_read)(method, Some(bread::<S>)))?;
205            cvt((ffi.BIO_meth_set_puts)(method, Some(bputs::<S>)))?;
206            cvt((ffi.BIO_meth_set_ctrl)(method, Some(ctrl::<S>)))?;
207            cvt((ffi.BIO_meth_set_create)(method, Some(create)))?;
208            cvt((ffi.BIO_meth_set_destroy)(method, Some(destroy::<S>)))?;
209            Ok(Self(method))
210        }
211    }
212}
213
214impl Drop for BioMethod {
215    fn drop(&mut self) {
216        let ffi = crate::get();
217        unsafe {
218            (ffi.BIO_meth_free)(self.0);
219        }
220    }
221}
222
223pub(crate) unsafe fn from_raw_parts<'a, T>(data: *const T, len: usize) -> &'a [T] {
224    if len == 0 {
225        &[]
226    } else {
227        unsafe { slice::from_raw_parts(data, len) }
228    }
229}
230
231unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a mut [T] {
232    if len == 0 {
233        &mut []
234    } else {
235        unsafe { slice::from_raw_parts_mut(data, len) }
236    }
237}
238
239#[inline]
240pub(crate) fn cvt_p<T>(r: *mut T) -> Result<*mut T, ErrorStack> {
241    if r.is_null() {
242        Err(ErrorStack::get())
243    } else {
244        Ok(r)
245    }
246}
247
248#[inline]
249pub(crate) fn cvt(r: c_int) -> Result<c_int, ErrorStack> {
250    if r <= 0 {
251        Err(ErrorStack::get())
252    } else {
253        Ok(r)
254    }
255}