BID-FFI integration:\n- Add plugin-tester io subcommand and TLV helpers; E2E open/write/read/close\n- Implement FileBox plugin invoke (birth/open/read/write/close) with BID-1 two-pass\n- Add BID loader/registry/plugin_box modules; prefer plugin-backed FileBox in interpreter\n- Introduce PluginFileBox with minimal read/write/close dispatch\n- Update runner debug paths; add local simple tests\n- Docs: plugin-tester guide and FileBox Nyash↔BID mapping; CURRENT_TASK updated
This commit is contained in:
@ -9,6 +9,7 @@ use libloading::{Library, Symbol};
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::os::raw::{c_char, c_void};
|
||||
use std::path::PathBuf;
|
||||
use std::io::Write;
|
||||
|
||||
// ============ FFI Types (プラグインと同じ定義) ============
|
||||
|
||||
@ -58,6 +59,11 @@ enum Commands {
|
||||
/// Path to plugin .so file
|
||||
plugin: PathBuf,
|
||||
},
|
||||
/// File I/O end-to-end test (open/write/read/close)
|
||||
Io {
|
||||
/// Path to plugin .so file
|
||||
plugin: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
// ============ Host Functions (テスト用実装) ============
|
||||
@ -107,9 +113,87 @@ fn main() {
|
||||
match args.command {
|
||||
Commands::Check { plugin } => check_plugin(&plugin),
|
||||
Commands::Lifecycle { plugin } => test_lifecycle(&plugin),
|
||||
Commands::Io { plugin } => test_file_io(&plugin),
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Minimal BID-1 TLV Helpers ============
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct TlvHeader { version: u16, argc: u16 }
|
||||
|
||||
const TLV_VERSION: u16 = 1;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum Tag { Bool=1, I32=2, I64=3, F32=4, F64=5, String=6, Bytes=7, Handle=8, Void=9 }
|
||||
|
||||
fn tlv_encode_string(s: &str, buf: &mut Vec<u8>) {
|
||||
let header_pos = buf.len();
|
||||
buf.extend_from_slice(&[0,0,0,0]);
|
||||
let mut argc: u16 = 0;
|
||||
// entry
|
||||
let bytes = s.as_bytes();
|
||||
buf.push(Tag::String as u8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(bytes);
|
||||
argc += 1;
|
||||
// write header
|
||||
buf[header_pos..header_pos+2].copy_from_slice(&TLV_VERSION.to_le_bytes());
|
||||
buf[header_pos+2..header_pos+4].copy_from_slice(&argc.to_le_bytes());
|
||||
}
|
||||
|
||||
fn tlv_encode_two_strings(a: &str, b: &str, buf: &mut Vec<u8>) {
|
||||
let header_pos = buf.len();
|
||||
buf.extend_from_slice(&[0,0,0,0]);
|
||||
let mut argc: u16 = 0;
|
||||
for s in [a,b] {
|
||||
let bytes = s.as_bytes();
|
||||
buf.push(Tag::String as u8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(bytes.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(bytes);
|
||||
argc += 1;
|
||||
}
|
||||
buf[header_pos..header_pos+2].copy_from_slice(&TLV_VERSION.to_le_bytes());
|
||||
buf[header_pos+2..header_pos+4].copy_from_slice(&argc.to_le_bytes());
|
||||
}
|
||||
|
||||
fn tlv_encode_i32(v: i32, buf: &mut Vec<u8>) {
|
||||
let header_pos = buf.len();
|
||||
buf.extend_from_slice(&[0,0,0,0]);
|
||||
buf.push(Tag::I32 as u8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&4u16.to_le_bytes());
|
||||
buf.extend_from_slice(&v.to_le_bytes());
|
||||
buf[header_pos..header_pos+2].copy_from_slice(&TLV_VERSION.to_le_bytes());
|
||||
buf[header_pos+2..header_pos+4].copy_from_slice(&1u16.to_le_bytes());
|
||||
}
|
||||
|
||||
fn tlv_encode_bytes(data: &[u8], buf: &mut Vec<u8>) {
|
||||
let header_pos = buf.len();
|
||||
buf.extend_from_slice(&[0,0,0,0]);
|
||||
buf.push(Tag::Bytes as u8);
|
||||
buf.push(0);
|
||||
buf.extend_from_slice(&(data.len() as u16).to_le_bytes());
|
||||
buf.extend_from_slice(data);
|
||||
buf[header_pos..header_pos+2].copy_from_slice(&TLV_VERSION.to_le_bytes());
|
||||
buf[header_pos+2..header_pos+4].copy_from_slice(&1u16.to_le_bytes());
|
||||
}
|
||||
|
||||
fn tlv_decode_first(bytes: &[u8]) -> Option<(u8, &[u8])> {
|
||||
if bytes.len() < 4 { return None; }
|
||||
let argc = u16::from_le_bytes([bytes[2], bytes[3]]);
|
||||
if argc == 0 { return None; }
|
||||
if bytes.len() < 8 { return None; }
|
||||
let tag = bytes[4];
|
||||
let size = u16::from_le_bytes([bytes[6], bytes[7]]) as usize;
|
||||
if bytes.len() < 8+size { return None; }
|
||||
Some((tag, &bytes[8..8+size]))
|
||||
}
|
||||
|
||||
fn check_plugin(path: &PathBuf) {
|
||||
println!("{}", "=== Nyash Plugin Checker ===".bold());
|
||||
println!("Plugin: {}", path.display());
|
||||
@ -217,7 +301,183 @@ fn check_plugin(path: &PathBuf) {
|
||||
fn test_lifecycle(path: &PathBuf) {
|
||||
println!("{}", "=== Lifecycle Test ===".bold());
|
||||
println!("Testing birth/fini for: {}", path.display());
|
||||
|
||||
// TODO: birth/finiのテスト実装
|
||||
println!("{}: Lifecycle test not yet implemented", "TODO".yellow());
|
||||
}
|
||||
|
||||
// プラグインをロード
|
||||
let library = match unsafe { Library::new(path) } {
|
||||
Ok(lib) => lib,
|
||||
Err(e) => {
|
||||
eprintln!("{}: Failed to load plugin: {}", "ERROR".red(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
// ABI version
|
||||
let abi_fn: Symbol<unsafe extern "C" fn() -> u32> = match library.get(b"nyash_plugin_abi") {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("{}: nyash_plugin_abi not found: {}", "ERROR".red(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let abi_version = abi_fn();
|
||||
println!("{}: ABI version: {}", "✓".green(), abi_version);
|
||||
if abi_version != 1 {
|
||||
eprintln!("{}: Unsupported ABI version (expected 1)", "WARNING".yellow());
|
||||
}
|
||||
|
||||
// init
|
||||
let init_fn: Symbol<unsafe extern "C" fn(*const NyashHostVtable, *mut NyashPluginInfo) -> i32> =
|
||||
match library.get(b"nyash_plugin_init") {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("{}: nyash_plugin_init not found: {}", "ERROR".red(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let mut plugin_info = std::mem::zeroed::<NyashPluginInfo>();
|
||||
let result = init_fn(&HOST_VTABLE, &mut plugin_info);
|
||||
if result != 0 {
|
||||
eprintln!("{}: nyash_plugin_init failed with code {}", "ERROR".red(), result);
|
||||
return;
|
||||
}
|
||||
println!("{}: Plugin initialized", "✓".green());
|
||||
|
||||
// invoke
|
||||
let invoke_fn: Symbol<unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32> =
|
||||
match library.get(b"nyash_plugin_invoke") {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
eprintln!("{}: nyash_plugin_invoke not found: {}", "ERROR".red(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let type_id = plugin_info.type_id;
|
||||
println!("{}: BoxType ID = {}", "i".blue(), type_id);
|
||||
|
||||
// birth
|
||||
let mut out = [0u8; 8];
|
||||
let mut out_len: usize = out.len();
|
||||
let rc = invoke_fn(type_id, 0, 0, std::ptr::null(), 0, out.as_mut_ptr(), &mut out_len as *mut usize);
|
||||
if rc != 0 {
|
||||
eprintln!("{}: birth invoke failed with code {}", "ERROR".red(), rc);
|
||||
return;
|
||||
}
|
||||
if out_len < 4 {
|
||||
eprintln!("{}: birth returned too small result ({} bytes)", "ERROR".red(), out_len);
|
||||
return;
|
||||
}
|
||||
let instance_id = u32::from_le_bytes(out[0..4].try_into().unwrap());
|
||||
println!("{}: birth → instance_id={}", "✓".green(), instance_id);
|
||||
|
||||
// fini
|
||||
let rc = invoke_fn(type_id, u32::MAX, instance_id, std::ptr::null(), 0, std::ptr::null_mut(), std::ptr::null_mut());
|
||||
if rc != 0 {
|
||||
eprintln!("{}: fini invoke failed with code {}", "ERROR".red(), rc);
|
||||
return;
|
||||
}
|
||||
println!("{}: fini → instance {} cleaned", "✓".green(), instance_id);
|
||||
|
||||
// shutdown
|
||||
if let Ok(shutdown_fn) = library.get::<Symbol<unsafe extern "C" fn()>>(b"nyash_plugin_shutdown") {
|
||||
shutdown_fn();
|
||||
println!("{}: Plugin shutdown completed", "✓".green());
|
||||
}
|
||||
}
|
||||
|
||||
println!("\n{}", "Lifecycle test completed!".green().bold());
|
||||
}
|
||||
|
||||
fn test_file_io(path: &PathBuf) {
|
||||
println!("{}", "=== File I/O Test ===".bold());
|
||||
println!("Testing open/write/read/close: {}", path.display());
|
||||
|
||||
// Load
|
||||
let library = match unsafe { Library::new(path) } {
|
||||
Ok(lib) => lib,
|
||||
Err(e) => {
|
||||
eprintln!("{}: Failed to load plugin: {}", "ERROR".red(), e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
unsafe {
|
||||
let abi: Symbol<unsafe extern "C" fn() -> u32> = library.get(b"nyash_plugin_abi").unwrap();
|
||||
println!("{}: ABI version: {}", "✓".green(), abi());
|
||||
let init: Symbol<unsafe extern "C" fn(*const NyashHostVtable, *mut NyashPluginInfo) -> i32> = library.get(b"nyash_plugin_init").unwrap();
|
||||
let mut info = std::mem::zeroed::<NyashPluginInfo>();
|
||||
assert_eq!(0, init(&HOST_VTABLE, &mut info));
|
||||
let invoke: Symbol<unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize)->i32> = library.get(b"nyash_plugin_invoke").unwrap();
|
||||
let shutdown: Symbol<unsafe extern "C" fn()> = library.get(b"nyash_plugin_shutdown").unwrap();
|
||||
|
||||
// birth
|
||||
let mut buf_len: usize = 0;
|
||||
let rc = invoke(info.type_id, 0, 0, std::ptr::null(), 0, std::ptr::null_mut(), &mut buf_len as *mut usize);
|
||||
assert!(rc == -1 && buf_len >= 4, "unexpected birth preflight");
|
||||
let mut out = vec![0u8; buf_len];
|
||||
let mut out_len = buf_len;
|
||||
assert_eq!(0, invoke(info.type_id, 0, 0, std::ptr::null(), 0, out.as_mut_ptr(), &mut out_len as *mut usize));
|
||||
let instance_id = u32::from_le_bytes(out[0..4].try_into().unwrap());
|
||||
println!("{}: birth → instance_id={}", "✓".green(), instance_id);
|
||||
|
||||
// open: write mode
|
||||
let mut args = Vec::new();
|
||||
let test_path = "plugins/nyash-filebox-plugin/target/test_io.txt";
|
||||
tlv_encode_two_strings(test_path, "w", &mut args);
|
||||
let mut res_len: usize = 0;
|
||||
let rc = invoke(info.type_id, 1, instance_id, args.as_ptr(), args.len(), std::ptr::null_mut(), &mut res_len as *mut usize);
|
||||
assert!(rc == -1 || rc == 0);
|
||||
let mut res = vec![0u8; res_len.max(4)];
|
||||
let mut rl = res_len;
|
||||
let _ = invoke(info.type_id, 1, instance_id, args.as_ptr(), args.len(), res.as_mut_ptr(), &mut rl as *mut usize);
|
||||
println!("{}: open(w)", "✓".green());
|
||||
|
||||
// write
|
||||
let content = b"Hello from plugin-tester!";
|
||||
let mut wargs = Vec::new();
|
||||
tlv_encode_bytes(content, &mut wargs);
|
||||
let mut rlen: usize = 0;
|
||||
let rc = invoke(info.type_id, 3, instance_id, wargs.as_ptr(), wargs.len(), std::ptr::null_mut(), &mut rlen as *mut usize);
|
||||
assert!(rc == -1 || rc == 0);
|
||||
let mut wb = vec![0u8; rlen.max(8)];
|
||||
let mut rl2 = rlen;
|
||||
let _ = invoke(info.type_id, 3, instance_id, wargs.as_ptr(), wargs.len(), wb.as_mut_ptr(), &mut rl2 as *mut usize);
|
||||
if let Some((tag, payload)) = tlv_decode_first(&wb[..rl2]) {
|
||||
assert_eq!(tag, Tag::I32 as u8);
|
||||
let mut n = [0u8;4]; n.copy_from_slice(payload);
|
||||
let written = i32::from_le_bytes(n);
|
||||
println!("{}: write {} bytes", "✓".green(), written);
|
||||
}
|
||||
|
||||
// close
|
||||
let mut clen: usize = 0;
|
||||
let _ = invoke(info.type_id, 4, instance_id, std::ptr::null(), 0, std::ptr::null_mut(), &mut clen as *mut usize);
|
||||
let mut cb = vec![0u8; clen.max(4)]; let mut cbl = clen; let _ = invoke(info.type_id, 4, instance_id, std::ptr::null(), 0, cb.as_mut_ptr(), &mut cbl as *mut usize);
|
||||
println!("{}: close", "✓".green());
|
||||
|
||||
// reopen read
|
||||
let mut args2 = Vec::new(); tlv_encode_two_strings(test_path, "r", &mut args2);
|
||||
let mut r0: usize = 0; let _ = invoke(info.type_id, 1, instance_id, args2.as_ptr(), args2.len(), std::ptr::null_mut(), &mut r0 as *mut usize);
|
||||
let mut ob = vec![0u8; r0.max(4)]; let mut obl=r0; let _=invoke(info.type_id,1,instance_id,args2.as_ptr(),args2.len(),ob.as_mut_ptr(),&mut obl as *mut usize);
|
||||
println!("{}: open(r)", "✓".green());
|
||||
|
||||
// read 1024
|
||||
let mut rargs = Vec::new(); tlv_encode_i32(1024, &mut rargs);
|
||||
let mut rneed: usize = 0; let rc = invoke(info.type_id, 2, instance_id, rargs.as_ptr(), rargs.len(), std::ptr::null_mut(), &mut rneed as *mut usize);
|
||||
assert!(rc == -1 || rc == 0);
|
||||
let mut rb = vec![0u8; rneed.max(16)]; let mut rbl=rneed; let rc2=invoke(info.type_id,2,instance_id,rargs.as_ptr(),rargs.len(),rb.as_mut_ptr(),&mut rbl as *mut usize);
|
||||
if rc2 != 0 { println!("{}: read rc={} (expected 0)", "WARN".yellow(), rc2); }
|
||||
if let Some((tag, payload)) = tlv_decode_first(&rb[..rbl]) {
|
||||
assert_eq!(tag, Tag::Bytes as u8);
|
||||
let s = String::from_utf8_lossy(payload).to_string();
|
||||
println!("{}: read {} bytes → '{}'", "✓".green(), payload.len(), s);
|
||||
} else {
|
||||
println!("{}: read decode failed (len={})", "WARN".yellow(), rbl);
|
||||
}
|
||||
|
||||
// close & shutdown
|
||||
let mut clen2: usize = 0; let _=invoke(info.type_id,4,instance_id,std::ptr::null(),0,std::ptr::null_mut(),&mut clen2 as *mut usize);
|
||||
shutdown();
|
||||
println!("\n{}", "File I/O test completed!".green().bold());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user