use crate::ipc::IpcMessage;
use std::collections::VecDeque;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::{ptr, slice};
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LazilyFfiStatus {
Ok = 0,
Empty = 1,
NullPointer = 2,
InvalidMessage = 3,
EncodeFailed = 4,
Panic = 5,
}
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum LazilyFfiMessageKind {
#[default]
Unknown = 0,
Snapshot = 1,
Delta = 2,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LazilyFfiBytes {
pub ptr: *mut u8,
pub len: usize,
}
impl LazilyFfiBytes {
pub const fn empty() -> Self {
Self {
ptr: ptr::null_mut(),
len: 0,
}
}
fn from_vec(bytes: Vec<u8>) -> Self {
if bytes.is_empty() {
return Self::empty();
}
let mut bytes = bytes.into_boxed_slice();
let ptr = bytes.as_mut_ptr();
let len = bytes.len();
std::mem::forget(bytes);
Self { ptr, len }
}
}
impl Default for LazilyFfiBytes {
fn default() -> Self {
Self::empty()
}
}
#[derive(Debug, Default)]
pub struct LazilyFfiChannel {
queue: VecDeque<Vec<u8>>,
}
#[unsafe(no_mangle)]
pub extern "C" fn lazily_ffi_channel_new() -> *mut LazilyFfiChannel {
Box::into_raw(Box::<LazilyFfiChannel>::default())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_channel_free(channel: *mut LazilyFfiChannel) {
if channel.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| {
unsafe { drop(Box::from_raw(channel)) };
}));
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_channel_send_json(
channel: *mut LazilyFfiChannel,
ptr: *const u8,
len: usize,
) -> LazilyFfiStatus {
ffi_guard(|| {
let channel = unsafe { channel_mut(channel) }?;
let bytes = unsafe { bytes_from_raw(ptr, len) }?;
let message = decode_message_json(bytes)?;
let encoded = encode_message_json(&message)?;
channel.queue.push_back(encoded);
Ok(())
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_channel_recv_json(
channel: *mut LazilyFfiChannel,
out: *mut LazilyFfiBytes,
) -> LazilyFfiStatus {
ffi_guard(|| {
let out = unsafe { out_bytes_mut(out) }?;
*out = LazilyFfiBytes::empty();
let channel = unsafe { channel_mut(channel) }?;
match channel.queue.pop_front() {
Some(bytes) => {
*out = LazilyFfiBytes::from_vec(bytes);
Ok(())
}
None => Err(LazilyFfiStatus::Empty),
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_channel_len(
channel: *const LazilyFfiChannel,
out_len: *mut usize,
) -> LazilyFfiStatus {
ffi_guard(|| {
let channel = unsafe { channel_ref(channel) }?;
let out_len = unsafe { out_len.as_mut() }.ok_or(LazilyFfiStatus::NullPointer)?;
*out_len = channel.queue.len();
Ok(())
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_ipc_message_validate_json(
ptr: *const u8,
len: usize,
) -> LazilyFfiStatus {
ffi_guard(|| {
let bytes = unsafe { bytes_from_raw(ptr, len) }?;
decode_message_json(bytes)?;
Ok(())
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_ipc_message_kind_json(
ptr: *const u8,
len: usize,
out_kind: *mut LazilyFfiMessageKind,
) -> LazilyFfiStatus {
ffi_guard(|| {
let out_kind = unsafe { out_kind.as_mut() }.ok_or(LazilyFfiStatus::NullPointer)?;
*out_kind = LazilyFfiMessageKind::Unknown;
let bytes = unsafe { bytes_from_raw(ptr, len) }?;
let message = decode_message_json(bytes)?;
*out_kind = match message {
IpcMessage::Snapshot(_) => LazilyFfiMessageKind::Snapshot,
IpcMessage::Delta(_) => LazilyFfiMessageKind::Delta,
};
Ok(())
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_ipc_message_clone_json(
ptr: *const u8,
len: usize,
out: *mut LazilyFfiBytes,
) -> LazilyFfiStatus {
ffi_guard(|| {
let out = unsafe { out_bytes_mut(out) }?;
*out = LazilyFfiBytes::empty();
let bytes = unsafe { bytes_from_raw(ptr, len) }?;
let message = decode_message_json(bytes)?;
*out = LazilyFfiBytes::from_vec(encode_message_json(&message)?);
Ok(())
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn lazily_ffi_bytes_free(bytes: LazilyFfiBytes) {
if bytes.ptr.is_null() {
return;
}
let _ = catch_unwind(AssertUnwindSafe(|| {
unsafe {
drop(Box::from_raw(ptr::slice_from_raw_parts_mut(
bytes.ptr, bytes.len,
)))
};
}));
}
fn ffi_guard(operation: impl FnOnce() -> Result<(), LazilyFfiStatus>) -> LazilyFfiStatus {
match catch_unwind(AssertUnwindSafe(operation)) {
Ok(Ok(())) => LazilyFfiStatus::Ok,
Ok(Err(status)) => status,
Err(_) => LazilyFfiStatus::Panic,
}
}
fn decode_message_json(bytes: &[u8]) -> Result<IpcMessage, LazilyFfiStatus> {
serde_json::from_slice(bytes).map_err(|_| LazilyFfiStatus::InvalidMessage)
}
fn encode_message_json(message: &IpcMessage) -> Result<Vec<u8>, LazilyFfiStatus> {
serde_json::to_vec(message).map_err(|_| LazilyFfiStatus::EncodeFailed)
}
unsafe fn channel_ref<'a>(
channel: *const LazilyFfiChannel,
) -> Result<&'a LazilyFfiChannel, LazilyFfiStatus> {
unsafe { channel.as_ref() }.ok_or(LazilyFfiStatus::NullPointer)
}
unsafe fn channel_mut<'a>(
channel: *mut LazilyFfiChannel,
) -> Result<&'a mut LazilyFfiChannel, LazilyFfiStatus> {
unsafe { channel.as_mut() }.ok_or(LazilyFfiStatus::NullPointer)
}
unsafe fn out_bytes_mut<'a>(
out: *mut LazilyFfiBytes,
) -> Result<&'a mut LazilyFfiBytes, LazilyFfiStatus> {
unsafe { out.as_mut() }.ok_or(LazilyFfiStatus::NullPointer)
}
unsafe fn bytes_from_raw<'a>(ptr: *const u8, len: usize) -> Result<&'a [u8], LazilyFfiStatus> {
if ptr.is_null() {
return if len == 0 {
Ok(&[])
} else {
Err(LazilyFfiStatus::NullPointer)
};
}
Ok(unsafe { slice::from_raw_parts(ptr, len) })
}