use core::ffi::c_void;
use polyplug::loader::BundleLoader;
use polyplug_abi::AbiError;
use polyplug_abi::AbiErrorCode;
use polyplug_abi::CallArena;
use polyplug_abi::GuestContractInstance;
use polyplug_abi::HostContractInstance;
use polyplug_abi::HostContractInterface;
use polyplug_abi::StringView;
use polyplug_abi::VmLoaderData;
use crate::{LuaConfig, LuaLoader};
#[repr(C)]
pub struct PolyplugLuaConfig {
pub _reserved: u8,
}
#[repr(C)]
pub struct PolyplugLuaHostDispatchBridge {
pub callback: Option<unsafe extern "C" fn(*mut c_void, u32, *const c_void, *mut c_void) -> u32>,
pub destroy_callback: Option<unsafe extern "C" fn(*mut c_void)>,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn polyplug_lua_host_vm_dispatch(
loader_data: VmLoaderData,
instance: GuestContractInstance,
fn_id: u32,
args: *const (),
out: *mut (),
_arena: *mut CallArena,
out_err: *mut AbiError,
) {
let result: AbiError =
unsafe { polyplug_lua_host_vm_dispatch_impl(loader_data, instance.data, fn_id, args, out) };
if !out_err.is_null() {
unsafe { out_err.write(result) };
}
}
unsafe fn polyplug_lua_host_vm_dispatch_impl(
loader_data: VmLoaderData,
instance_data: *mut c_void,
fn_id: u32,
args: *const (),
out: *mut (),
) -> AbiError {
let bridge_ptr: *const PolyplugLuaHostDispatchBridge =
loader_data.data as *const PolyplugLuaHostDispatchBridge;
if bridge_ptr.is_null() {
return AbiError {
code: AbiErrorCode::InvalidPointer as u32,
message: StringView::from_static(b"lua host dispatch bridge is null"),
};
}
let callback: Option<
unsafe extern "C" fn(*mut c_void, u32, *const c_void, *mut c_void) -> u32,
> = unsafe { (*bridge_ptr).callback };
match callback {
Some(cb) => {
let code: u32 = unsafe {
cb(
instance_data,
fn_id,
args as *const c_void,
out as *mut c_void,
)
};
if code == AbiErrorCode::Ok as u32 {
AbiError::ok()
} else {
AbiError {
code,
message: StringView::from_static(b"lua host contract returned error"),
}
}
}
None => AbiError {
code: AbiErrorCode::InvalidPointer as u32,
message: StringView::from_static(b"lua host dispatch bridge has no callback"),
},
}
}
#[repr(C)]
pub struct PolyplugLuaLogBridge {
pub callback:
Option<unsafe extern "C" fn(*mut c_void, u32, *const u8, usize, *const u8, usize)>,
pub user_data: *mut c_void,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn polyplug_lua_log_trampoline(
user_data: *mut c_void,
level: u32,
scope: StringView,
message: StringView,
) {
let bridge_ptr: *const PolyplugLuaLogBridge = user_data as *const PolyplugLuaLogBridge;
if bridge_ptr.is_null() {
return;
}
let callback: Option<
unsafe extern "C" fn(*mut c_void, u32, *const u8, usize, *const u8, usize),
> = unsafe { (*bridge_ptr).callback };
if let Some(cb) = callback {
let inner_user_data: *mut c_void = unsafe { (*bridge_ptr).user_data };
unsafe {
cb(
inner_user_data,
level,
scope.ptr,
scope.len,
message.ptr,
message.len,
)
};
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn polyplug_lua_host_destroy_instance(
this: *const HostContractInterface,
instance: HostContractInstance,
) {
if this.is_null() {
return;
}
let bridge_ptr: *const PolyplugLuaHostDispatchBridge =
unsafe { (*this).user_data } as *const PolyplugLuaHostDispatchBridge;
if bridge_ptr.is_null() {
return;
}
let destroy_callback: Option<unsafe extern "C" fn(*mut c_void)> =
unsafe { (*bridge_ptr).destroy_callback };
if let Some(cb) = destroy_callback {
unsafe { cb(instance.data) };
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn polyplug_lua_loader_create(
config: *const PolyplugLuaConfig,
) -> *mut c_void {
let _ = config;
let loader: LuaLoader = LuaLoader::new(LuaConfig::default());
let trait_obj: Box<dyn BundleLoader> = Box::new(loader);
Box::into_raw(Box::new(trait_obj)) as *mut c_void
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn polyplug_lua_loader_free(ptr: *mut c_void) {
if ptr.is_null() {
return;
}
drop(unsafe { Box::<Box<dyn BundleLoader>>::from_raw(ptr as *mut Box<dyn BundleLoader>) });
}
#[cfg(test)]
mod tests {
use core::ffi::c_void;
use polyplug_abi::StringView;
use super::{PolyplugLuaLogBridge, polyplug_lua_log_trampoline};
struct Captured {
calls: u32,
level: u32,
scope: String,
message: String,
}
unsafe extern "C" fn capture_callback(
user_data: *mut c_void,
level: u32,
scope_ptr: *const u8,
scope_len: usize,
msg_ptr: *const u8,
msg_len: usize,
) {
let captured: &mut Captured = unsafe { &mut *(user_data as *mut Captured) };
captured.calls += 1;
captured.level = level;
let scope_bytes: &[u8] = unsafe { core::slice::from_raw_parts(scope_ptr, scope_len) };
let msg_bytes: &[u8] = unsafe { core::slice::from_raw_parts(msg_ptr, msg_len) };
captured.scope = String::from_utf8_lossy(scope_bytes).into_owned();
captured.message = String::from_utf8_lossy(msg_bytes).into_owned();
}
#[test]
fn trampoline_forwards_level_scope_message_intact() {
let mut captured = Captured {
calls: 0,
level: 0,
scope: String::new(),
message: String::new(),
};
let mut bridge = PolyplugLuaLogBridge {
callback: Some(capture_callback),
user_data: &mut captured as *mut Captured as *mut c_void,
};
let scope: StringView = StringView::from_static(b"manifest");
let message: StringView = StringView::from_static(b"ByBundle dep 'x' has no bundle_id");
unsafe {
polyplug_lua_log_trampoline(
&mut bridge as *mut PolyplugLuaLogBridge as *mut c_void,
2,
scope,
message,
);
}
assert_eq!(captured.calls, 1);
assert_eq!(captured.level, 2);
assert_eq!(captured.scope, "manifest");
assert_eq!(captured.message, "ByBundle dep 'x' has no bundle_id");
}
#[test]
fn trampoline_forwards_empty_views() {
let mut captured = Captured {
calls: 0,
level: 0,
scope: String::from("stale"),
message: String::from("stale"),
};
let mut bridge = PolyplugLuaLogBridge {
callback: Some(capture_callback),
user_data: &mut captured as *mut Captured as *mut c_void,
};
unsafe {
polyplug_lua_log_trampoline(
&mut bridge as *mut PolyplugLuaLogBridge as *mut c_void,
1,
StringView::from_static(b""),
StringView::from_static(b""),
);
}
assert_eq!(captured.calls, 1);
assert_eq!(captured.level, 1);
assert_eq!(captured.scope, "");
assert_eq!(captured.message, "");
}
#[test]
fn trampoline_is_noop_on_null_user_data() {
unsafe {
polyplug_lua_log_trampoline(
core::ptr::null_mut(),
3,
StringView::from_static(b"scope"),
StringView::from_static(b"message"),
);
}
}
#[test]
fn trampoline_is_noop_on_null_inner_callback() {
let mut bridge = PolyplugLuaLogBridge {
callback: None,
user_data: core::ptr::null_mut(),
};
unsafe {
polyplug_lua_log_trampoline(
&mut bridge as *mut PolyplugLuaLogBridge as *mut c_void,
3,
StringView::from_static(b"scope"),
StringView::from_static(b"message"),
);
}
}
}