#![allow(unsafe_code)]
use std::ffi::{CStr, CString, c_char};
use std::ptr::NonNull;
use crate::error::{Error, Result};
pub struct OwnedHandle<T> {
ptr: NonNull<T>,
free: unsafe extern "C" fn(*mut T),
}
unsafe impl<T> Send for OwnedHandle<T> {}
impl<T> OwnedHandle<T> {
pub(crate) fn new(ptr: *mut T, free: unsafe extern "C" fn(*mut T)) -> Result<Self> {
NonNull::new(ptr)
.map(|ptr| Self { ptr, free })
.ok_or(Error::NullPointer)
}
#[inline]
pub(crate) const fn as_ptr(&self) -> *const T {
self.ptr.as_ptr().cast_const()
}
}
impl<T> Drop for OwnedHandle<T> {
fn drop(&mut self) {
unsafe { (self.free)(self.ptr.as_ptr()) };
}
}
impl<T> std::fmt::Debug for OwnedHandle<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OwnedHandle")
.field("ptr", &self.ptr)
.finish_non_exhaustive()
}
}
pub unsafe fn take_c_string(ptr: *mut c_char) -> Result<String> {
if ptr.is_null() {
return Err(Error::NullPointer);
}
let cstr = unsafe { CStr::from_ptr(ptr) };
let s = cstr
.to_str()
.map(String::from)
.map_err(|_| Error::InvalidUtf8);
unsafe { xmtp_sys::xmtp_free_string(ptr) };
s
}
pub fn to_c_string(s: &str) -> Result<CString> {
CString::new(s).map_err(|_| Error::InvalidArgument("string contains NUL".into()))
}
pub unsafe fn read_borrowed_strings(ptr: *const *mut c_char, count: i32) -> Vec<String> {
if ptr.is_null() || count <= 0 {
return vec![];
}
(0..count.unsigned_abs() as usize)
.filter_map(|i| {
let s = unsafe { *ptr.add(i) };
if s.is_null() {
return None;
}
unsafe { CStr::from_ptr(s) }.to_str().ok().map(String::from)
})
.collect()
}
pub fn to_c_string_array(strings: &[&str]) -> Result<(Vec<CString>, Vec<*const c_char>)> {
let owned: Vec<CString> = strings
.iter()
.map(|s| to_c_string(s))
.collect::<Result<_>>()?;
let ptrs = owned.iter().map(|c| c.as_ptr()).collect();
Ok((owned, ptrs))
}
pub fn identifiers_to_ffi(
ids: &[crate::types::AccountIdentifier],
) -> Result<(Vec<CString>, Vec<*const c_char>, Vec<i32>)> {
let owned: Vec<CString> = ids
.iter()
.map(|id| to_c_string(&id.address))
.collect::<Result<_>>()?;
let ptrs = owned.iter().map(|c| c.as_ptr()).collect();
let kinds = ids.iter().map(|id| id.kind as i32).collect();
Ok((owned, ptrs, kinds))
}
pub unsafe fn take_nullable_string(ptr: *mut c_char) -> Option<String> {
if ptr.is_null() {
None
} else {
unsafe { take_c_string(ptr) }.ok()
}
}
pub fn c_str_ptr(opt: Option<&CString>) -> *const c_char {
opt.map_or(std::ptr::null(), |c| c.as_ptr())
}
pub fn optional_c_string(s: Option<&str>) -> Result<Option<CString>> {
s.map(to_c_string).transpose()
}
pub struct FfiList<T> {
ptr: *mut T,
len: i32,
free: unsafe extern "C" fn(*mut T),
}
impl<T> FfiList<T> {
pub fn new(
ptr: *mut T,
len_fn: unsafe extern "C" fn(*const T) -> i32,
free: unsafe extern "C" fn(*mut T),
) -> Self {
if ptr.is_null() {
return Self { ptr, len: 0, free };
}
let len = unsafe { len_fn(ptr.cast_const()) }.max(0);
Self { ptr, len, free }
}
#[inline]
pub const fn len(&self) -> i32 {
self.len
}
#[inline]
pub const fn as_ptr(&self) -> *mut T {
self.ptr
}
}
impl<T> Drop for FfiList<T> {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe { (self.free)(self.ptr) };
}
}
}
#[inline]
pub fn ffi_usize(raw: i32) -> usize {
raw.max(0) as usize
}
pub fn to_ffi_len(len: usize) -> Result<i32> {
i32::try_from(len).map_err(|_| Error::InvalidArgument("length exceeds i32::MAX".into()))
}