use crate::ffi::*;
use crate::{AgentEvent, CheckResult, Severity};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
pub struct FfiEventData {
event: FfiEvent,
_strings: Vec<CString>,
}
impl FfiEventData {
pub fn as_ptr(&self) -> *const FfiEvent {
&self.event
}
}
impl AsRef<FfiEvent> for FfiEventData {
fn as_ref(&self) -> &FfiEvent {
&self.event
}
}
pub fn agent_event_to_ffi(event: &AgentEvent) -> FfiEventData {
let mut ffi = FfiEvent {
kind: 0,
pid: 0,
process: 0,
dll_name: std::ptr::null(),
dll_base: 0,
tid: 0,
thread_handle: 0,
dbg_string: std::ptr::null(),
target_path: std::ptr::null(),
target_bytes: std::ptr::null(),
target_bytes_len: 0,
};
let mut strings = Vec::new();
match event {
AgentEvent::ProcessCreated {
pid,
process,
target_bytes,
target_path,
} => {
ffi.kind = EVENT_PROCESS_CREATED;
ffi.pid = *pid;
ffi.process = *process as isize;
ffi.target_bytes = target_bytes.as_ptr();
ffi.target_bytes_len = target_bytes.len();
ffi.target_path = push_c_string(&mut strings, target_path) as *const u8;
}
AgentEvent::DllLoaded { name, base } => {
ffi.kind = EVENT_DLL_LOADED;
ffi.dll_base = *base;
ffi.dll_name = push_c_string(&mut strings, name);
}
AgentEvent::InitialBreakpoint => {
ffi.kind = EVENT_INITIAL_BREAKPOINT;
}
AgentEvent::ThreadCreated { tid, handle } => {
ffi.kind = EVENT_THREAD_CREATED;
ffi.tid = *tid;
ffi.thread_handle = *handle as isize;
}
AgentEvent::Finished => {
ffi.kind = EVENT_FINISHED;
}
AgentEvent::ThreadFinished { tid } => {
ffi.kind = EVENT_THREAD_FINISHED;
ffi.tid = *tid;
}
AgentEvent::DebugString { string } => {
ffi.kind = EVENT_DEBUG_STRING;
ffi.dbg_string = push_c_string(&mut strings, string) as *const u8;
}
AgentEvent::AgentInitialized { target_path } => {
ffi.kind = EVENT_AGENT_INITIALIZED;
ffi.target_path = push_c_string(&mut strings, target_path) as *const u8;
}
AgentEvent::AgentFinished => {
ffi.kind = EVENT_AGENT_FINISHED;
ffi.target_path = std::ptr::null();
}
_ => {}
}
FfiEventData {
event: ffi,
_strings: strings,
}
}
fn push_c_string(strings: &mut Vec<CString>, value: &str) -> *const c_char {
let c_string = into_c_string(value.to_string());
let ptr = c_string.as_ptr();
strings.push(c_string);
ptr
}
fn into_c_string(value: String) -> CString {
CString::new(value).unwrap_or_else(|err| {
let sanitized = err
.into_vec()
.into_iter()
.map(|byte| if byte == 0 { b' ' } else { byte })
.collect::<Vec<_>>();
CString::new(sanitized).expect("NUL bytes were replaced")
})
}
pub fn string_to_ffi_ptr(value: impl Into<String>) -> *mut c_char {
into_c_string(value.into()).into_raw()
}
pub unsafe fn free_ffi_string(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}
pub unsafe fn ffi_event_to_agent_event(event: &FfiEvent) -> AgentEvent<'_> {
match event.kind {
EVENT_PROCESS_CREATED => {
let path = unsafe { ptr_to_str(event.target_path) };
let bytes = if event.target_bytes.is_null() || event.target_bytes_len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(event.target_bytes, event.target_bytes_len) }
};
AgentEvent::ProcessCreated {
pid: event.pid,
process: event.process as _,
target_path: path,
target_bytes: bytes,
}
}
EVENT_DLL_LOADED => {
let name = unsafe { c_char_ptr_to_str(event.dll_name) };
AgentEvent::DllLoaded {
name,
base: event.dll_base,
}
}
EVENT_INITIAL_BREAKPOINT => AgentEvent::InitialBreakpoint,
EVENT_THREAD_CREATED => AgentEvent::ThreadCreated {
tid: event.tid,
handle: event.thread_handle as _,
},
EVENT_THREAD_FINISHED => AgentEvent::ThreadFinished { tid: event.tid },
EVENT_DEBUG_STRING => {
let str = unsafe { ptr_to_str(event.dbg_string) };
AgentEvent::DebugString { string: str }
}
EVENT_AGENT_INITIALIZED => {
let path = unsafe { ptr_to_str(event.target_path) };
AgentEvent::AgentInitialized { target_path: path }
}
EVENT_AGENT_FINISHED => AgentEvent::AgentFinished,
EVENT_FINISHED => AgentEvent::Finished,
_ => AgentEvent::Unknown,
}
}
unsafe fn ptr_to_str<'a>(ptr: *const u8) -> &'a str {
if ptr.is_null() {
""
} else {
unsafe { CStr::from_ptr(ptr as *const c_char).to_str().unwrap_or("") }
}
}
unsafe fn c_char_ptr_to_str<'a>(ptr: *const c_char) -> &'a str {
if ptr.is_null() {
""
} else {
unsafe { CStr::from_ptr(ptr).to_str().unwrap_or("") }
}
}
pub fn check_results_to_ffi(results: Vec<CheckResult>) -> FfiResultArray {
if results.is_empty() {
return FfiResultArray {
ptr: std::ptr::null_mut(),
len: 0,
};
}
let mut ffi: Vec<FfiCheckResult> = results
.into_iter()
.map(|r| FfiCheckResult {
plugin: into_c_string(r.plugin).into_raw(),
check: into_c_string(r.check).into_raw(),
severity: match r.severity {
Severity::Pass => 0,
Severity::Info => 1,
Severity::Warn => 2,
Severity::Fail => 3,
Severity::Critical => 4,
},
message: into_c_string(r.message).into_raw(),
details: r
.details
.map(|d| into_c_string(d).into_raw())
.unwrap_or(std::ptr::null_mut()),
})
.collect();
let arr = FfiResultArray {
ptr: ffi.as_mut_ptr(),
len: ffi.len(),
};
std::mem::forget(ffi);
arr
}
pub unsafe fn free_ffi_results(arr: FfiResultArray) {
unsafe {
if arr.ptr.is_null() {
return;
}
let results = Vec::from_raw_parts(arr.ptr, arr.len, arr.len);
for r in results {
free_ffi_string(r.plugin);
free_ffi_string(r.check);
free_ffi_string(r.message);
free_ffi_string(r.details);
}
}
}
pub unsafe fn ffi_array_to_results(arr: &FfiResultArray) -> Vec<CheckResult> {
unsafe {
if arr.ptr.is_null() || arr.len == 0 {
return Vec::new();
}
std::slice::from_raw_parts(arr.ptr, arr.len)
.iter()
.map(|r| CheckResult {
plugin: string_from_ptr(r.plugin),
check: string_from_ptr(r.check),
severity: match r.severity {
0 => Severity::Pass,
1 => Severity::Info,
2 => Severity::Warn,
3 => Severity::Fail,
_ => Severity::Critical,
},
message: string_from_ptr(r.message),
details: (!r.details.is_null()).then(|| string_from_ptr(r.details)),
})
.collect()
}
}
unsafe fn string_from_ptr(ptr: *const c_char) -> String {
if ptr.is_null() {
String::new()
} else {
unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }
}
}