mod error;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use std::panic::{AssertUnwindSafe, catch_unwind};
use std::path::Path;
use std::ptr;
use crate::catalog::TetFile;
use crate::query::{ExecuteQueryOptions, execute_query_json};
use crate::verify::{VerifyOptions, verify_tet_bytes};
pub use error::clear_last_error;
pub const TET_ABI_VERSION: u32 = 1;
#[repr(C)]
pub struct TetHandle {
_private: [u8; 0],
}
fn handle_mut<'a>(ptr: *mut TetHandle) -> Option<&'a mut TetFile> {
if ptr.is_null() {
error::set_last_error("handle: null pointer");
return None;
}
Some(unsafe { &mut *ptr.cast::<TetFile>() })
}
fn ffi_guard<F, T>(f: F) -> Option<T>
where
F: FnOnce() -> Option<T>,
{
match catch_unwind(AssertUnwindSafe(f)) {
Ok(v) => v,
Err(_) => {
error::set_last_error("internal panic (aborting cdylib build is recommended for FFI)");
None
}
}
}
fn cstr_input<'a>(ptr: *const c_char, field: &'static str) -> Option<&'a CStr> {
if ptr.is_null() {
error::set_last_error(format!("{field}: null pointer"));
return None;
}
let s = unsafe { CStr::from_ptr(ptr) };
if s.to_bytes().is_empty() {
error::set_last_error(format!("{field}: empty path"));
return None;
}
Some(s)
}
fn return_json(s: String) -> *mut c_char {
match CString::new(s) {
Ok(c) => c.into_raw(),
Err(_) => {
error::set_last_error("internal: JSON contained interior NUL");
ptr::null_mut()
}
}
}
fn json_output<E: std::fmt::Display>(r: Result<String, E>) -> *mut c_char {
match r {
Ok(s) => return_json(s),
Err(e) => {
error::set_last_error(e.to_string());
ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_abi_version() -> u32 {
TET_ABI_VERSION
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_open(path: *const c_char) -> *mut TetHandle {
ffi_guard(|| {
let cstr = cstr_input(path, "path")?;
let path_str = match cstr.to_str() {
Ok(p) => p,
Err(_) => {
error::set_last_error("path: invalid UTF-8");
return None;
}
};
match TetFile::open(path_str) {
Ok(file) => Some(Box::into_raw(Box::new(file)).cast::<TetHandle>()),
Err(e) => {
error::set_last_error(e.to_string());
None
}
}
})
.unwrap_or(ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_close(handle: *mut TetHandle) {
if handle.is_null() {
return;
}
drop(unsafe { Box::from_raw(handle.cast::<TetFile>()) });
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_last_error() -> *const c_char {
error::last_error_cstr()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_clear_error() {
clear_last_error();
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_summary_json(handle: *mut TetHandle) -> *mut c_char {
ffi_guard(|| {
let file = handle_mut(handle)?;
Some(json_output(
file.summary()
.map_err(|e| e.to_string())
.and_then(|s| serde_json::to_string(&s).map_err(|e| e.to_string())),
))
})
.unwrap_or(ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_query_json(
handle: *mut TetHandle,
query_json: *const c_char,
) -> *mut c_char {
ffi_guard(|| {
let file = handle_mut(handle)?;
let q = cstr_input(query_json, "query_json")?;
let query = match q.to_str() {
Ok(s) => s,
Err(_) => {
error::set_last_error("query_json: invalid UTF-8");
return None;
}
};
let path = file.path();
let mmap = file.mmap();
let resp = match execute_query_json(
query,
path,
mmap,
ExecuteQueryOptions::execute_no_preview(),
None,
) {
Ok(r) => r,
Err(e) => {
error::set_last_error(e.to_string());
return None;
}
};
Some(json_output(
serde_json::to_string(&resp).map_err(|e| e.to_string()),
))
})
.unwrap_or(ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_verify_json(path: *const c_char) -> *mut c_char {
ffi_guard(|| {
let cstr = cstr_input(path, "path")?;
let path_str = match cstr.to_str() {
Ok(p) => p,
Err(_) => {
error::set_last_error("path: invalid UTF-8");
return None;
}
};
let data = match std::fs::read(path_str) {
Ok(d) => d,
Err(e) => {
error::set_last_error(e.to_string());
return None;
}
};
let report = verify_tet_bytes(&data, Some(Path::new(path_str)), VerifyOptions::default());
Some(json_output(
serde_json::to_string(&report).map_err(|e| e.to_string()),
))
})
.unwrap_or(ptr::null_mut())
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn tet_string_free(s: *mut c_char) {
if s.is_null() {
return;
}
drop(unsafe { CString::from_raw(s) });
}