use std::ffi::{CStr, CString};
use std::os::raw::c_int;
use std::ptr;
pub const ENPROT_OK: c_int = 0;
pub const ENPROT_ERR_PARSE: c_int = 1;
pub const ENPROT_ERR_CRYPTO: c_int = 2;
pub const ENPROT_ERR_IO: c_int = 3;
pub const ENPROT_ERR_INVALID: c_int = 4;
#[repr(C)]
pub struct EnprotResult {
pub code: c_int,
pub error: *const std::os::raw::c_char,
}
impl EnprotResult {
fn ok() -> Self {
EnprotResult {
code: ENPROT_OK,
error: ptr::null(),
}
}
fn err(code: c_int, msg: &str) -> Self {
EnprotResult {
code,
error: CString::new(msg).unwrap_or_default().into_raw(),
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enprot_process(config_json: *const std::os::raw::c_char) -> EnprotResult {
if config_json.is_null() {
return EnprotResult::err(ENPROT_ERR_INVALID, "config_json is null");
}
let config_str = match unsafe { CStr::from_ptr(config_json) }.to_str() {
Ok(s) => s,
Err(_) => return EnprotResult::err(ENPROT_ERR_INVALID, "invalid UTF-8 in config_json"),
};
let config: serde_json::Value = match serde_json::from_str(config_str) {
Ok(v) => v,
Err(e) => return EnprotResult::err(ENPROT_ERR_PARSE, &format!("invalid JSON: {e}")),
};
if config["file"].as_str().is_none() {
return EnprotResult::err(ENPROT_ERR_INVALID, "missing 'file' in config");
}
if config["operation"].as_str().is_none() {
return EnprotResult::err(ENPROT_ERR_INVALID, "missing 'operation' in config");
}
EnprotResult::ok()
}
#[unsafe(no_mangle)]
pub extern "C" fn enprot_version() -> *const std::os::raw::c_char {
static VERSION: &[u8] = concat!(env!("CARGO_PKG_VERSION"), "\0").as_bytes();
VERSION.as_ptr() as *const std::os::raw::c_char
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn enprot_free_error(p: *mut std::os::raw::c_char) {
if !p.is_null() {
unsafe { drop(CString::from_raw(p)) };
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CStr;
#[test]
fn version_returns_nonempty() {
let v = unsafe { CStr::from_ptr(enprot_version()) }
.to_str()
.unwrap();
assert!(!v.is_empty());
}
#[test]
fn null_config_returns_error() {
let r = unsafe { enprot_process(ptr::null()) };
assert_eq!(r.code, ENPROT_ERR_INVALID);
}
#[test]
fn valid_json_missing_fields_returns_error() {
let json = CString::new(r#"{"foo": "bar"}"#).unwrap();
let r = unsafe { enprot_process(json.as_ptr()) };
assert_eq!(r.code, ENPROT_ERR_INVALID);
}
}