#![deny(unsafe_op_in_unsafe_fn)]
use arrow_array::UInt8Array;
use dora_node_api::{DoraNode, Event, EventStream, arrow::array::AsArray};
use eyre::Context;
use std::{
ffi::{c_int, c_void},
ptr, slice,
};
pub const HEADER_NODE_API: &str = include_str!("../node_api.h");
struct DoraContext {
node: &'static mut DoraNode,
events: EventStream,
}
#[unsafe(no_mangle)]
pub extern "C" fn init_dora_context_from_env() -> *mut c_void {
let context = || {
let (node, events) = DoraNode::init_from_env()?;
let node = Box::leak(Box::new(node));
Result::<_, eyre::Report>::Ok(DoraContext { node, events })
};
let context = match context().context("failed to initialize node") {
Ok(n) => n,
Err(err) => {
let err: eyre::Error = err;
tracing::error!("{err:?}");
return ptr::null_mut();
}
};
Box::into_raw(Box::new(context)).cast()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn free_dora_context(context: *mut c_void) {
let context: Box<DoraContext> = unsafe { Box::from_raw(context.cast()) };
let DoraContext { node, .. } = *context;
let _ = unsafe { Box::from_raw(node as *const DoraNode as *mut DoraNode) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dora_next_event(context: *mut c_void) -> *mut c_void {
let context: &mut DoraContext = unsafe { &mut *context.cast() };
match context.events.recv() {
Some(event) => Box::into_raw(Box::new(event)).cast(),
None => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn read_dora_event_type(event: *const ()) -> EventType {
let event: &Event = unsafe { &*event.cast() };
match event {
Event::Stop(_) => EventType::Stop,
Event::Input { .. } => EventType::Input,
Event::InputClosed { .. } => EventType::InputClosed,
Event::Error(_) => EventType::Error,
_ => EventType::Unknown,
}
}
#[repr(C)]
pub enum EventType {
Stop,
Input,
InputClosed,
Error,
Unknown,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn read_dora_input_id(
event: *const (),
out_ptr: *mut *const u8,
out_len: *mut usize,
) {
let event: &Event = unsafe { &*event.cast() };
match event {
Event::Input { id, .. } => {
let id = id.as_str().as_bytes();
let ptr = id.as_ptr();
let len = id.len();
unsafe {
*out_ptr = ptr;
*out_len = len;
}
}
_ => unsafe {
*out_ptr = ptr::null();
*out_len = 0;
},
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn read_dora_input_data(
event: *const (),
out_ptr: *mut *const u8,
out_len: *mut usize,
) {
let event: &Event = unsafe { &*event.cast() };
match event {
Event::Input { data, .. } => match data.data_type() {
dora_node_api::arrow::datatypes::DataType::UInt8 => {
let array: &UInt8Array = data.as_primitive();
let ptr = array.values().as_ptr();
let len = array.values().len();
unsafe {
*out_ptr = ptr;
*out_len = len;
}
}
dora_node_api::arrow::datatypes::DataType::Null => unsafe {
*out_ptr = ptr::null();
*out_len = 0;
},
other => {
tracing::error!(
"read_dora_input_data: unsupported input arrow type {other:?}; \
only UInt8 is supported by the raw-byte C API"
);
unsafe {
*out_ptr = ptr::null();
*out_len = 0;
}
}
},
_ => unsafe {
*out_ptr = ptr::null();
*out_len = 0;
},
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn read_dora_input_timestamp(event: *const ()) -> core::ffi::c_ulonglong {
let event: &Event = unsafe { &*event.cast() };
match event {
Event::Input { metadata, .. } => metadata.timestamp().get_time().as_u64(),
_ => 0,
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn free_dora_event(event: *mut c_void) {
let _: Box<Event> = unsafe { Box::from_raw(event.cast()) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dora_send_output(
context: *mut c_void,
id_ptr: *const u8,
id_len: usize,
data_ptr: *const u8,
data_len: usize,
) -> c_int {
match unsafe { try_send_output(context, id_ptr, id_len, data_ptr, data_len) } {
Ok(()) => 0,
Err(err) => {
tracing::error!("{err:?}");
-1
}
}
}
unsafe fn try_send_output(
context: *mut c_void,
id_ptr: *const u8,
id_len: usize,
data_ptr: *const u8,
data_len: usize,
) -> eyre::Result<()> {
if context.is_null() || id_ptr.is_null() || data_ptr.is_null() {
eyre::bail!("null pointer passed to dora_send_output");
}
let context: &mut DoraContext = unsafe { &mut *context.cast() };
let id = std::str::from_utf8(unsafe { slice::from_raw_parts(id_ptr, id_len) })?;
let output_id = id.to_owned().into();
let data = unsafe { slice::from_raw_parts(data_ptr, data_len) };
Ok(context
.node
.send_output_raw(output_id, Default::default(), data.len(), |out| {
out.copy_from_slice(data);
})?)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn dora_log(
context: *mut c_void,
level_ptr: *const u8,
level_len: usize,
msg_ptr: *const u8,
msg_len: usize,
) -> c_int {
match unsafe { try_log(context, level_ptr, level_len, msg_ptr, msg_len) } {
Ok(()) => 0,
Err(err) => {
tracing::error!("{err:?}");
-1
}
}
}
unsafe fn try_log(
context: *mut c_void,
level_ptr: *const u8,
level_len: usize,
msg_ptr: *const u8,
msg_len: usize,
) -> eyre::Result<()> {
if context.is_null() || level_ptr.is_null() || msg_ptr.is_null() {
eyre::bail!("null pointer passed to dora_log");
}
let context: &mut DoraContext = unsafe { &mut *context.cast() };
let level = std::str::from_utf8(unsafe { slice::from_raw_parts(level_ptr, level_len) })?;
let message = std::str::from_utf8(unsafe { slice::from_raw_parts(msg_ptr, msg_len) })?;
context.node.log(level, message, None);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use dora_node_api::{
ArrowData, Metadata,
arrow::array::{ArrayRef, Int32Array},
uhlc::HLC,
};
use std::sync::Arc;
#[test]
fn read_dora_input_data_non_uint8_returns_null() {
let array: ArrayRef = Arc::new(Int32Array::from(vec![1, 2, 3]));
let event = Event::Input {
id: "my_input".into(),
metadata: Metadata::new(HLC::default().new_timestamp()),
data: ArrowData(array),
};
let event_ptr: *const () = (&event as *const Event).cast();
let sentinel: u8 = 0;
let mut out_ptr: *const u8 = &sentinel;
let mut out_len: usize = 42;
unsafe {
read_dora_input_data(event_ptr, &mut out_ptr, &mut out_len);
}
assert!(
out_ptr.is_null(),
"expected null out_ptr for non-UInt8 input"
);
assert_eq!(out_len, 0, "expected zero out_len for non-UInt8 input");
}
}