tfhe_c_api_dynamic_buffer/
lib.rs

1//! This crate provides a [`DynamicBuffer`] struct that allows to easily share a pointer to u8 with
2//! C APIs and free that pointer properly by carrying a `destructor_callback`. In that regard it is
3//! carrying a very barebone vtable so that freeing the memory pointed to by the [`DynamicBuffer`]
4//! is easy either on the C or Rust side.
5//!
6//! A `From` implementation is provided to convert a `Vec` of `u8` into a [`DynamicBuffer`] easily,
7//! the destructor being populated automatically.
8//!
9//! A [`DynamicBufferView`] is also provided to indicate that the struct does not own the data and
10//! is merely used to share data in a read-only way.
11
12#![cfg_attr(all(doc, not(doctest)), feature(doc_auto_cfg))]
13#![cfg_attr(all(doc, not(doctest)), feature(doc_cfg))]
14#![warn(rustdoc::broken_intra_doc_links)]
15
16use std::ffi::c_int;
17
18#[repr(C)]
19pub struct DynamicBufferView {
20    pub pointer: *const u8,
21    pub length: usize,
22}
23
24impl DynamicBufferView {
25    /// Returns a view to the memory borrowed by the [`DynamicBufferView`].
26    ///
27    /// # Safety
28    ///
29    /// This is safe to call as long as the pointer is valid and the length corresponds to the
30    /// length of the underlying buffer.
31    pub unsafe fn as_slice(&self) -> &[u8] {
32        std::slice::from_raw_parts(self.pointer, self.length)
33    }
34}
35
36impl From<&[u8]> for DynamicBufferView {
37    fn from(a: &[u8]) -> Self {
38        Self {
39            pointer: a.as_ptr(),
40            length: a.len(),
41        }
42    }
43}
44
45#[repr(C)]
46pub struct DynamicBuffer {
47    pub pointer: *mut u8,
48    pub length: usize,
49    pub destructor: Option<unsafe extern "C" fn(*mut u8, usize) -> c_int>,
50}
51
52impl DynamicBuffer {
53    /// Destroy the [`DynamicBuffer`] freeing the underlying memory using the provided
54    /// `destructor_callback` and clearing or zeroing all the members.
55    ///
56    /// If the `pointer` stored in [`DynamicBuffer`] is NULL, then `length` is zeroed out and the
57    /// `destructor_callback` is set to `None`. It is similar to how free ignores NULL in C, we just
58    /// do some additional housekeeping to signal the [`DynamicBuffer`] is an empty shell.
59    ///
60    /// # Safety
61    ///
62    /// Destroy is safe to call only if the `destructor_callback` is the method that needs to be
63    /// called to free the stored pointer. For example in C++, memory allocated with `new` must be
64    /// freed with `delete`, memory allocated with `new[]` must be freed with `delete[]`.
65    ///
66    /// Length must indicate how many `u8` are present in the allocation and can be used by the
67    /// `destructor_callback` to free memory. For example in the case of a `Vec` being turned into a
68    /// [`DynamicBuffer`] the length is obtained by first calling the `len` function on the `Vec`.
69    pub unsafe fn destroy(&mut self) -> Result<(), &str> {
70        if self.pointer.is_null() {
71            // Finish emptying stuff
72            self.length = 0;
73            self.destructor = None;
74            return Ok(());
75        }
76
77        match self.destructor {
78            Some(destructor_callback) => {
79                let res = destructor_callback(self.pointer, self.length);
80                if res == 0 {
81                    // If the deallocation is successful then we empty the buffer
82                    self.pointer = std::ptr::null_mut();
83                    self.length = 0;
84                    self.destructor = None;
85                    return Ok(());
86                }
87                Err("destructor returned a non 0 error code")
88            }
89            // We could not free because of a missing destructor, return an error
90            None => Err("destructor is NULL, could not destroy DynamicBuffer"),
91        }
92    }
93}
94
95unsafe extern "C" fn free_u8_ptr_built_from_vec_u8(pointer: *mut u8, length: usize) -> c_int {
96    if pointer.is_null() {
97        return 0;
98    }
99
100    let slice = std::slice::from_raw_parts_mut(pointer, length);
101
102    drop(Box::from_raw(slice));
103
104    0
105}
106
107impl From<Vec<u8>> for DynamicBuffer {
108    fn from(value: Vec<u8>) -> Self {
109        let boxed_slice = value.into_boxed_slice();
110        let length = boxed_slice.len();
111        let pointer = Box::leak(boxed_slice);
112
113        Self {
114            pointer: pointer.as_mut_ptr(),
115            length,
116            destructor: Some(free_u8_ptr_built_from_vec_u8),
117        }
118    }
119}
120
121fn check_ptr_is_non_null_and_aligned<T>(ptr: *const T) -> Result<(), String> {
122    if ptr.is_null() {
123        return Err(format!("pointer is null, got: {ptr:p}"));
124    }
125    let expected_alignment = std::mem::align_of::<T>();
126    if ptr as usize % expected_alignment != 0 {
127        return Err(format!(
128            "pointer is misaligned, expected {expected_alignment} bytes alignment, got pointer: \
129            {ptr:p}. You May have mixed some pointers in your function call.",
130        ));
131    }
132    Ok(())
133}
134
135/// Get a mutable reference from a pointer checking the pointer is well aligned for the given type.
136///
137/// # Safety
138///
139/// Caller of this function needs to make sure the pointer type corresponds to the data type being
140/// pointed to.
141///
142/// Caller of this function needs to make sure the aliasing rules for mutable reference are
143/// respected.
144///
145/// The basics are: at any time only a single mutable reference may exist to a given memory location
146/// XOR any number of immutable reference may exist to a given memory location.
147///
148/// Failure to abide by the above rules will result in undefined behavior (UB).
149unsafe fn get_mut_checked<'a, T>(ptr: *mut T) -> Result<&'a mut T, String> {
150    match check_ptr_is_non_null_and_aligned(ptr) {
151        Ok(()) => ptr
152            .as_mut()
153            .ok_or_else(|| "Error while converting to mut reference".into()),
154        Err(e) => Err(e),
155    }
156}
157
158/// C API to destroy a [`DynamicBuffer`].
159///
160/// # Safety
161///
162/// This function is safe to call if `dynamic_buffer` is not aliased to avoid double frees.
163#[no_mangle]
164pub unsafe extern "C" fn destroy_dynamic_buffer(dynamic_buffer: *mut DynamicBuffer) -> c_int {
165    // Mimicks C for calls of free on NULL, nothing occurs
166    if dynamic_buffer.is_null() {
167        return 0;
168    }
169
170    let dynamic_buffer = match get_mut_checked(dynamic_buffer) {
171        Ok(dynamic_buffer) => dynamic_buffer,
172        Err(cause) => {
173            if cfg!(feature = "c_api_print_error_source") {
174                println!("{cause}");
175            }
176
177            return 1;
178        }
179    };
180
181    match dynamic_buffer.destroy() {
182        Ok(_) => 0,
183        Err(cause) => {
184            if cfg!(feature = "c_api_print_error_source") {
185                println!("{cause}");
186            }
187
188            1
189        }
190    }
191}
192
193#[cfg(test)]
194mod test {
195    use super::*;
196
197    #[test]
198    fn test_dynamic_buffer_vec_u8_custom_destructor() {
199        let vec = vec![99u8; 1000];
200        let len = vec.len();
201        let ptr = vec.leak();
202
203        unsafe extern "C" fn custom_destroy_vec_u8_buffer(
204            pointer: *mut u8,
205            length: usize,
206        ) -> c_int {
207            if pointer.is_null() {
208                return 0;
209            }
210
211            let slice = std::slice::from_raw_parts_mut(pointer, length);
212
213            drop(Box::from_raw(slice));
214
215            0
216        }
217
218        let mut dynamic_buffer = DynamicBuffer {
219            pointer: ptr.as_mut_ptr(),
220            length: len,
221            destructor: Some(custom_destroy_vec_u8_buffer),
222        };
223
224        let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
225
226        assert_eq!(res, 0);
227        assert!(dynamic_buffer.pointer.is_null());
228        assert_eq!(dynamic_buffer.length, 0);
229        assert!(dynamic_buffer.destructor.is_none());
230
231        assert!(dynamic_buffer.pointer.is_null());
232        let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
233        // Same as free in C, destroy on a NULL pointer does nothing
234        assert_eq!(res, 0);
235        assert!(dynamic_buffer.pointer.is_null());
236        assert_eq!(dynamic_buffer.length, 0);
237        assert!(dynamic_buffer.destructor.is_none());
238
239        let mut some_u8 = 0u8;
240
241        dynamic_buffer.pointer = &mut some_u8 as *mut u8;
242
243        assert!(dynamic_buffer.destructor.is_none());
244        let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
245        assert_eq!(res, 1);
246    }
247
248    #[test]
249    fn test_dynamic_buffer_vec_u8_default_destructor() {
250        let vec = vec![99u8; 1000];
251
252        let mut dynamic_buffer: DynamicBuffer = vec.clone().into();
253
254        let res = unsafe { destroy_dynamic_buffer(&mut dynamic_buffer as *mut DynamicBuffer) };
255
256        assert_eq!(res, 0);
257        assert!(dynamic_buffer.pointer.is_null());
258        assert_eq!(dynamic_buffer.length, 0);
259        assert!(dynamic_buffer.destructor.is_none());
260
261        let mut dynamic_buffer: DynamicBuffer = vec.into();
262
263        let res = unsafe { dynamic_buffer.destroy() };
264
265        assert!(res.is_ok());
266        assert!(dynamic_buffer.pointer.is_null());
267        assert_eq!(dynamic_buffer.length, 0);
268        assert!(dynamic_buffer.destructor.is_none());
269    }
270}