feat(phase-9.75g-0): Implement BID-FFI Day 2 - Metadata API and plugin lifecycle

BID-1 Implementation Progress (Day 2 Complete\! 🎉):

Metadata API Implementation:
- Add NyashHostVtable for host function integration
  - Memory allocation/free functions
  - FutureBox wake support
  - Logging capability
- Implement plugin metadata structures
  - NyashPluginInfo: type_id, type_name, methods
  - NyashMethodInfo: method_id, method_name, signature_hash
  - PluginMetadata: Rust-side holder with lifecycle state

Plugin API Definitions:
- Define C FFI function signatures
  - nyash_plugin_abi(): Get ABI version
  - nyash_plugin_init(): Initialize with host vtable
  - nyash_plugin_invoke(): Unified method invocation
  - nyash_plugin_shutdown(): Cleanup resources
- Implement PluginHandle for loaded plugin management
- Add two-call pattern support for dynamic result sizes

Test Coverage: 7/7 
- bid::types::tests (2 tests)
- bid::tlv::tests (2 tests)
- bid::metadata::tests (2 tests)
- bid::plugin_api::tests (1 test)

Everything is Box philosophy progressing with practical FFI design\!

Next: Day 3 - Existing Box integration (StringBox/IntegerBox/FutureBox bridge)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-08-17 19:01:16 +09:00
parent f5ee08f375
commit ec99a97957
3 changed files with 506 additions and 0 deletions

254
src/bid/plugin_api.rs Normal file
View File

@ -0,0 +1,254 @@
use super::{BidError, BidResult, NyashHostVtable, NyashPluginInfo};
use std::os::raw::c_char;
/// Plugin API function signatures for C FFI
///
/// These are the function signatures that plugins must implement.
/// They are defined as Rust types for type safety when loading plugins.
/// Get plugin ABI version
/// Returns: BID version number (1 for BID-1)
pub type PluginAbiFn = unsafe extern "C" fn() -> u32;
/// Initialize plugin
/// Parameters:
/// - host: Host function table for plugin to use
/// - info: Plugin information to be filled by plugin
/// Returns: 0 on success, negative error code on failure
pub type PluginInitFn = unsafe extern "C" fn(
host: *const NyashHostVtable,
info: *mut NyashPluginInfo,
) -> i32;
/// Invoke a plugin method
/// Parameters:
/// - type_id: Box type ID
/// - method_id: Method ID
/// - instance_id: Instance ID (for instance methods)
/// - args: BID-1 TLV encoded arguments
/// - args_len: Length of arguments
/// - result: Buffer for BID-1 TLV encoded result
/// - result_len: Input: buffer size, Output: actual result size
/// Returns: 0 on success, negative error code on failure
pub type PluginInvokeFn = unsafe extern "C" fn(
type_id: u32,
method_id: u32,
instance_id: u32,
args: *const u8,
args_len: usize,
result: *mut u8,
result_len: *mut usize,
) -> i32;
/// Shutdown plugin and cleanup resources
pub type PluginShutdownFn = unsafe extern "C" fn();
/// Plugin API entry points
pub const PLUGIN_ABI_SYMBOL: &str = "nyash_plugin_abi";
pub const PLUGIN_INIT_SYMBOL: &str = "nyash_plugin_init";
pub const PLUGIN_INVOKE_SYMBOL: &str = "nyash_plugin_invoke";
pub const PLUGIN_SHUTDOWN_SYMBOL: &str = "nyash_plugin_shutdown";
/// Plugin handle containing loaded functions
pub struct PluginHandle {
pub abi: PluginAbiFn,
pub init: PluginInitFn,
pub invoke: PluginInvokeFn,
pub shutdown: PluginShutdownFn,
}
impl PluginHandle {
/// Validate ABI version
pub fn check_abi(&self) -> BidResult<()> {
let version = unsafe { (self.abi)() };
if version != super::BID_VERSION as u32 {
return Err(BidError::VersionMismatch);
}
Ok(())
}
/// Initialize plugin with host vtable
pub fn initialize(
&self,
host: &NyashHostVtable,
info: &mut NyashPluginInfo,
) -> BidResult<()> {
let result = unsafe {
(self.init)(
host as *const NyashHostVtable,
info as *mut NyashPluginInfo,
)
};
if result != 0 {
Err(BidError::from_raw(result))
} else {
Ok(())
}
}
/// Invoke a plugin method
pub fn invoke(
&self,
type_id: u32,
method_id: u32,
instance_id: u32,
args: &[u8],
result_buffer: &mut Vec<u8>,
) -> BidResult<()> {
// First call: get required size
let mut required_size = 0;
let result = unsafe {
(self.invoke)(
type_id,
method_id,
instance_id,
args.as_ptr(),
args.len(),
std::ptr::null_mut(),
&mut required_size,
)
};
// Check for error (except buffer too small)
if result != 0 && result != -1 {
return Err(BidError::from_raw(result));
}
// Allocate buffer if needed
if required_size > 0 {
result_buffer.resize(required_size, 0);
// Second call: get actual data
let mut actual_size = required_size;
let result = unsafe {
(self.invoke)(
type_id,
method_id,
instance_id,
args.as_ptr(),
args.len(),
result_buffer.as_mut_ptr(),
&mut actual_size,
)
};
if result != 0 {
return Err(BidError::from_raw(result));
}
// Trim to actual size
result_buffer.truncate(actual_size);
}
Ok(())
}
/// Shutdown plugin
pub fn shutdown(&self) {
unsafe {
(self.shutdown)();
}
}
}
/// Helper for creating host vtable with Rust closures
pub struct HostVtableBuilder {
vtable: NyashHostVtable,
}
impl HostVtableBuilder {
pub fn new() -> Self {
Self {
vtable: NyashHostVtable::empty(),
}
}
pub fn with_alloc<F>(mut self, f: F) -> Self
where
F: Fn(usize) -> *mut std::os::raw::c_void + 'static,
{
// Note: In real implementation, would need to store the closure
// and create a proper extern "C" function. This is simplified.
self
}
pub fn with_free<F>(mut self, f: F) -> Self
where
F: Fn(*mut std::os::raw::c_void) + 'static,
{
self
}
pub fn with_log<F>(mut self, f: F) -> Self
where
F: Fn(&str) + 'static,
{
self
}
pub fn build(self) -> NyashHostVtable {
self.vtable
}
}
#[cfg(test)]
mod tests {
use super::*;
// Mock plugin functions for testing
unsafe extern "C" fn mock_abi() -> u32 {
1 // BID-1
}
unsafe extern "C" fn mock_init(
_host: *const NyashHostVtable,
info: *mut NyashPluginInfo,
) -> i32 {
if !info.is_null() {
(*info).type_id = 99;
(*info).method_count = 0;
}
0
}
unsafe extern "C" fn mock_invoke(
_type_id: u32,
_method_id: u32,
_instance_id: u32,
_args: *const u8,
_args_len: usize,
_result: *mut u8,
result_len: *mut usize,
) -> i32 {
if !result_len.is_null() {
*result_len = 0;
}
0
}
unsafe extern "C" fn mock_shutdown() {}
#[test]
fn test_plugin_handle() {
let handle = PluginHandle {
abi: mock_abi,
init: mock_init,
invoke: mock_invoke,
shutdown: mock_shutdown,
};
// Check ABI
assert!(handle.check_abi().is_ok());
// Initialize
let host = NyashHostVtable::empty();
let mut info = NyashPluginInfo::empty();
assert!(handle.initialize(&host, &mut info).is_ok());
assert_eq!(info.type_id, 99);
// Invoke
let mut result = Vec::new();
assert!(handle.invoke(99, 1, 0, &[], &mut result).is_ok());
}
}