sodot_mpc/ffi/
mod.rs

1#[allow(warnings)]
2mod bindings;
3
4mod conversions;
5
6pub use bindings::*;
7
8use std::{
9    borrow::Cow,
10    marker::PhantomData,
11    os::raw::c_void,
12    ptr::{self},
13    slice,
14    str::{self},
15};
16
17use crate::{Result, RoomUUID, SodotError};
18
19/// A `repr(transparent)` wrapper around [bindings::ConstStr] adding a lifetime
20#[repr(transparent)]
21#[derive(Clone, Copy, Debug)]
22pub(super) struct ConstStr<'a> {
23    ffi: bindings::ConstStr,
24    _phantom: PhantomData<&'a str>,
25}
26
27/// Convertable to any `ConstSlice_*` ffi type (e.g. [ConstSlice_u32])
28pub struct ConstSlice<'a, T> {
29    ptr: *const T,
30    len: usize,
31    _phantom: PhantomData<&'a [T]>,
32}
33
34pub struct Room<'a> {
35    host_url: ConstStr<'a>,
36    uuid: ConstStr<'a>,
37}
38
39type CallbackFnSingle = Option<unsafe extern "C" fn(*mut c_void, ResultFFI, bindings::ConstStr)>;
40type CallbackFnDouble = Option<unsafe extern "C" fn(*mut c_void, ResultFFI, bindings::ConstStr, bindings::ConstStr)>;
41
42type SingleSender = futures_channel::oneshot::Sender<Result<String>>;
43type DoubleSender = futures_channel::oneshot::Sender<Result<(String, String)>>;
44
45/// Callback data for FFI calls, it's values should not outlive the `'a` lifetime.
46///
47pub(super) struct CallbackData<'a, C: Copy> {
48    callback: C,
49    extra: *mut c_void,
50    _phantom: PhantomData<&'a ()>,
51}
52
53impl<C: Copy> CallbackData<'_, C> {
54    pub(super) fn callback(&self) -> C {
55        self.callback
56    }
57    /// SAFETY: The pointer should not outlive the lifetime of the `CallbackData` struct.
58    pub(super) unsafe fn extra(&self) -> *mut c_void {
59        self.extra
60    }
61}
62
63/// Trait for types that can provide a raw FFI representation.
64///
65/// This trait is used to convert Rust types into their corresponding C FFI types
66/// for passing across the FFI boundary.
67///
68/// # Safety
69///
70/// Users must ensure that the returned FFI value is never used past the duration of the original value's lifetime
71pub(crate) trait AsRawFFI {
72    /// The corresponding C FFI type.
73    type FFI;
74
75    /// Converts this value to its raw FFI representation.
76    ///
77    /// # Safety
78    ///
79    /// Users must ensure that the returned FFI value is never used past the duration of the original value's lifetime
80    unsafe fn raw_ffi(&self) -> Self::FFI;
81}
82
83/// Trait for types that can be parsed from FFI string data.
84///
85/// This trait enables conversion from C strings (received via FFI) back into
86/// Rust types. It's used for parsing return values from C functions
87pub(crate) trait FromFFI: Sized {
88    /// Parses this type from an FFI string representation.
89    ///
90    /// The input can be either a borrowed string slice or an owned string,
91    /// provided via the `Cow` type for flexibility.
92    ///
93    /// # Panics
94    ///
95    /// Implementations may panic if the input string is malformed, indicating a FFI implementation bug
96    fn parse_ffi<'a>(s: impl Into<Cow<'a, str>>) -> Self;
97}
98
99/// Trait for types that can be converted to FFI string representation.
100///
101/// This trait enables conversion of Rust types into string representations
102/// suitable for passing to C functions via FFI. The string type is parameterized
103/// to allow for both borrowed and owned string representations depending on
104/// the source data.
105///
106pub(crate) trait ToFFI {
107    /// The string type returned by the FFI conversion.
108    ///
109    /// This can be `&str` for types that already contain string data,
110    /// or `String` for types that need to generate string representations
111    /// (like hex encoding of byte arrays).
112    type Str<'a>: AsRawFFI + 'a
113    where
114        Self: 'a;
115
116    /// Converts this value to its FFI string representation.
117    ///
118    /// The returned string type implements `AsRawFFI` and can be safely
119    /// passed across the FFI boundary.
120    fn ffi<'a>(&'a self) -> Self::Str<'a>;
121}
122
123/// This should be used for any async executor function accepting a callback of type:
124/// `fn(*mut c_void, ResultFFI, ConstStr)`
125/// you can access the `cb` and `extra` pointers via the `CallbackData` accepted
126/// Note that they *should not* escape the `'a` lifetime of the `CallbackData<'a>`.
127pub(crate) async fn async_ffi_single<T, F>(f: F) -> Result<T, SodotError>
128where
129    T: FromFFI,
130    for<'a> F: FnOnce(CallbackData<'a, CallbackFnSingle>) -> ResultFFI,
131{
132    let (tx, rx) = futures_channel::oneshot::channel();
133    let sender: *mut SingleSender = Box::into_raw(Box::new(tx));
134    let callback_data =
135        CallbackData::<'_, CallbackFnSingle> { callback: Some(sodot_single_cb), extra: sender.cast(), _phantom: PhantomData };
136    if let Err(e) = Result::<(), SodotError>::from(f(callback_data)) {
137        // The callback will not be called, so we need to free the sender.
138        let _ = unsafe { Box::from_raw(sender) };
139        return Err(e);
140    }
141    let v = rx.await.map_err(|e| SodotError::internal(format!("Failed to receive FFI result: {e}")))??;
142    Ok(T::parse_ffi(v))
143}
144
145/// The same as `async_ffi_single`, but for two output values.
146/// Should be used for any async executor accepting a callback of type:
147/// `fn(*mut c_void, ResultFFI, ConstStr, ConstStr)`
148pub(crate) async fn async_ffi_double<T1, T2, F>(f: F) -> Result<(T1, T2), SodotError>
149where
150    T1: FromFFI,
151    T2: FromFFI,
152    for<'a> F: FnOnce(CallbackData<'a, CallbackFnDouble>) -> ResultFFI,
153{
154    let (tx, rx) = futures_channel::oneshot::channel();
155    let sender: *mut DoubleSender = Box::into_raw(Box::new(tx));
156    let callback_data =
157        CallbackData::<'_, CallbackFnDouble> { callback: Some(sodot_double_cb), extra: sender.cast(), _phantom: PhantomData };
158    if let Err(e) = Result::<(), SodotError>::from(f(callback_data)) {
159        // The callback will not be called, so we need to free the sender.
160        let _ = unsafe { Box::from_raw(sender) };
161        return Err(e);
162    }
163    let (v1, v2) = rx.await.map_err(|e| SodotError::internal(format!("Failed to receive FFI result: {e}")))??;
164    Ok((T1::parse_ffi(v1), T2::parse_ffi(v2)))
165}
166
167/// A native function called outside our runtime by the FFI layer, for async results.
168/// Used with `async_ffi_single`
169unsafe extern "C" fn sodot_single_cb(extra: *mut c_void, result: ResultFFI, str: bindings::ConstStr) {
170    let str = ConstStr::from_inner(str);
171    let tx = unsafe { Box::from_raw(extra.cast::<SingleSender>()) };
172    let _ = tx.send(Result::from(result).map(|()| str.as_str().to_string()));
173}
174
175/// Same as `sodot_single_cb` but used with `async_ffi_double` for outputing 2 strings.
176unsafe extern "C" fn sodot_double_cb(extra: *mut c_void, result: ResultFFI, str1: bindings::ConstStr, str2: bindings::ConstStr) {
177    let str1 = ConstStr::from_inner(str1);
178    let str2 = ConstStr::from_inner(str2);
179    let tx = unsafe { Box::from_raw(extra.cast::<DoubleSender>()) };
180    let _ = tx.send(Result::from(result).map(|()| (str1.as_str().to_string(), str2.as_str().to_string())));
181}
182
183impl HeapString {
184    /// Empty HeapString
185    pub const NULL: Self = Self { ptr: ptr::null_mut(), len: 0 };
186
187    pub(super) fn as_str(&self) -> &str {
188        str::from_utf8(self.as_slice()).expect("[SODOT] Invalid UTF-8 in HeapString from FFI")
189    }
190
191    pub(super) fn ffi_mut(&mut self) -> &mut Self {
192        self
193    }
194
195    pub(super) fn as_slice(&self) -> &[u8] {
196        if self.ptr.is_null() {
197            debug_assert_eq!(self.len, 0, "[SODOT] Invalid HeapString");
198            &[]
199        } else {
200            unsafe { slice::from_raw_parts(self.ptr, self.len) }
201        }
202    }
203}
204
205impl Drop for HeapString {
206    fn drop(&mut self) {
207        if !self.ptr.is_null() {
208            unsafe {
209                bindings::sodot_dealloc_heap_string(bindings::HeapString { ptr: self.ptr, len: self.len });
210            }
211        }
212    }
213}
214
215impl AsRef<str> for HeapString {
216    fn as_ref(&self) -> &str {
217        self.as_str()
218    }
219}
220
221impl<'a> ConstStr<'a> {
222    pub(super) const NULL: Self = Self { ffi: bindings::ConstStr { ptr: ptr::null(), len: 0 }, _phantom: PhantomData };
223
224    fn from_inner(ffi: bindings::ConstStr) -> Self {
225        Self { ffi, _phantom: PhantomData }
226    }
227
228    fn as_slice(&self) -> &'a [u8] {
229        if self.ffi.ptr.is_null() {
230            debug_assert_eq!(self.ffi.len, 0, "[SODOT] Invalid ConstStr");
231            &[]
232        } else {
233            unsafe { slice::from_raw_parts(self.ffi.ptr, self.ffi.len) }
234        }
235    }
236
237    pub fn as_str(&self) -> &'a str {
238        str::from_utf8(self.as_slice()).expect("[SODOT] invalid utf8 in ConstStr")
239    }
240
241    pub(super) fn from_str(s: &'a str) -> Self {
242        let bytes = s.as_bytes();
243        if bytes.is_empty() {
244            Self::NULL
245        } else {
246            Self { ffi: bindings::ConstStr { ptr: bytes.as_ptr(), len: bytes.len() }, _phantom: PhantomData }
247        }
248    }
249}
250
251impl<'a, T> ConstSlice<'a, T> {
252    pub fn new(slice: &'a [T]) -> Self {
253        if slice.is_empty() {
254            Self { ptr: ptr::null(), len: 0, _phantom: PhantomData }
255        } else {
256            Self { ptr: slice.as_ptr(), len: slice.len(), _phantom: PhantomData }
257        }
258    }
259}
260
261impl<'a, T> From<&'a [T]> for ConstSlice<'a, T> {
262    fn from(slice: &'a [T]) -> Self {
263        Self { ptr: slice.as_ptr(), len: slice.len(), _phantom: PhantomData }
264    }
265}
266
267impl<'a> Room<'a> {
268    pub fn new(host_url: &'a str, uuid: &'a RoomUUID) -> Self {
269        Self { host_url: ConstStr::from_str(host_url), uuid: ConstStr::from_str(uuid.as_str()) }
270    }
271}
272
273impl ResultFFI {
274    pub(super) fn ok_and<T>(self, f: impl FnOnce() -> T) -> Result<T> {
275        Result::from(self).map(|()| f())
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use crate::error::Repr;
282
283    use super::*;
284    use std::{
285        cell::RefCell,
286        pin::pin,
287        task::{Context, Poll, Waker},
288    };
289
290    const OK: ResultFFI = ResultFFI { error_code: 0, err_str: HeapString::NULL };
291
292    const INVALID_UUID_ERR: ResultFFI = ResultFFI { err_str: HeapString::NULL, error_code: -1 };
293
294    impl FromFFI for String {
295        fn parse_ffi<'a>(s: impl Into<Cow<'a, str>>) -> Self {
296            s.into().into_owned()
297        }
298    }
299
300    #[test]
301    fn test_async_ffi_single() {
302        let external_cb = RefCell::new(None);
303        let fut = async_ffi_single::<String, _>(|cb_data| {
304            external_cb.replace(Some((cb_data.callback(), unsafe { cb_data.extra() })));
305            OK
306        });
307        let mut fut = pin!(fut);
308        let mut ctx = Context::from_waker(Waker::noop());
309
310        let polled = fut.as_mut().poll(&mut ctx);
311        assert!(polled.is_pending(), "Expected future to be pending");
312
313        let (cb, extra) = external_cb.take().unwrap();
314        let val = "hello world";
315        // Call the callback with the extra data
316        unsafe { cb.unwrap()(extra, OK, ConstStr::from_str(val).raw_ffi()) };
317        // Now it should finish
318        let polled = fut.as_mut().poll(&mut ctx);
319        assert!(matches!(&polled, Poll::Ready(Ok(s)) if s == "hello world"), "{polled:?}");
320    }
321
322    #[test]
323    fn test_async_ffi_double() {
324        let external_cb = RefCell::new(None);
325        let fut = async_ffi_double::<String, String, _>(|cb_data| {
326            external_cb.replace(Some((cb_data.callback(), unsafe { cb_data.extra() })));
327            OK
328        });
329
330        let mut fut = pin!(fut);
331        let mut ctx = Context::from_waker(Waker::noop());
332
333        let polled = fut.as_mut().poll(&mut ctx);
334        assert!(polled.is_pending(), "Expected future to be pending");
335
336        let (cb, extra) = external_cb.take().unwrap();
337        // Call the callback with the extra data
338        let (v1, v2) = ("hello", "world");
339        unsafe { cb.unwrap()(extra, OK, ConstStr::from_str(v1).raw_ffi(), ConstStr::from_str(v2).raw_ffi()) };
340        // Now it should finish
341        let polled = fut.as_mut().poll(&mut ctx);
342        assert!(matches!(&polled, Poll::Ready(Ok((s1, s2))) if s1 == v1 && s2 == v2), "{polled:?}");
343    }
344
345    #[test]
346    fn test_async_ffi_single_error_first() {
347        let external_cb = RefCell::new(None);
348        let fut = async_ffi_single::<String, _>(|cb_data| {
349            external_cb.replace(Some((cb_data.callback(), unsafe { cb_data.extra() })));
350            INVALID_UUID_ERR
351        });
352        let mut fut = pin!(fut);
353        let mut ctx = Context::from_waker(Waker::noop());
354
355        let polled = fut.as_mut().poll(&mut ctx);
356        assert!(matches!(&polled, Poll::Ready(Err(e)) if matches!(e.repr(), Repr::InvalidUuid(_))), "{polled:?}");
357    }
358
359    #[test]
360    fn test_async_ffi_double_error_first() {
361        let external_cb = RefCell::new(None);
362        let fut = async_ffi_double::<String, String, _>(|cb_data| {
363            external_cb.replace(Some((cb_data.callback(), unsafe { cb_data.extra() })));
364            INVALID_UUID_ERR
365        });
366
367        let mut fut = pin!(fut);
368        let mut ctx = Context::from_waker(Waker::noop());
369
370        let polled = fut.as_mut().poll(&mut ctx);
371        assert!(matches!(&polled, Poll::Ready(Err(e)) if matches!(e.repr(), Repr::InvalidUuid(_))), "{polled:?}");
372    }
373
374    #[test]
375    fn test_async_ffi_single_bug() {
376        let external_cb = RefCell::new(None);
377        {
378            let fut = async_ffi_single::<String, _>(|cb_data| {
379                external_cb.replace(Some((cb_data.callback(), unsafe { cb_data.extra() })));
380                OK
381            });
382            let mut fut = pin!(fut);
383            let mut ctx = Context::from_waker(Waker::noop());
384
385            let polled = fut.as_mut().poll(&mut ctx);
386            assert!(polled.is_pending(), "Expected future to be pending");
387        }
388        // Future is now dropped.
389
390        let (cb, extra) = external_cb.take().unwrap();
391        // Call the callback with the extra data
392        unsafe { cb.unwrap()(extra, OK, ConstStr::NULL.raw_ffi()) };
393    }
394
395    #[test]
396    fn test_async_ffi_double_bug() {
397        let external_cb = RefCell::new(None);
398        {
399            let fut = async_ffi_double::<String, String, _>(|cb_data| {
400                external_cb.replace(Some((cb_data.callback(), unsafe { cb_data.extra() })));
401                OK
402            });
403
404            let mut fut = pin!(fut);
405            let mut ctx = Context::from_waker(Waker::noop());
406
407            let polled = fut.as_mut().poll(&mut ctx);
408            assert!(polled.is_pending(), "Expected future to be pending");
409        }
410        // The future is now dropped.
411        let (cb, extra) = external_cb.take().unwrap();
412        // Call the callback with the extra data
413        unsafe { cb.unwrap()(extra, OK, ConstStr::NULL.raw_ffi(), ConstStr::NULL.raw_ffi()) };
414    }
415}