1use std::ffi::{CStr, CString};
7use std::os::raw::{c_char, c_int};
8use std::slice;
9
10#[repr(C)]
12pub struct HandlerContext {
13 _private: [u8; 0],
14}
15
16#[repr(C)]
18pub struct FfiResult {
19 pub code: c_int,
21 pub data: *mut u8,
23 pub data_len: usize,
25 pub error: *const c_char,
27}
28
29#[no_mangle]
37pub unsafe extern "C" fn pforge_execute_handler(
38 handler_name: *const c_char,
39 input_json: *const u8,
40 input_len: usize,
41) -> FfiResult {
42 if handler_name.is_null() || input_json.is_null() {
44 return FfiResult {
45 code: -1,
46 data: std::ptr::null_mut(),
47 data_len: 0,
48 error: create_error_string("Null pointer provided"),
49 };
50 }
51
52 let name = match CStr::from_ptr(handler_name).to_str() {
54 Ok(s) => s,
55 Err(_) => {
56 return FfiResult {
57 code: -2,
58 data: std::ptr::null_mut(),
59 data_len: 0,
60 error: create_error_string("Invalid UTF-8 in handler name"),
61 }
62 }
63 };
64
65 let _input = slice::from_raw_parts(input_json, input_len);
67
68 let response = serde_json::json!({
71 "handler": name,
72 "input_size": input_len,
73 "status": "ok"
74 });
75
76 match serde_json::to_vec(&response) {
77 Ok(data) => {
78 let mut boxed = data.into_boxed_slice();
79 let data_ptr = boxed.as_mut_ptr();
80 let data_len = boxed.len();
81 #[allow(clippy::mem_forget)]
84 std::mem::forget(boxed);
85
86 FfiResult {
87 code: 0,
88 data: data_ptr,
89 data_len,
90 error: std::ptr::null(),
91 }
92 }
93 Err(e) => FfiResult {
94 code: -3,
95 data: std::ptr::null_mut(),
96 data_len: 0,
97 error: create_error_string(&format!("Serialization error: {}", e)),
98 },
99 }
100}
101
102#[no_mangle]
108pub unsafe extern "C" fn pforge_free_result(result: FfiResult) {
109 if !result.data.is_null() && result.data_len > 0 {
110 let _ = Vec::from_raw_parts(result.data, result.data_len, result.data_len);
111 }
112 if !result.error.is_null() {
113 let _ = CString::from_raw(result.error as *mut c_char);
114 }
115}
116
117#[no_mangle]
122pub unsafe extern "C" fn pforge_version() -> *const c_char {
123 static VERSION: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
124 VERSION.as_ptr() as *const c_char
125}
126
127fn create_error_string(msg: &str) -> *const c_char {
130 match CString::new(msg) {
131 Ok(s) => s.into_raw() as *const c_char,
132 Err(_) => std::ptr::null(),
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use std::ffi::CString;
140
141 #[test]
142 fn test_version() {
143 unsafe {
144 let version = pforge_version();
145 assert!(!version.is_null());
146 let version_str = CStr::from_ptr(version).to_str().unwrap();
147 assert!(version_str.starts_with("0.1"));
148 }
149 }
150
151 #[test]
152 fn test_execute_handler_null_safety() {
153 unsafe {
154 let result = pforge_execute_handler(std::ptr::null(), std::ptr::null(), 0);
156 assert_eq!(result.code, -1);
157 pforge_free_result(result);
158 }
159 }
160
161 #[test]
162 fn test_execute_handler_success() {
163 unsafe {
164 let handler_name = CString::new("test_handler").unwrap();
165 let input = b"{}";
166
167 let result = pforge_execute_handler(handler_name.as_ptr(), input.as_ptr(), input.len());
168
169 assert_eq!(result.code, 0);
170 assert!(!result.data.is_null());
171 assert!(result.data_len > 0);
172
173 let data_slice = slice::from_raw_parts(result.data, result.data_len);
175 let response: serde_json::Value = serde_json::from_slice(data_slice).unwrap();
176 assert_eq!(response["handler"], "test_handler");
177 assert_eq!(response["status"], "ok");
178
179 pforge_free_result(result);
180 }
181 }
182}