cradle_plugin_api/
convert.rs1use crate::ffi::*;
2use crate::{AgentEvent, CheckResult, Severity};
3use std::ffi::{CStr, CString};
4use std::os::raw::c_char;
5
6pub struct FfiEventData {
8 event: FfiEvent,
9 _strings: Vec<CString>,
10}
11
12impl FfiEventData {
13 pub fn as_ptr(&self) -> *const FfiEvent {
15 &self.event
16 }
17}
18
19impl AsRef<FfiEvent> for FfiEventData {
20 fn as_ref(&self) -> &FfiEvent {
21 &self.event
22 }
23}
24
25pub fn agent_event_to_ffi(event: &AgentEvent) -> FfiEventData {
27 let mut ffi = FfiEvent {
28 kind: 0,
29 pid: 0,
30 process: 0,
31 dll_name: std::ptr::null(),
32 dll_base: 0,
33 tid: 0,
34 thread_handle: 0,
35 dbg_string: std::ptr::null(),
36 target_path: std::ptr::null(),
37 target_bytes: std::ptr::null(),
38 target_bytes_len: 0,
39 };
40 let mut strings = Vec::new();
41
42 match event {
43 AgentEvent::ProcessCreated {
44 pid,
45 process,
46 target_bytes,
47 target_path,
48 } => {
49 ffi.kind = EVENT_PROCESS_CREATED;
50 ffi.pid = *pid;
51 ffi.process = *process as isize;
52 ffi.target_bytes = target_bytes.as_ptr();
53 ffi.target_bytes_len = target_bytes.len();
54 ffi.target_path = push_c_string(&mut strings, target_path) as *const u8;
55 }
56 AgentEvent::DllLoaded { name, base } => {
57 ffi.kind = EVENT_DLL_LOADED;
58 ffi.dll_base = *base;
59 ffi.dll_name = push_c_string(&mut strings, name);
60 }
61 AgentEvent::InitialBreakpoint => {
62 ffi.kind = EVENT_INITIAL_BREAKPOINT;
63 }
64 AgentEvent::ThreadCreated { tid, handle } => {
65 ffi.kind = EVENT_THREAD_CREATED;
66 ffi.tid = *tid;
67 ffi.thread_handle = *handle as isize;
68 }
69 AgentEvent::Finished => {
70 ffi.kind = EVENT_FINISHED;
71 }
72 AgentEvent::ThreadFinished { tid } => {
73 ffi.kind = EVENT_THREAD_FINISHED;
74 ffi.tid = *tid;
75 }
76 AgentEvent::DebugString { string } => {
77 ffi.kind = EVENT_DEBUG_STRING;
78 ffi.dbg_string = push_c_string(&mut strings, string) as *const u8;
79 }
80 AgentEvent::AgentInitialized { target_path } => {
81 ffi.kind = EVENT_AGENT_INITIALIZED;
82 ffi.target_path = push_c_string(&mut strings, target_path) as *const u8;
83 }
84 AgentEvent::AgentFinished => {
85 ffi.kind = EVENT_AGENT_FINISHED;
86 ffi.target_path = std::ptr::null();
87 }
88 _ => {}
89 }
90 FfiEventData {
91 event: ffi,
92 _strings: strings,
93 }
94}
95
96fn push_c_string(strings: &mut Vec<CString>, value: &str) -> *const c_char {
97 let c_string = into_c_string(value.to_string());
98 let ptr = c_string.as_ptr();
99 strings.push(c_string);
100 ptr
101}
102
103fn into_c_string(value: String) -> CString {
104 CString::new(value).unwrap_or_else(|err| {
105 let sanitized = err
106 .into_vec()
107 .into_iter()
108 .map(|byte| if byte == 0 { b' ' } else { byte })
109 .collect::<Vec<_>>();
110 CString::new(sanitized).expect("NUL bytes were replaced")
111 })
112}
113
114pub fn string_to_ffi_ptr(value: impl Into<String>) -> *mut c_char {
118 into_c_string(value.into()).into_raw()
119}
120
121pub unsafe fn free_ffi_string(ptr: *mut c_char) {
128 if !ptr.is_null() {
129 unsafe {
130 let _ = CString::from_raw(ptr);
131 }
132 }
133}
134
135pub unsafe fn ffi_event_to_agent_event(event: &FfiEvent) -> AgentEvent<'_> {
142 match event.kind {
143 EVENT_PROCESS_CREATED => {
144 let path = unsafe { ptr_to_str(event.target_path) };
145 let bytes = if event.target_bytes.is_null() || event.target_bytes_len == 0 {
146 &[]
147 } else {
148 unsafe { std::slice::from_raw_parts(event.target_bytes, event.target_bytes_len) }
149 };
150 AgentEvent::ProcessCreated {
151 pid: event.pid,
152 process: event.process as _,
153 target_path: path,
154 target_bytes: bytes,
155 }
156 }
157 EVENT_DLL_LOADED => {
158 let name = unsafe { c_char_ptr_to_str(event.dll_name) };
159 AgentEvent::DllLoaded {
160 name,
161 base: event.dll_base,
162 }
163 }
164 EVENT_INITIAL_BREAKPOINT => AgentEvent::InitialBreakpoint,
165 EVENT_THREAD_CREATED => AgentEvent::ThreadCreated {
166 tid: event.tid,
167 handle: event.thread_handle as _,
168 },
169 EVENT_THREAD_FINISHED => AgentEvent::ThreadFinished { tid: event.tid },
170 EVENT_DEBUG_STRING => {
171 let str = unsafe { ptr_to_str(event.dbg_string) };
172 AgentEvent::DebugString { string: str }
173 }
174 EVENT_AGENT_INITIALIZED => {
175 let path = unsafe { ptr_to_str(event.target_path) };
176 AgentEvent::AgentInitialized { target_path: path }
177 }
178 EVENT_AGENT_FINISHED => AgentEvent::AgentFinished,
179 EVENT_FINISHED => AgentEvent::Finished,
180 _ => AgentEvent::Unknown,
181 }
182}
183
184unsafe fn ptr_to_str<'a>(ptr: *const u8) -> &'a str {
185 if ptr.is_null() {
186 ""
187 } else {
188 unsafe { CStr::from_ptr(ptr as *const c_char).to_str().unwrap_or("") }
189 }
190}
191
192unsafe fn c_char_ptr_to_str<'a>(ptr: *const c_char) -> &'a str {
193 if ptr.is_null() {
194 ""
195 } else {
196 unsafe { CStr::from_ptr(ptr).to_str().unwrap_or("") }
197 }
198}
199
200pub fn check_results_to_ffi(results: Vec<CheckResult>) -> FfiResultArray {
202 if results.is_empty() {
203 return FfiResultArray {
204 ptr: std::ptr::null_mut(),
205 len: 0,
206 };
207 }
208 let mut ffi: Vec<FfiCheckResult> = results
209 .into_iter()
210 .map(|r| FfiCheckResult {
211 plugin: into_c_string(r.plugin).into_raw(),
212 check: into_c_string(r.check).into_raw(),
213 severity: match r.severity {
214 Severity::Pass => 0,
215 Severity::Info => 1,
216 Severity::Warn => 2,
217 Severity::Fail => 3,
218 Severity::Critical => 4,
219 },
220 message: into_c_string(r.message).into_raw(),
221 details: r
222 .details
223 .map(|d| into_c_string(d).into_raw())
224 .unwrap_or(std::ptr::null_mut()),
225 })
226 .collect();
227 let arr = FfiResultArray {
228 ptr: ffi.as_mut_ptr(),
229 len: ffi.len(),
230 };
231 std::mem::forget(ffi);
232 arr
233}
234
235pub unsafe fn free_ffi_results(arr: FfiResultArray) {
240 unsafe {
241 if arr.ptr.is_null() {
242 return;
243 }
244 let results = Vec::from_raw_parts(arr.ptr, arr.len, arr.len);
245 for r in results {
246 free_ffi_string(r.plugin);
247 free_ffi_string(r.check);
248 free_ffi_string(r.message);
249 free_ffi_string(r.details);
250 }
251 }
252}
253
254pub unsafe fn ffi_array_to_results(arr: &FfiResultArray) -> Vec<CheckResult> {
259 unsafe {
260 if arr.ptr.is_null() || arr.len == 0 {
261 return Vec::new();
262 }
263 std::slice::from_raw_parts(arr.ptr, arr.len)
264 .iter()
265 .map(|r| CheckResult {
266 plugin: string_from_ptr(r.plugin),
267 check: string_from_ptr(r.check),
268 severity: match r.severity {
269 0 => Severity::Pass,
270 1 => Severity::Info,
271 2 => Severity::Warn,
272 3 => Severity::Fail,
273 _ => Severity::Critical,
274 },
275 message: string_from_ptr(r.message),
276 details: (!r.details.is_null()).then(|| string_from_ptr(r.details)),
277 })
278 .collect()
279 }
280}
281
282unsafe fn string_from_ptr(ptr: *const c_char) -> String {
283 if ptr.is_null() {
284 String::new()
285 } else {
286 unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() }
287 }
288}