// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use alloc::boxed::Box; use alloc::ffi::CString; use alloc::format; use alloc::string::{String, ToString}; use alloc::vec::Vec; use anyhow::{anyhow, bail, Result}; use core::ffi::{c_char, c_longlong, c_void, CStr}; use core::{mem, ptr}; /// Status of a call on `RegorusEngine`. #[derive(Debug, PartialEq)] #[repr(C)] pub enum RegorusStatus { /// The operation was successful. Ok, /// The operation was unsuccessful. Error, /// Invalid data format provided. InvalidDataFormat, /// Invalid entrypoint rule specified. InvalidEntrypoint, /// Compilation failed. CompilationFailed, /// Invalid argument provided. InvalidArgument, /// Invalid module ID. InvalidModuleId, /// Invalid policy content. InvalidPolicy, /// The engine panicked and cannot be reused until reset. Panic, /// The engine remains poisoned because a previous panic was detected. Poisoned, } /// Type of data contained in RegorusResult #[repr(C)] #[allow(unused)] pub enum RegorusDataType { /// No data / void None, /// String data (output field is valid) String, /// Boolean data (bool_value field is valid) Boolean, /// Integer data (int_value field is valid) Integer, /// Pointer data (pointer_value field is valid) Pointer, } /// Result of a call on `RegorusEngine`. /// /// Must be freed using `regorus_result_drop`. #[repr(C)] pub struct RegorusResult { /// Status pub(crate) status: RegorusStatus, /// Type of data contained in this result pub(crate) data_type: RegorusDataType, /// String output produced by the call. /// Valid when data_type is String. Owned by Rust. pub(crate) output: *mut c_char, /// Boolean value. /// Valid when data_type is Boolean. pub(crate) bool_value: bool, /// Integer value. /// Valid when data_type is Integer. pub(crate) int_value: c_longlong, /// Pointer value. /// Valid when data_type is Pointer. pub(crate) pointer_value: *mut c_void, /// Errors produced by the call. /// Owned by Rust. pub(crate) error_message: *mut c_char, } /// Byte buffer returned from FFI for binary payloads. /// /// Must be freed using `regorus_buffer_drop`. #[repr(C)] pub struct RegorusBuffer { /// Pointer to byte buffer data. pub data: *mut u8, /// Number of bytes stored in `data`. pub len: usize, /// Capacity of the allocation backing `data`. pub capacity: usize, } impl RegorusResult { /// Create a successful result with no data. pub(crate) fn ok_void() -> Self { Self { status: RegorusStatus::Ok, data_type: RegorusDataType::None, output: ptr::null_mut(), bool_value: false, int_value: 0, pointer_value: ptr::null_mut(), error_message: ptr::null_mut(), } } /// Create a successful result with string output. pub(crate) fn ok_string(output: String) -> Self { Self { status: RegorusStatus::Ok, data_type: RegorusDataType::String, output: to_c_str(output), bool_value: false, int_value: 0, pointer_value: ptr::null_mut(), error_message: ptr::null_mut(), } } /// Create a successful result with boolean value. #[allow(unused)] pub(crate) fn ok_bool(value: bool) -> Self { Self { status: RegorusStatus::Ok, data_type: RegorusDataType::Boolean, output: ptr::null_mut(), bool_value: value, int_value: 0, pointer_value: ptr::null_mut(), error_message: ptr::null_mut(), } } /// Create a successful result with integer value. #[allow(unused)] pub(crate) fn ok_int(value: i64) -> Self { Self { status: RegorusStatus::Ok, data_type: RegorusDataType::Integer, output: ptr::null_mut(), bool_value: false, int_value: value as c_longlong, pointer_value: ptr::null_mut(), error_message: ptr::null_mut(), } } /// Create a successful result with pointer value. pub(crate) fn ok_pointer(pointer: *mut c_void) -> Self { Self { status: RegorusStatus::Ok, data_type: RegorusDataType::Pointer, output: ptr::null_mut(), bool_value: false, int_value: 0, pointer_value: pointer, error_message: ptr::null_mut(), } } /// Create an error result with specific status. pub(crate) fn err(status: RegorusStatus) -> Self { Self { status, data_type: RegorusDataType::None, output: ptr::null_mut(), bool_value: false, int_value: 0, pointer_value: ptr::null_mut(), error_message: ptr::null_mut(), } } /// Create an error result with status and message. pub(crate) fn err_with_message(status: RegorusStatus, message: String) -> Self { Self { status, data_type: RegorusDataType::None, output: ptr::null_mut(), bool_value: false, int_value: 0, pointer_value: ptr::null_mut(), error_message: to_c_str(message), } } } impl RegorusBuffer { pub(crate) fn from_vec(mut data: Vec) -> *mut RegorusBuffer { let buffer = RegorusBuffer { data: data.as_mut_ptr(), len: data.len(), capacity: data.capacity(), }; mem::forget(data); Box::into_raw(Box::new(buffer)) } } pub(crate) fn to_c_str(s: String) -> *mut c_char { match CString::new(s) { Ok(cs) => cs.into_raw(), _ => to_c_str("binding error: failed to create c-style string".to_string()), } } pub(crate) fn from_c_str(s: *const c_char) -> Result { if s.is_null() { bail!("null pointer"); } unsafe { CStr::from_ptr(s) .to_str() .map_err(|e| anyhow!("invalid utf8: {e}")) .map(|s| s.to_string()) } } pub(crate) fn to_ref<'a, T>(t: *mut T) -> Result<&'a mut T> { unsafe { t.as_mut().ok_or_else(|| anyhow!("null pointer")) } } pub(crate) fn to_regorus_result(r: Result<()>) -> RegorusResult { match r { Ok(()) => RegorusResult::ok_void(), Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")), } } pub(crate) fn to_regorus_string_result(r: Result) -> RegorusResult { match r { Ok(s) => RegorusResult::ok_string(s), Err(e) => RegorusResult::err_with_message(RegorusStatus::Error, format!("{e}")), } } /// Drop a `RegorusBuffer`. /// /// `data` is not valid after drop. #[no_mangle] pub extern "C" fn regorus_buffer_drop(buffer: *mut RegorusBuffer) { if let Ok(buffer) = to_ref(buffer) { unsafe { if !buffer.data.is_null() { let _ = Vec::from_raw_parts(buffer.data, buffer.len, buffer.capacity); } let _ = Box::from_raw(ptr::from_mut(buffer)); } } } /// Drop a `RegorusResult`. /// /// `output` and `error_message` strings are not valid after drop. #[no_mangle] pub extern "C" fn regorus_result_drop(r: RegorusResult) { unsafe { if !r.error_message.is_null() { let _ = CString::from_raw(r.error_message); } if !r.output.is_null() { let _ = CString::from_raw(r.output); } } }