native_ossl/bio.rs
1//! BIO wrappers — `MemBio`, `MemBioBuf<'a>`, `Bio`.
2//!
3//! BIOs are OpenSSL's generic I/O abstraction. This module exposes three types:
4//!
5//! - [`MemBio`] — a writable, growable in-memory BIO (`BIO_s_mem()`). Used for
6//! encoding output (PEM, DER). Call `data()` after writing to read the result
7//! as a `&[u8]` slice without copying.
8//!
9//! - [`MemBioBuf<'a>`] — a read-only view of a caller-supplied slice
10//! (`BIO_new_mem_buf()`). Zero-copy input path for PEM parsing.
11//!
12//! - [`Bio`] — shared ownership wrapper around a raw `BIO*`. Used when OpenSSL
13//! needs a `BIO` that outlives the immediate call (e.g. TLS `SSL_set_bio`).
14
15use crate::error::ErrorStack;
16use native_ossl_sys as sys;
17use std::marker::PhantomData;
18use std::ptr;
19
20// ── MemBio — writable in-memory BIO ──────────────────────────────────────────
21
22/// A writable, growable in-memory BIO.
23///
24/// Data written to this BIO accumulates in an internal buffer managed by
25/// OpenSSL. After writing, `data()` returns a borrowed slice without copying.
26pub struct MemBio {
27 ptr: *mut sys::BIO,
28}
29
30impl MemBio {
31 /// Create a new empty writable `BIO_s_mem()` BIO.
32 ///
33 /// # Errors
34 ///
35 /// Returns `Err` if OpenSSL cannot allocate the BIO.
36 pub fn new() -> Result<Self, ErrorStack> {
37 let method = unsafe { sys::BIO_s_mem() };
38 if method.is_null() {
39 return Err(ErrorStack::drain());
40 }
41 let ptr = unsafe { sys::BIO_new(method) };
42 if ptr.is_null() {
43 return Err(ErrorStack::drain());
44 }
45 Ok(MemBio { ptr })
46 }
47
48 /// Write bytes into the BIO's internal buffer.
49 ///
50 /// # Errors
51 ///
52 /// Returns `Err` if the write fails.
53 pub fn write(&mut self, data: &[u8]) -> Result<(), ErrorStack> {
54 let mut written: usize = 0;
55 let rc = unsafe {
56 sys::BIO_write_ex(
57 self.ptr,
58 data.as_ptr().cast(),
59 data.len(),
60 std::ptr::addr_of_mut!(written),
61 )
62 };
63 if rc != 1 || written != data.len() {
64 return Err(ErrorStack::drain());
65 }
66 Ok(())
67 }
68
69 /// Borrow the current contents of the BIO's buffer as a `&[u8]`.
70 ///
71 /// The slice is valid until the next write operation or until `self` is dropped.
72 /// This is a zero-copy view — no allocation occurs.
73 #[must_use]
74 pub fn data(&self) -> &[u8] {
75 let mut ptr: *mut std::os::raw::c_char = ptr::null_mut();
76 // BIO_get_mem_data is the C macro equivalent of:
77 // BIO_ctrl(b, BIO_CTRL_INFO, 0, (char**)(pp))
78 // BIO_CTRL_INFO = 3.
79 let len = unsafe {
80 sys::BIO_ctrl(
81 self.ptr,
82 3, // BIO_CTRL_INFO
83 0,
84 (&raw mut ptr).cast::<std::os::raw::c_void>(),
85 )
86 };
87 if len <= 0 || ptr.is_null() {
88 return &[];
89 }
90 let n = usize::try_from(len).unwrap_or(0);
91 unsafe { std::slice::from_raw_parts(ptr.cast::<u8>(), n) }
92 }
93
94 /// Move the buffer contents into a freshly allocated `Vec<u8>`.
95 ///
96 /// Prefer `data()` when a borrow suffices.
97 #[must_use]
98 pub fn into_vec(self) -> Vec<u8> {
99 self.data().to_vec()
100 }
101
102 /// Return the raw `BIO*` pointer.
103 ///
104 /// The pointer is valid for the lifetime of `self`.
105 #[must_use]
106 #[allow(dead_code)] // used by x509/ssl modules added in Phase 7-8
107 pub(crate) fn as_ptr(&mut self) -> *mut sys::BIO {
108 self.ptr
109 }
110}
111
112impl Drop for MemBio {
113 fn drop(&mut self) {
114 unsafe { sys::BIO_free_all(self.ptr) };
115 }
116}
117
118// SAFETY: BIO_s_mem() BIOs do not reference external state.
119unsafe impl Send for MemBio {}
120
121// ── MemBioBuf — read-only view into a caller slice ───────────────────────────
122
123/// A read-only BIO wrapping a borrowed byte slice (`BIO_new_mem_buf()`).
124///
125/// Zero-copy: no data is copied from the slice. The `BIO*` pointer reads
126/// directly from the caller's memory. The lifetime `'a` ties the BIO to the
127/// source slice.
128pub struct MemBioBuf<'a> {
129 ptr: *mut sys::BIO,
130 _data: PhantomData<&'a [u8]>,
131}
132
133impl<'a> MemBioBuf<'a> {
134 /// Create a read-only BIO backed by `data`.
135 ///
136 /// OpenSSL reads from `data` directly; no copy occurs.
137 ///
138 /// # Errors
139 ///
140 /// Returns `Err` if OpenSSL cannot allocate the BIO wrapper, or if
141 /// `data.len()` exceeds `i32::MAX`.
142 pub fn new(data: &'a [u8]) -> Result<Self, ErrorStack> {
143 // BIO_new_mem_buf reads from the caller's slice directly.
144 // -1 means use data.len() (NUL-terminated string convention is not used here
145 // because we pass the explicit length).
146 let len = i32::try_from(data.len()).map_err(|_| ErrorStack::drain())?;
147 let ptr = unsafe { sys::BIO_new_mem_buf(data.as_ptr().cast(), len) };
148 if ptr.is_null() {
149 return Err(ErrorStack::drain());
150 }
151 Ok(MemBioBuf {
152 ptr,
153 _data: PhantomData,
154 })
155 }
156
157 /// Return the raw `BIO*` pointer.
158 #[must_use]
159 #[allow(dead_code)] // used by x509/ssl modules added in Phase 7-8
160 pub(crate) fn as_ptr(&self) -> *mut sys::BIO {
161 self.ptr
162 }
163}
164
165impl Drop for MemBioBuf<'_> {
166 fn drop(&mut self) {
167 unsafe { sys::BIO_free(self.ptr) };
168 }
169}
170
171// SAFETY: the slice reference `'a` bounds the BIO's use; it cannot outlive the slice.
172unsafe impl Send for MemBioBuf<'_> {}
173
174// ── Bio — shared ownership BIO ────────────────────────────────────────────────
175
176/// Shared ownership wrapper around a `BIO*`.
177///
178/// Used where OpenSSL takes ownership of a BIO (e.g. `SSL_set_bio`) or where
179/// the same BIO must be reachable from multiple Rust values. Implemented with
180/// `BIO_up_ref` / `BIO_free`.
181pub struct Bio {
182 ptr: *mut sys::BIO,
183}
184
185impl Bio {
186 /// Create a linked in-memory BIO pair suitable for in-process TLS.
187 ///
188 /// Returns `(bio1, bio2)` where data written to `bio1` is readable from
189 /// `bio2` and vice-versa. Pass each half to [`crate::ssl::Ssl::set_bio_duplex`] on
190 /// the client and server `Ssl` objects respectively.
191 ///
192 /// # Errors
193 ///
194 /// Returns `Err` if OpenSSL fails to allocate the pair.
195 pub fn new_pair() -> Result<(Self, Self), crate::error::ErrorStack> {
196 let mut b1: *mut sys::BIO = std::ptr::null_mut();
197 let mut b2: *mut sys::BIO = std::ptr::null_mut();
198 let rc = unsafe {
199 sys::BIO_new_bio_pair(
200 std::ptr::addr_of_mut!(b1),
201 0,
202 std::ptr::addr_of_mut!(b2),
203 0,
204 )
205 };
206 if rc != 1 {
207 return Err(crate::error::ErrorStack::drain());
208 }
209 Ok((Bio { ptr: b1 }, Bio { ptr: b2 }))
210 }
211
212 /// Wrap a raw `BIO*` transferring ownership to this `Bio`.
213 ///
214 /// # Safety
215 ///
216 /// `ptr` must be a valid, non-null `BIO*` that the caller is giving up ownership of.
217 #[must_use]
218 #[allow(dead_code)] // used by ssl tests
219 pub(crate) unsafe fn from_ptr_owned(ptr: *mut sys::BIO) -> Self {
220 Bio { ptr }
221 }
222
223 /// Return the raw `BIO*` pointer. Valid for the lifetime of `self`.
224 #[must_use]
225 pub(crate) fn as_ptr(&self) -> *mut sys::BIO {
226 self.ptr
227 }
228}
229
230impl Clone for Bio {
231 fn clone(&self) -> Self {
232 unsafe { sys::BIO_up_ref(self.ptr) };
233 Bio { ptr: self.ptr }
234 }
235}
236
237impl Drop for Bio {
238 fn drop(&mut self) {
239 unsafe { sys::BIO_free(self.ptr) };
240 }
241}
242
243// SAFETY: `BIO_up_ref` / `BIO_free` are thread-safe for memory BIOs.
244unsafe impl Send for Bio {}
245unsafe impl Sync for Bio {}
246
247// ── Tests ─────────────────────────────────────────────────────────────────────
248
249#[cfg(test)]
250mod tests {
251 use super::*;
252
253 #[test]
254 fn mem_bio_write_and_data() {
255 let mut bio = MemBio::new().unwrap();
256 bio.write(b"hello").unwrap();
257 bio.write(b" world").unwrap();
258 assert_eq!(bio.data(), b"hello world");
259 }
260
261 #[test]
262 fn mem_bio_empty() {
263 let bio = MemBio::new().unwrap();
264 assert_eq!(bio.data(), b"");
265 }
266
267 #[test]
268 fn mem_bio_buf_zero_copy() {
269 let source = b"PEM data goes here";
270 let bio = MemBioBuf::new(source).unwrap();
271 // Verify the BIO's internal read pointer equals the source slice pointer.
272 let mut char_ptr: *mut std::os::raw::c_char = ptr::null_mut();
273 // BIO_get_mem_data via BIO_ctrl(BIO_CTRL_INFO=3).
274 let len = unsafe {
275 sys::BIO_ctrl(
276 bio.as_ptr(),
277 3, // BIO_CTRL_INFO
278 0,
279 (&raw mut char_ptr).cast::<std::os::raw::c_void>(),
280 )
281 };
282 assert_eq!(usize::try_from(len).unwrap(), source.len());
283 // The data pointer must be the same as the source slice's pointer.
284 assert_eq!(char_ptr.cast::<u8>().cast_const(), source.as_ptr());
285 }
286
287 #[test]
288 fn bio_clone_shares_object() {
289 // Create a MemBio and wrap its underlying pointer in a Bio to test Clone.
290 let mut mem = MemBio::new().unwrap();
291 mem.write(b"test").unwrap();
292
293 // Build a Bio using the MemBio's pointer (up_ref first to share ownership).
294 let raw = mem.as_ptr();
295 unsafe { sys::BIO_up_ref(raw) };
296 let bio = unsafe { Bio::from_ptr_owned(raw) };
297 let bio2 = bio.clone();
298
299 // Both should point to the same BIO object.
300 assert_eq!(bio.as_ptr(), bio2.as_ptr());
301 }
302}