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#[repr(transparent)]
21#[derive(Clone, Copy, Debug)]
22pub(super) struct ConstStr<'a> {
23 ffi: bindings::ConstStr,
24 _phantom: PhantomData<&'a str>,
25}
26
27pub 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
45pub(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 pub(super) unsafe fn extra(&self) -> *mut c_void {
59 self.extra
60 }
61}
62
63pub(crate) trait AsRawFFI {
72 type FFI;
74
75 unsafe fn raw_ffi(&self) -> Self::FFI;
81}
82
83pub(crate) trait FromFFI: Sized {
88 fn parse_ffi<'a>(s: impl Into<Cow<'a, str>>) -> Self;
97}
98
99pub(crate) trait ToFFI {
107 type Str<'a>: AsRawFFI + 'a
113 where
114 Self: 'a;
115
116 fn ffi<'a>(&'a self) -> Self::Str<'a>;
121}
122
123pub(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 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
145pub(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 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
167unsafe 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
175unsafe 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 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 unsafe { cb.unwrap()(extra, OK, ConstStr::from_str(val).raw_ffi()) };
317 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 let (v1, v2) = ("hello", "world");
339 unsafe { cb.unwrap()(extra, OK, ConstStr::from_str(v1).raw_ffi(), ConstStr::from_str(v2).raw_ffi()) };
340 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 let (cb, extra) = external_cb.take().unwrap();
391 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 let (cb, extra) = external_cb.take().unwrap();
412 unsafe { cb.unwrap()(extra, OK, ConstStr::NULL.raw_ffi(), ConstStr::NULL.raw_ffi()) };
414 }
415}