Files
hakorune/src/bid/metadata.rs

247 lines
6.6 KiB
Rust

use super::{BidError, BidResult};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
/// Host function table provided to plugins
#[repr(C)]
pub struct NyashHostVtable {
pub alloc: unsafe extern "C" fn(size: usize) -> *mut u8,
pub free: unsafe extern "C" fn(ptr: *mut u8),
pub wake: unsafe extern "C" fn(handle: u64),
pub log: unsafe extern "C" fn(level: i32, msg: *const c_char),
}
impl NyashHostVtable {
/// Create a vtable with no-op stubs (for tests)
pub fn empty() -> Self {
unsafe extern "C" fn a(_size: usize) -> *mut u8 {
std::ptr::null_mut()
}
unsafe extern "C" fn f(_ptr: *mut u8) {}
unsafe extern "C" fn w(_h: u64) {}
unsafe extern "C" fn l(_level: i32, _m: *const c_char) {}
Self {
alloc: a,
free: f,
wake: w,
log: l,
}
}
}
/// Method information
#[repr(C)]
pub struct NyashMethodInfo {
/// Method ID (unique within the Box type)
pub method_id: u32,
/// Method name (null-terminated C string)
pub method_name: *const c_char,
/// Type signature hash for validation
pub signature_hash: u32,
}
// SAFETY: The C pointers in NyashMethodInfo are read-only after initialization
// and point to valid memory managed by the plugin system
unsafe impl Send for NyashMethodInfo {}
unsafe impl Sync for NyashMethodInfo {}
impl NyashMethodInfo {
/// Create method info with safe string handling
pub fn new(
method_id: u32,
method_name: &str,
signature_hash: u32,
) -> BidResult<(Self, CString)> {
let c_name = CString::new(method_name).map_err(|_| BidError::InvalidUtf8)?;
let info = Self {
method_id,
method_name: c_name.as_ptr(),
signature_hash,
};
Ok((info, c_name))
}
/// Get method name as string (unsafe: requires valid pointer)
pub unsafe fn name(&self) -> BidResult<&str> {
if self.method_name.is_null() {
return Err(BidError::InvalidArgs);
}
CStr::from_ptr(self.method_name)
.to_str()
.map_err(|_| BidError::InvalidUtf8)
}
}
/// Plugin information
#[repr(C)]
pub struct NyashPluginInfo {
/// Box type ID (e.g., 6 for FileBox)
pub type_id: u32,
/// Type name (null-terminated C string)
pub type_name: *const c_char,
/// Number of methods
pub method_count: u32,
/// Method information array
pub methods: *const NyashMethodInfo,
}
// SAFETY: The C pointers in NyashPluginInfo are read-only after initialization
// and point to valid memory managed by the plugin system
unsafe impl Send for NyashPluginInfo {}
unsafe impl Sync for NyashPluginInfo {}
impl NyashPluginInfo {
/// Create an empty plugin info
pub fn empty() -> Self {
Self {
type_id: 0,
type_name: std::ptr::null(),
method_count: 0,
methods: std::ptr::null(),
}
}
/// Get type name as string (unsafe: requires valid pointer)
pub unsafe fn name(&self) -> BidResult<&str> {
if self.type_name.is_null() {
return Err(BidError::InvalidArgs);
}
CStr::from_ptr(self.type_name)
.to_str()
.map_err(|_| BidError::InvalidUtf8)
}
/// Get methods as slice (unsafe: requires valid pointer and count)
pub unsafe fn methods_slice(&self) -> BidResult<&[NyashMethodInfo]> {
if self.methods.is_null() || self.method_count == 0 {
return Ok(&[]);
}
Ok(std::slice::from_raw_parts(
self.methods,
self.method_count as usize,
))
}
}
/// Plugin lifecycle state
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PluginState {
/// Plugin loaded but not initialized
Loaded,
/// Plugin initialized and ready
Ready,
/// Plugin shutting down
ShuttingDown,
/// Plugin unloaded
Unloaded,
}
/// Plugin metadata holder for Rust side
pub struct PluginMetadata {
pub info: NyashPluginInfo,
pub state: PluginState,
// Keep CStrings alive for C interop
#[allow(dead_code)]
type_name_holder: Option<CString>,
#[allow(dead_code)]
method_holders: Vec<(NyashMethodInfo, CString)>,
}
impl PluginMetadata {
/// Create metadata from plugin info
pub fn new(
type_id: u32,
type_name: &str,
methods: Vec<(u32, &str, u32)>, // (id, name, hash)
) -> BidResult<Self> {
// Create type name
let type_name_holder = CString::new(type_name).map_err(|_| BidError::InvalidUtf8)?;
// Create method infos
let mut method_holders = Vec::new();
for (id, name, hash) in methods {
let (info, holder) = NyashMethodInfo::new(id, name, hash)?;
method_holders.push((info, holder));
}
// Build plugin info
let info = NyashPluginInfo {
type_id,
type_name: type_name_holder.as_ptr(),
method_count: method_holders.len() as u32,
methods: if method_holders.is_empty() {
std::ptr::null()
} else {
method_holders.as_ptr() as *const NyashMethodInfo
},
};
Ok(Self {
info,
state: PluginState::Loaded,
type_name_holder: Some(type_name_holder),
method_holders,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plugin_metadata_creation() {
let methods = vec![
(1, "open", 0x12345678),
(2, "read", 0x87654321),
(3, "write", 0x11223344),
(4, "close", 0xABCDEF00),
];
let metadata = PluginMetadata::new(6, "FileBox", methods).unwrap();
assert_eq!(metadata.info.type_id, 6);
assert_eq!(metadata.info.method_count, 4);
assert_eq!(metadata.state, PluginState::Loaded);
unsafe {
assert_eq!(metadata.info.name().unwrap(), "FileBox");
let methods = metadata.info.methods_slice().unwrap();
assert_eq!(methods.len(), 4);
assert_eq!(methods[0].method_id, 1);
assert_eq!(methods[0].name().unwrap(), "open");
}
}
#[test]
fn test_host_vtable() {
unsafe extern "C" fn a(_size: usize) -> *mut u8 {
std::ptr::null_mut()
}
unsafe extern "C" fn f(_p: *mut u8) {}
unsafe extern "C" fn w(_h: u64) {}
unsafe extern "C" fn l(_level: i32, _m: *const c_char) {}
let _v = NyashHostVtable {
alloc: a,
free: f,
wake: w,
log: l,
};
}
}