1use crate::binary_types::RbResponse;
6use crate::buffer::FfiBuffer;
7use crate::handle::{PluginHandle, PluginHandleManager};
8use crate::panic_guard::catch_panic;
9use dashmap::DashMap;
10use once_cell::sync::OnceCell;
11use rustbridge_core::{LogLevel, PluginConfig};
12use rustbridge_logging::{LogCallback, LogCallbackManager};
13use rustbridge_transport::ResponseEnvelope;
14use std::ffi::c_void;
15use std::panic::AssertUnwindSafe;
16use std::ptr;
17
18pub type FfiPluginHandle = *mut c_void;
20
21#[unsafe(no_mangle)]
37pub unsafe extern "C" fn plugin_init(
38 plugin_ptr: *mut c_void,
39 config_json: *const u8,
40 config_len: usize,
41 log_callback: Option<LogCallback>,
42) -> FfiPluginHandle {
43 match catch_panic(
45 0,
46 AssertUnwindSafe(|| unsafe {
47 plugin_init_impl(plugin_ptr, config_json, config_len, log_callback)
48 }),
49 ) {
50 Ok(handle) => handle,
51 Err(_error_buffer) => {
52 ptr::null_mut()
55 }
56 }
57}
58
59unsafe fn plugin_init_impl(
61 plugin_ptr: *mut c_void,
62 config_json: *const u8,
63 config_len: usize,
64 log_callback: Option<LogCallback>,
65) -> FfiPluginHandle {
66 if plugin_ptr.is_null() {
68 return ptr::null_mut();
69 }
70
71 let config = if config_json.is_null() || config_len == 0 {
73 PluginConfig::default()
74 } else {
75 let config_slice = unsafe { std::slice::from_raw_parts(config_json, config_len) };
77 match PluginConfig::from_json(config_slice) {
78 Ok(c) => c,
79 Err(e) => {
80 eprintln!("Failed to parse config: {}", e);
82 return ptr::null_mut();
83 }
84 }
85 };
86
87 LogCallbackManager::global().register_plugin(log_callback);
89
90 let log_level = match config.log_level.to_lowercase().as_str() {
92 "trace" => LogLevel::Trace,
93 "debug" => LogLevel::Debug,
94 "info" => LogLevel::Info,
95 "warn" => LogLevel::Warn,
96 "error" => LogLevel::Error,
97 "off" => LogLevel::Off,
98 _ => LogLevel::Info, };
100 LogCallbackManager::global().set_level(log_level);
101
102 rustbridge_logging::init_logging();
104
105 crate::panic_guard::install_panic_hook();
107
108 let plugin: Box<Box<dyn rustbridge_core::Plugin>> =
111 unsafe { Box::from_raw(plugin_ptr as *mut Box<dyn rustbridge_core::Plugin>) };
112
113 let handle = match PluginHandle::new(*plugin, config) {
115 Ok(h) => h,
116 Err(e) => {
117 tracing::error!("Failed to create handle: {}", e);
118 return ptr::null_mut();
119 }
120 };
121
122 if let Err(e) = handle.start() {
124 tracing::error!("Failed to start plugin: {}", e);
125 return ptr::null_mut();
126 }
127
128 let id = PluginHandleManager::global().register(handle);
130
131 if let Some(h) = PluginHandleManager::global().get(id) {
133 h.set_id(id);
134 }
135
136 id as FfiPluginHandle
137}
138
139#[unsafe(no_mangle)]
155pub unsafe extern "C" fn plugin_call(
156 handle: FfiPluginHandle,
157 type_tag: *const std::ffi::c_char,
158 request: *const u8,
159 request_len: usize,
160) -> FfiBuffer {
161 let handle_id = handle as u64;
163 match catch_panic(
164 handle_id,
165 AssertUnwindSafe(|| unsafe { plugin_call_impl(handle, type_tag, request, request_len) }),
166 ) {
167 Ok(result) => result,
168 Err(error_buffer) => error_buffer,
169 }
170}
171
172unsafe fn plugin_call_impl(
174 handle: FfiPluginHandle,
175 type_tag: *const std::ffi::c_char,
176 request: *const u8,
177 request_len: usize,
178) -> FfiBuffer {
179 let id = handle as u64;
181 let plugin_handle = match PluginHandleManager::global().get(id) {
182 Some(h) => h,
183 None => return FfiBuffer::error(1, "Invalid handle"),
184 };
185
186 let type_tag_str = if type_tag.is_null() {
188 return FfiBuffer::error(4, "Type tag is null");
189 } else {
190 match unsafe { std::ffi::CStr::from_ptr(type_tag) }.to_str() {
192 Ok(s) => s,
193 Err(_) => return FfiBuffer::error(4, "Invalid type tag encoding"),
194 }
195 };
196
197 let request_data = if request.is_null() || request_len == 0 {
199 &[]
200 } else {
201 unsafe { std::slice::from_raw_parts(request, request_len) }
203 };
204
205 match plugin_handle.call(type_tag_str, request_data) {
207 Ok(response_data) => {
208 match ResponseEnvelope::success_raw(&response_data) {
210 Ok(envelope) => match envelope.to_bytes() {
211 Ok(bytes) => FfiBuffer::from_vec(bytes),
212 Err(e) => FfiBuffer::error(5, &format!("Serialization error: {}", e)),
213 },
214 Err(e) => FfiBuffer::error(5, &format!("Serialization error: {}", e)),
215 }
216 }
217 Err(e) => {
218 let envelope = ResponseEnvelope::from_error(&e);
219 match envelope.to_bytes() {
220 Ok(bytes) => {
221 let mut buf = FfiBuffer::from_vec(bytes);
222 buf.error_code = e.error_code();
223 buf
224 }
225 Err(se) => FfiBuffer::error(e.error_code(), &format!("{}: {}", e, se)),
226 }
227 }
228 }
229}
230
231#[unsafe(no_mangle)]
237pub unsafe extern "C" fn plugin_free_buffer(buffer: *mut FfiBuffer) {
238 unsafe {
239 if !buffer.is_null() {
240 (*buffer).free();
241 }
242 }
243}
244
245#[unsafe(no_mangle)]
257pub unsafe extern "C" fn plugin_shutdown(handle: FfiPluginHandle) -> bool {
258 let handle_id = handle as u64;
260 catch_panic(handle_id, AssertUnwindSafe(|| plugin_shutdown_impl(handle))).unwrap_or_default() }
262
263fn plugin_shutdown_impl(handle: FfiPluginHandle) -> bool {
265 let id = handle as u64;
266
267 let plugin_handle = match PluginHandleManager::global().remove(id) {
269 Some(h) => h,
270 None => return false,
271 };
272
273 let result = match plugin_handle.shutdown(5000) {
275 Ok(()) => true,
276 Err(e) => {
277 tracing::error!("Shutdown error: {}", e);
278 false
279 }
280 };
281
282 clear_binary_handlers();
284
285 LogCallbackManager::global().unregister_plugin();
289
290 result
291}
292
293#[unsafe(no_mangle)]
302pub unsafe extern "C" fn plugin_set_log_level(handle: FfiPluginHandle, level: u8) {
303 let id = handle as u64;
304
305 if let Some(plugin_handle) = PluginHandleManager::global().get(id) {
306 plugin_handle.set_log_level(LogLevel::from_u8(level));
307 }
308}
309
310#[unsafe(no_mangle)]
322pub unsafe extern "C" fn plugin_get_state(handle: FfiPluginHandle) -> u8 {
323 let id = handle as u64;
324
325 match PluginHandleManager::global().get(id) {
326 Some(h) => match h.state() {
327 rustbridge_core::LifecycleState::Installed => 0,
328 rustbridge_core::LifecycleState::Starting => 1,
329 rustbridge_core::LifecycleState::Active => 2,
330 rustbridge_core::LifecycleState::Stopping => 3,
331 rustbridge_core::LifecycleState::Stopped => 4,
332 rustbridge_core::LifecycleState::Failed => 5,
333 },
334 None => 255,
335 }
336}
337
338#[unsafe(no_mangle)]
349pub unsafe extern "C" fn plugin_get_rejected_count(handle: FfiPluginHandle) -> u64 {
350 let id = handle as u64;
351 match PluginHandleManager::global().get(id) {
352 Some(h) => h.rejected_request_count(),
353 None => 0,
354 }
355}
356
357pub type BinaryMessageHandler =
367 fn(handle: &PluginHandle, request: &[u8]) -> Result<Vec<u8>, rustbridge_core::PluginError>;
368
369static BINARY_HANDLERS: OnceCell<DashMap<u32, BinaryMessageHandler>> = OnceCell::new();
375
376fn binary_handlers() -> &'static DashMap<u32, BinaryMessageHandler> {
378 BINARY_HANDLERS.get_or_init(DashMap::new)
379}
380
381pub fn register_binary_handler(message_id: u32, handler: BinaryMessageHandler) {
386 binary_handlers().insert(message_id, handler);
387}
388
389pub(crate) fn clear_binary_handlers() {
394 binary_handlers().clear();
395}
396
397#[unsafe(no_mangle)]
413pub unsafe extern "C" fn plugin_call_raw(
414 handle: FfiPluginHandle,
415 message_id: u32,
416 request: *const c_void,
417 request_size: usize,
418) -> RbResponse {
419 let handle_id = handle as u64;
421 match catch_panic(
422 handle_id,
423 AssertUnwindSafe(|| unsafe {
424 plugin_call_raw_impl(handle, message_id, request, request_size)
425 }),
426 ) {
427 Ok(result) => result,
428 Err(error_buffer) => {
429 let msg = if error_buffer.is_error() && !error_buffer.data.is_null() {
431 let slice =
433 unsafe { std::slice::from_raw_parts(error_buffer.data, error_buffer.len) };
434 String::from_utf8_lossy(slice).into_owned()
435 } else {
436 "Internal error (panic)".to_string()
437 };
438 let mut buf = error_buffer;
440 unsafe { buf.free() };
442 RbResponse::error(11, &msg)
443 }
444 }
445}
446
447unsafe fn plugin_call_raw_impl(
449 handle: FfiPluginHandle,
450 message_id: u32,
451 request: *const c_void,
452 request_size: usize,
453) -> RbResponse {
454 let id = handle as u64;
456 let plugin_handle = match PluginHandleManager::global().get(id) {
457 Some(h) => h,
458 None => return RbResponse::error(1, "Invalid handle"),
459 };
460
461 if !plugin_handle.state().can_handle_requests() {
463 return RbResponse::error(1, "Plugin not in Active state");
464 }
465
466 let request_data = if request.is_null() || request_size == 0 {
468 &[]
469 } else {
470 unsafe { std::slice::from_raw_parts(request as *const u8, request_size) }
472 };
473
474 let handler = binary_handlers().get(&message_id).map(|r| *r);
476
477 match handler {
478 Some(h) => {
479 match h(&plugin_handle, request_data) {
481 Ok(response_bytes) => {
482 let mut response = RbResponse::empty();
485 let len = response_bytes.len();
486 let capacity = response_bytes.capacity();
487 let data = response_bytes.leak().as_mut_ptr();
488
489 response.error_code = 0;
490 response.len = len as u32;
491 response.capacity = capacity as u32;
492 response.data = data as *mut c_void;
493
494 response
495 }
496 Err(e) => RbResponse::error(e.error_code(), &e.to_string()),
497 }
498 }
499 None => RbResponse::error(6, &format!("Unknown message ID: {}", message_id)),
500 }
501}
502
503#[unsafe(no_mangle)]
509pub unsafe extern "C" fn rb_response_free(response: *mut RbResponse) {
510 unsafe {
511 if !response.is_null() {
512 (*response).free();
513 }
514 }
515}
516
517#[cfg(test)]
522#[path = "exports/exports_tests.rs"]
523mod exports_tests;
524
525#[cfg(test)]
526#[path = "exports/ffi_boundary_tests.rs"]
527mod ffi_boundary_tests;