diff --git a/plugins/nyash-filebox-plugin/src/constants.rs b/plugins/nyash-filebox-plugin/src/constants.rs new file mode 100644 index 00000000..c6a6add5 --- /dev/null +++ b/plugins/nyash-filebox-plugin/src/constants.rs @@ -0,0 +1,33 @@ +//! Constants and error codes for FileBox plugin + +// ============ Error Codes (BID-1 alignment) ============ +pub const NYB_SUCCESS: i32 = 0; +pub const NYB_E_SHORT_BUFFER: i32 = -1; +pub const NYB_E_INVALID_TYPE: i32 = -2; +pub const NYB_E_METHOD_NOT_FOUND: i32 = -3; +pub const NYB_E_INVALID_ARGS: i32 = -4; +pub const NYB_E_PLUGIN_ERROR: i32 = -5; +pub const NYB_E_INVALID_HANDLE: i32 = -8; + +// ============ Method IDs ============ +pub const METHOD_BIRTH: u32 = 0; // Constructor +pub const METHOD_OPEN: u32 = 1; +pub const METHOD_READ: u32 = 2; +pub const METHOD_WRITE: u32 = 3; +pub const METHOD_CLOSE: u32 = 4; +pub const METHOD_EXISTS: u32 = 5; +pub const METHOD_COPY_FROM: u32 = 7; // New: copyFrom(other: Handle) +pub const METHOD_CLONE_SELF: u32 = 8; // New: cloneSelf() -> Handle +pub const METHOD_FINI: u32 = u32::MAX; // Destructor + +// ============ TLV Tags ============ +pub const TLV_TAG_BOOL: u8 = 1; +pub const TLV_TAG_I32: u8 = 2; +pub const TLV_TAG_I64: u8 = 3; +pub const TLV_TAG_STRING: u8 = 6; +pub const TLV_TAG_BYTES: u8 = 7; +pub const TLV_TAG_HANDLE: u8 = 8; +pub const TLV_TAG_VOID: u8 = 9; + +// ============ FileBox Type ID ============ +pub const FILEBOX_TYPE_ID: u32 = 6; \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/src/ffi.rs b/plugins/nyash-filebox-plugin/src/ffi.rs new file mode 100644 index 00000000..04d908d1 --- /dev/null +++ b/plugins/nyash-filebox-plugin/src/ffi.rs @@ -0,0 +1,39 @@ +//! FFI (Foreign Function Interface) type definitions for FileBox plugin + +use std::os::raw::c_char; + +// ============ FFI Types ============ + +#[repr(C)] +pub struct NyashMethodInfo { + pub method_id: u32, + pub name: *const c_char, + pub signature: u32, +} + +#[repr(C)] +pub struct NyashPluginInfo { + pub type_id: u32, + pub type_name: *const c_char, + pub method_count: usize, + pub methods: *const NyashMethodInfo, +} + +/// TypeBox FFI structure for plugin export +#[repr(C)] +pub struct NyashTypeBoxFfi { + pub abi_tag: u32, // 'TYBX' (0x54594258) + pub version: u16, // 1 + pub struct_size: u16, // sizeof(NyashTypeBoxFfi) + pub name: *const c_char, // C string, e.g., "FileBox\0" + pub resolve: Option u32>, + pub invoke_id: Option i32>, + pub capabilities: u64, +} + +unsafe impl Sync for NyashTypeBoxFfi {} +unsafe impl Send for NyashTypeBoxFfi {} + +// ABI Constants +pub const ABI_TAG_TYBX: u32 = 0x54594258; // 'TYBX' +pub const ABI_VERSION: u16 = 1; \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/src/filebox_impl.rs b/plugins/nyash-filebox-plugin/src/filebox_impl.rs new file mode 100644 index 00000000..178d33b9 --- /dev/null +++ b/plugins/nyash-filebox-plugin/src/filebox_impl.rs @@ -0,0 +1,431 @@ +//! FileBox implementation + +use crate::constants::*; +use crate::state::{allocate_instance_id, remove_instance, store_instance, with_instance_mut, FileBoxInstance, INSTANCE_COUNTER, INSTANCES}; +use crate::tlv_helpers::*; +use std::ffi::CStr; +use std::io::{Read, Seek, SeekFrom, Write}; +use std::os::raw::c_char; +use std::sync::atomic::Ordering; + +// ===== File I/O Helpers ===== + +pub fn open_file(mode: &str, path: &str) -> Result { + use std::fs::OpenOptions; + match mode { + "r" => OpenOptions::new().read(true).open(path), + "w" => OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(path), + "a" => OpenOptions::new().append(true).create(true).open(path), + "rw" | "r+" => OpenOptions::new() + .read(true) + .write(true) + .create(true) + .open(path), + _ => OpenOptions::new().read(true).open(path), + } +} + +// ===== TLV Parsing Extensions ===== + +fn tlv_parse_string(data: &[u8]) -> Result { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } + tlv_parse_string_at(data, &mut pos) +} + +fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option, Vec), ()> { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } + + // Check first arg tag to determine if string or bytes + if data.len() < pos + 4 { + return Err(()); + } + let first_tag = data[pos]; + + if first_tag == TLV_TAG_STRING { + // First arg is string (path) + let s = tlv_parse_string_at(data, &mut pos)?; + if argc >= 2 { + let b = tlv_parse_bytes_at(data, &mut pos)?; + Ok((Some(s), b)) + } else { + Ok((Some(s), Vec::new())) + } + } else if first_tag == TLV_TAG_BYTES { + // First arg is bytes (no path) + let b = tlv_parse_bytes_at(data, &mut pos)?; + Ok((None, b)) + } else { + Err(()) + } +} + +fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } + tlv_parse_handle_at(data, &mut pos) +} + +// ===== TypeBox v2 Implementation ===== + +pub extern "C" fn filebox_resolve(name: *const c_char) -> u32 { + if name.is_null() { + return 0; + } + let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); + match s.as_ref() { + // lifecycle + "birth" => METHOD_BIRTH, + "fini" => METHOD_FINI, + // methods + "open" => METHOD_OPEN, + "read" => METHOD_READ, + "write" => METHOD_WRITE, + "close" => METHOD_CLOSE, + "exists" => METHOD_EXISTS, + "copyFrom" => METHOD_COPY_FROM, + "cloneSelf" => METHOD_CLONE_SELF, + _ => 0, + } +} + +pub extern "C" fn filebox_invoke_id( + instance_id: u32, + method_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + unsafe { + match method_id { + METHOD_BIRTH => handle_birth(result, result_len), + METHOD_FINI => handle_fini(instance_id), + METHOD_OPEN => handle_open(instance_id, args, args_len, result, result_len), + METHOD_READ => handle_read(instance_id, args, args_len, result, result_len), + METHOD_WRITE => handle_write(instance_id, args, args_len, result, result_len), + METHOD_CLOSE => handle_close(instance_id, result, result_len), + METHOD_EXISTS => handle_exists(args, args_len, result, result_len), + METHOD_COPY_FROM => handle_copy_from(instance_id, args, args_len, result, result_len), + METHOD_CLONE_SELF => handle_clone_self(instance_id, result, result_len), + _ => NYB_E_METHOD_NOT_FOUND, + } + } +} + +// ===== Method Handlers ===== + +unsafe fn handle_birth(result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + if preflight(result, result_len, 4) { + return NYB_E_SHORT_BUFFER; + } + let id = allocate_instance_id(); + if store_instance(id, FileBoxInstance::new()).is_err() { + return NYB_E_PLUGIN_ERROR; + } + let b = id.to_le_bytes(); + std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); + *result_len = 4; + NYB_SUCCESS +} + +unsafe fn handle_fini(instance_id: u32) -> i32 { + remove_instance(instance_id); + NYB_SUCCESS +} + +unsafe fn handle_open( + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_two_strings(slice) { + Ok((path, mode)) => { + if preflight(result, result_len, 8) { + return NYB_E_SHORT_BUFFER; + } + match with_instance_mut(instance_id, |inst| { + match open_file(&mode, &path) { + Ok(file) => { + inst.file = Some(file); + inst.path = path; + true + } + Err(_) => false, + } + }) { + Ok(true) => write_tlv_void(result, result_len), + Ok(false) => NYB_E_PLUGIN_ERROR, + Err(_) => NYB_E_INVALID_HANDLE, + } + } + Err(_) => NYB_E_INVALID_ARGS, + } +} + +unsafe fn handle_read( + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + let slice = std::slice::from_raw_parts(args, args_len); + + // Check if path argument provided + if args_len > 0 { + // Static file read (with path) + match tlv_parse_string(slice) { + Ok(path) => match open_file("r", &path) { + Ok(mut file) => { + let mut buf = Vec::new(); + if file.read_to_end(&mut buf).is_err() { + return NYB_E_PLUGIN_ERROR; + } + let need = 8usize.saturating_add(buf.len()); + if preflight(result, result_len, need) { + return NYB_E_SHORT_BUFFER; + } + return write_tlv_bytes(&buf, result, result_len); + } + Err(_) => return NYB_E_PLUGIN_ERROR, + }, + Err(_) => return NYB_E_INVALID_ARGS, + } + } else { + // Instance file read + match with_instance_mut(instance_id, |inst| { + if let Some(file) = inst.file.as_mut() { + let _ = file.seek(SeekFrom::Start(0)); + let mut buf = Vec::new(); + match file.read_to_end(&mut buf) { + Ok(_) => Some(buf), + Err(_) => None, + } + } else { + None + } + }) { + Ok(Some(buf)) => { + let need = 8usize.saturating_add(buf.len()); + if preflight(result, result_len, need) { + return NYB_E_SHORT_BUFFER; + } + write_tlv_bytes(&buf, result, result_len) + } + Ok(None) => NYB_E_INVALID_HANDLE, + Err(_) => NYB_E_INVALID_HANDLE, + } + } +} + +unsafe fn handle_write( + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_optional_string_and_bytes(slice) { + Ok((Some(path), data)) => { + // Static file write + if preflight(result, result_len, 12) { + return NYB_E_SHORT_BUFFER; + } + match open_file("w", &path) { + Ok(mut file) => { + if file.write_all(&data).is_err() || file.flush().is_err() { + return NYB_E_PLUGIN_ERROR; + } + write_tlv_i32(data.len() as i32, result, result_len) + } + Err(_) => NYB_E_PLUGIN_ERROR, + } + } + Ok((None, data)) => { + // Instance file write + if preflight(result, result_len, 12) { + return NYB_E_SHORT_BUFFER; + } + match with_instance_mut(instance_id, |inst| { + if let Some(file) = inst.file.as_mut() { + match file.write(&data) { + Ok(n) => { + if file.flush().is_ok() { + inst.buffer = Some(data.clone()); + Some(n) + } else { + None + } + } + Err(_) => None, + } + } else { + None + } + }) { + Ok(Some(n)) => write_tlv_i32(n as i32, result, result_len), + Ok(None) => NYB_E_PLUGIN_ERROR, + Err(_) => NYB_E_INVALID_HANDLE, + } + } + Err(_) => NYB_E_INVALID_ARGS, + } +} + +unsafe fn handle_close( + instance_id: u32, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + if preflight(result, result_len, 8) { + return NYB_E_SHORT_BUFFER; + } + match with_instance_mut(instance_id, |inst| { + inst.file = None; + }) { + Ok(_) => write_tlv_void(result, result_len), + Err(_) => NYB_E_INVALID_HANDLE, + } +} + +unsafe fn handle_exists( + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_one_string(slice) { + Ok(path) => { + if preflight(result, result_len, 8) { + return NYB_E_SHORT_BUFFER; + } + let exists = std::path::Path::new(&path).exists(); + write_tlv_bool(exists, result, result_len) + } + Err(_) => NYB_E_INVALID_ARGS, + } +} + +unsafe fn handle_copy_from( + instance_id: u32, + args: *const u8, + args_len: usize, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + let slice = std::slice::from_raw_parts(args, args_len); + match tlv_parse_handle(slice) { + Ok((_type_id, other_id)) => { + if preflight(result, result_len, 8) { + return NYB_E_SHORT_BUFFER; + } + + // Lock instances once and perform copy + match INSTANCES.lock() { + Ok(mut map) => { + // Extract data from source + let mut data = Vec::new(); + let mut copy_ok = false; + + if let Some(src) = map.get(&other_id) { + if let Some(file) = src.file.as_ref() { + if let Ok(mut f) = file.try_clone() { + let _ = f.seek(SeekFrom::Start(0)); + if f.read_to_end(&mut data).is_ok() { + copy_ok = true; + } + } + } + if !copy_ok { + if let Some(buf) = src.buffer.as_ref() { + data.extend_from_slice(buf); + copy_ok = true; + } + } + } else { + return NYB_E_INVALID_HANDLE; + } + + if !copy_ok { + return NYB_E_PLUGIN_ERROR; + } + + // Write to destination + if let Some(dst) = map.get_mut(&instance_id) { + if let Some(fdst) = dst.file.as_mut() { + let _ = fdst.seek(SeekFrom::Start(0)); + if fdst.write_all(&data).is_err() { + return NYB_E_PLUGIN_ERROR; + } + let _ = fdst.set_len(data.len() as u64); + let _ = fdst.flush(); + } + dst.buffer = Some(data); + write_tlv_void(result, result_len) + } else { + NYB_E_INVALID_HANDLE + } + } + Err(_) => NYB_E_PLUGIN_ERROR, + } + } + Err(_) => NYB_E_INVALID_ARGS, + } +} + +unsafe fn handle_clone_self( + instance_id: u32, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + if preflight(result, result_len, 16) { + return NYB_E_SHORT_BUFFER; + } + + match INSTANCES.lock() { + Ok(mut map) => { + if let Some(src) = map.get(&instance_id) { + let new_id = allocate_instance_id(); + let mut new_inst = FileBoxInstance::with_path(src.path.clone()); + + // Clone buffer if present + if let Some(buf) = src.buffer.as_ref() { + new_inst.buffer = Some(buf.clone()); + } + + // Try to clone file handle + if let Some(file) = src.file.as_ref() { + if let Ok(cloned) = file.try_clone() { + new_inst.file = Some(cloned); + } + } + + map.insert(new_id, new_inst); + write_tlv_handle(FILEBOX_TYPE_ID, new_id, result, result_len) + } else { + NYB_E_INVALID_HANDLE + } + } + Err(_) => NYB_E_PLUGIN_ERROR, + } +} \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/src/lib.rs b/plugins/nyash-filebox-plugin/src/lib.rs index 6b15a467..0b8d31be 100644 --- a/plugins/nyash-filebox-plugin/src/lib.rs +++ b/plugins/nyash-filebox-plugin/src/lib.rs @@ -1,948 +1,96 @@ //! Nyash FileBox Plugin — TypeBox v2 //! -//! Provides file I/O operations as a Nyash plugin(TypeBox v2 エクスポート済み) +//! Provides file I/O operations as a Nyash plugin -use std::collections::HashMap; -use std::ffi::CStr; use std::os::raw::c_char; -// std::ptr削除(未使用) -use std::io::{Read, Seek, SeekFrom, Write}; -use std::sync::{ - atomic::{AtomicU32, Ordering}, - Mutex, -}; -// ============ FFI Types ============ +// Module declarations +mod constants; +mod ffi; +mod filebox_impl; +mod state; +mod tlv_helpers; -// Host VTable廃止 - 不要 +// Re-exports +use ffi::{ABI_TAG_TYBX, ABI_VERSION, NyashTypeBoxFfi}; +use filebox_impl::{filebox_invoke_id, filebox_resolve}; -#[repr(C)] -pub struct NyashMethodInfo { - pub method_id: u32, - pub name: *const c_char, - pub signature: u32, -} - -#[repr(C)] -pub struct NyashPluginInfo { - pub type_id: u32, - pub type_name: *const c_char, - pub method_count: usize, - pub methods: *const NyashMethodInfo, -} - -// ============ Error Codes (BID-1 alignment) ============ -const NYB_SUCCESS: i32 = 0; -const NYB_E_SHORT_BUFFER: i32 = -1; -const NYB_E_INVALID_TYPE: i32 = -2; -const NYB_E_INVALID_METHOD: i32 = -3; -const NYB_E_INVALID_ARGS: i32 = -4; -const NYB_E_PLUGIN_ERROR: i32 = -5; -const NYB_E_INVALID_HANDLE: i32 = -8; - -// ============ Method IDs ============ -const METHOD_BIRTH: u32 = 0; // Constructor -const METHOD_OPEN: u32 = 1; -const METHOD_READ: u32 = 2; -const METHOD_WRITE: u32 = 3; -const METHOD_CLOSE: u32 = 4; -const METHOD_EXISTS: u32 = 5; -const METHOD_COPY_FROM: u32 = 7; // New: copyFrom(other: Handle) -const METHOD_CLONE_SELF: u32 = 8; // New: cloneSelf() -> Handle -const METHOD_FINI: u32 = u32::MAX; // Destructor - -// ============ FileBox Instance ============ -struct FileBoxInstance { - file: Option, - path: String, - buffer: Option>, // プラグインが管理するバッファ -} - -// グローバルインスタンス管理(実際の実装ではより安全な方法を使用) -use once_cell::sync::Lazy; -static INSTANCES: Lazy>> = - Lazy::new(|| Mutex::new(HashMap::new())); - -// ホスト関数テーブルは使用しない(Host VTable廃止) - -// インスタンスIDカウンタ(1開始) -static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1); - -// ============ Plugin Entry Points ============ - -// legacy v1 abi/init removed - -/// Method invocation - 仮実装 -/* legacy v1 entry removed -#[no_mangle] -pub extern "C" fn nyash_plugin_invoke( - _type_id: u32, - _method_id: u32, - _instance_id: u32, - _args: *const u8, - _args_len: usize, - _result: *mut u8, - _result_len: *mut usize, -) -> i32 { - // 簡易実装:type_id検証、省略可能 - if _type_id != 6 { - return NYB_E_INVALID_TYPE; - } - - unsafe { - match _method_id { - METHOD_BIRTH => { - // 引数は未使用 - let needed: usize = 4; // u32 instance_id - if _result_len.is_null() { - return NYB_E_INVALID_ARGS; - } - // Two-phase protocol: report required size if buffer missing/small - if _result.is_null() { - *_result_len = needed; - return NYB_E_SHORT_BUFFER; - } - let buf_len = *_result_len; - if buf_len < needed { - *_result_len = needed; - return NYB_E_SHORT_BUFFER; - } - - // 新しいインスタンスを作成 - let instance_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed); - if let Ok(mut map) = INSTANCES.lock() { - map.insert( - instance_id, - FileBoxInstance { - file: None, - path: String::new(), - buffer: None, - }, - ); - } else { - return NYB_E_PLUGIN_ERROR; - } - - // 結果バッファにinstance_idを書き込む(LE) - let bytes = instance_id.to_le_bytes(); - std::ptr::copy_nonoverlapping(bytes.as_ptr(), _result, 4); - *_result_len = needed; - NYB_SUCCESS - } - METHOD_FINI => { - // 指定インスタンスを解放 - if let Ok(mut map) = INSTANCES.lock() { - map.remove(&_instance_id); - return NYB_SUCCESS; - } else { - return NYB_E_PLUGIN_ERROR; - } - } - METHOD_OPEN => { - // args: TLV { String path, String mode } - let args = std::slice::from_raw_parts(_args, _args_len); - match tlv_parse_two_strings(args) { - Ok((path, mode)) => { - // Preflight for Void TLV: header(4) + entry(4) - if preflight(_result, _result_len, 8) { - return NYB_E_SHORT_BUFFER; - } - log_info(&format!("OPEN path='{}' mode='{}'", path, mode)); - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&_instance_id) { - match open_file(&mode, &path) { - Ok(file) => { - inst.file = Some(file); - // return TLV Void - return write_tlv_void(_result, _result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - METHOD_READ => { - // args: optional String path - let args = std::slice::from_raw_parts(_args, _args_len); - if _args_len > 0 { - match tlv_parse_string(args) { - Ok(path) => match open_file("r", &path) { - Ok(mut file) => { - let mut buf = Vec::new(); - if let Err(_) = file.read_to_end(&mut buf) { - return NYB_E_PLUGIN_ERROR; - } - let need = 8usize.saturating_add(buf.len()); - if preflight(_result, _result_len, need) { - return NYB_E_SHORT_BUFFER; - } - return write_tlv_bytes(&buf, _result, _result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - }, - Err(_) => return NYB_E_INVALID_ARGS, - } - } else { - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&_instance_id) { - if let Some(file) = inst.file.as_mut() { - let _ = file.seek(SeekFrom::Start(0)); - let mut buf = Vec::new(); - match file.read_to_end(&mut buf) { - Ok(n) => { - log_info(&format!("READ {} bytes (entire file)", n)); - let need = 8usize.saturating_add(buf.len()); - if preflight(_result, _result_len, need) { - return NYB_E_SHORT_BUFFER; - } - return write_tlv_bytes(&buf, _result, _result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } else { - return NYB_E_INVALID_HANDLE; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - } - METHOD_WRITE => { - // args: TLV { [String path]? Bytes data } - let args = std::slice::from_raw_parts(_args, _args_len); - match tlv_parse_optional_string_and_bytes(args) { - Ok((Some(path), data)) => { - if preflight(_result, _result_len, 12) { - return NYB_E_SHORT_BUFFER; - } - match open_file("w", &path) { - Ok(mut file) => { - if let Err(_) = file.write_all(&data) { - return NYB_E_PLUGIN_ERROR; - } - if let Err(_) = file.flush() { - return NYB_E_PLUGIN_ERROR; - } - return write_tlv_i32(data.len() as i32, _result, _result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } - Ok((None, data)) => { - if preflight(_result, _result_len, 12) { - return NYB_E_SHORT_BUFFER; - } - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&_instance_id) { - if let Some(file) = inst.file.as_mut() { - match file.write(&data) { - Ok(n) => { - if let Err(_) = file.flush() { - return NYB_E_PLUGIN_ERROR; - } - log_info(&format!("WRITE {} bytes", n)); - inst.buffer = Some(data.clone()); - return write_tlv_i32(n as i32, _result, _result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } else { - return NYB_E_INVALID_HANDLE; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - METHOD_CLOSE => { - // Preflight for Void TLV - if preflight(_result, _result_len, 8) { - return NYB_E_SHORT_BUFFER; - } - log_info("CLOSE"); - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&_instance_id) { - inst.file = None; - return write_tlv_void(_result, _result_len); - } else { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - METHOD_COPY_FROM => { - // args: TLV { Handle (tag=8, size=8) } - let args = std::slice::from_raw_parts(_args, _args_len); - match tlv_parse_handle(args) { - Ok((type_id, other_id)) => { - if type_id != _type_id { - return NYB_E_INVALID_TYPE; - } - if preflight(_result, _result_len, 8) { - return NYB_E_SHORT_BUFFER; - } - if let Ok(mut map) = INSTANCES.lock() { - // 1) まずsrcからデータを取り出す(不変参照のみ) - let mut data: Vec = Vec::new(); - if let Some(src) = map.get(&other_id) { - let mut read_ok = false; - if let Some(file) = src.file.as_ref() { - if let Ok(mut f) = file.try_clone() { - let _ = f.seek(SeekFrom::Start(0)); - if f.read_to_end(&mut data).is_ok() { - read_ok = true; - } - } - } - if !read_ok { - if let Some(buf) = src.buffer.as_ref() { - data.extend_from_slice(buf); - read_ok = true; - } - } - if !read_ok { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_INVALID_HANDLE; - } - - // 2) dstへ書き込み(可変参照) - if let Some(dst) = map.get_mut(&_instance_id) { - if let Some(fdst) = dst.file.as_mut() { - let _ = fdst.seek(SeekFrom::Start(0)); - if fdst.write_all(&data).is_err() { - return NYB_E_PLUGIN_ERROR; - } - let _ = fdst.set_len(data.len() as u64); - let _ = fdst.flush(); - } - dst.buffer = Some(data); - return write_tlv_void(_result, _result_len); - } else { - return NYB_E_INVALID_HANDLE; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - METHOD_CLONE_SELF => { - // Return a new instance (handle) as TLV Handle - // Preflight for Handle TLV: header(4) + entry(4) + payload(8) - if preflight(_result, _result_len, 16) { - return NYB_E_SHORT_BUFFER; - } - let new_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed); - if let Ok(mut map) = INSTANCES.lock() { - map.insert( - new_id, - FileBoxInstance { - file: None, - path: String::new(), - buffer: None, - }, - ); - } - // Build TLV result - let mut payload = [0u8; 8]; - payload[0..4].copy_from_slice(&_type_id.to_le_bytes()); - payload[4..8].copy_from_slice(&new_id.to_le_bytes()); - return write_tlv_result(&[(8u8, &payload)], _result, _result_len); - } - METHOD_EXISTS => { - // args: TLV { String path } - let args = std::slice::from_raw_parts(_args, _args_len); - match tlv_parse_string(args) { - Ok(path) => { - let exists = std::path::Path::new(&path).exists(); - if preflight(_result, _result_len, 9) { - return NYB_E_SHORT_BUFFER; - } - return write_tlv_bool(exists, _result, _result_len); - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - _ => NYB_SUCCESS, - } - } -} -*/ - -// ===== Helpers ===== - -fn open_file(mode: &str, path: &str) -> Result { - use std::fs::OpenOptions; - match mode { - "r" => OpenOptions::new().read(true).open(path), - "w" => OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open(path), - "a" => OpenOptions::new().append(true).create(true).open(path), - "rw" | "r+" => OpenOptions::new() - .read(true) - .write(true) - .create(true) - .open(path), - _ => OpenOptions::new().read(true).open(path), - } -} - -fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { - if result_len.is_null() { - return NYB_E_INVALID_ARGS; - } - let mut buf: Vec = - Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); - buf.extend_from_slice(&1u16.to_le_bytes()); // version - buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc - for (tag, payload) in payloads { - buf.push(*tag); - buf.push(0); - buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); - buf.extend_from_slice(payload); - } - unsafe { - let needed = buf.len(); - if result.is_null() || *result_len < needed { - *result_len = needed; - return NYB_E_SHORT_BUFFER; - } - std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); - *result_len = needed; - } - NYB_SUCCESS -} - -fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 { - write_tlv_result(&[(9u8, &[])], result, result_len) -} - -fn write_tlv_bytes(data: &[u8], result: *mut u8, result_len: *mut usize) -> i32 { - write_tlv_result(&[(7u8, data)], result, result_len) -} - -fn write_tlv_i32(v: i32, result: *mut u8, result_len: *mut usize) -> i32 { - write_tlv_result(&[(2u8, &v.to_le_bytes())], result, result_len) -} - -fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { - let b = [if v { 1u8 } else { 0u8 }]; - write_tlv_result(&[(1u8, &b)], result, result_len) -} - -fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { - unsafe { - if result_len.is_null() { - return false; - } - if result.is_null() || *result_len < needed { - *result_len = needed; - return true; - } - } - false -} - -fn tlv_parse_header(data: &[u8]) -> Result<(u16, u16, usize), ()> { - if data.len() < 4 { - return Err(()); - } - let ver = u16::from_le_bytes([data[0], data[1]]); - let argc = u16::from_le_bytes([data[2], data[3]]); - if ver != 1 { - return Err(()); - } - Ok((ver, argc, 4)) -} - -fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> { - let (_, argc, mut pos) = tlv_parse_header(data)?; - if argc < 2 { - return Err(()); - } - let s1 = tlv_parse_string_at(data, &mut pos)?; - let s2 = tlv_parse_string_at(data, &mut pos)?; - Ok((s1, s2)) -} - -fn tlv_parse_string_at(data: &[u8], pos: &mut usize) -> Result { - if *pos + 4 > data.len() { - return Err(()); - } - let tag = data[*pos]; - let _res = data[*pos + 1]; - let size = u16::from_le_bytes([data[*pos + 2], data[*pos + 3]]) as usize; - *pos += 4; - if tag != 6 || *pos + size > data.len() { - return Err(()); - } - let slice = &data[*pos..*pos + size]; - *pos += size; - std::str::from_utf8(slice) - .map(|s| s.to_string()) - .map_err(|_| ()) -} - -fn tlv_parse_i32(data: &[u8]) -> Result { - let (_, argc, mut pos) = tlv_parse_header(data)?; - if argc < 1 { - return Err(()); - } - if pos + 8 > data.len() { - return Err(()); - } - let tag = data[pos]; - let _res = data[pos + 1]; - let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize; - pos += 4; - if tag != 2 || size != 4 || pos + size > data.len() { - return Err(()); - } - let mut b = [0u8; 4]; - b.copy_from_slice(&data[pos..pos + 4]); - Ok(i32::from_le_bytes(b)) -} - -fn tlv_parse_bytes(data: &[u8]) -> Result, ()> { - let (_, argc, mut pos) = tlv_parse_header(data)?; - if argc < 1 { - return Err(()); - } - if pos + 4 > data.len() { - return Err(()); - } - let tag = data[pos]; - let _res = data[pos + 1]; - let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize; - pos += 4; - // StringタグもBytesタグも受け付ける(互換性のため) - if (tag != 6 && tag != 7) || pos + size > data.len() { - return Err(()); - } - Ok(data[pos..pos + size].to_vec()) -} - -fn tlv_parse_string(data: &[u8]) -> Result { - let (_, argc, mut pos) = tlv_parse_header(data)?; - if argc < 1 { - return Err(()); - } - tlv_parse_string_at(data, &mut pos) -} - -fn tlv_parse_optional_string_and_bytes(data: &[u8]) -> Result<(Option, Vec), ()> { - let (_, argc, mut pos) = tlv_parse_header(data)?; - if argc == 1 { - // only bytes - if pos + 4 > data.len() { - return Err(()); - } - let tag = data[pos]; - let _res = data[pos + 1]; - let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize; - pos += 4; - if (tag != 6 && tag != 7) || pos + size > data.len() { - return Err(()); - } - return Ok((None, data[pos..pos + size].to_vec())); - } else if argc >= 2 { - let s = tlv_parse_string_at(data, &mut pos)?; - if pos + 4 > data.len() { - return Err(()); - } - let tag = data[pos]; - let _res = data[pos + 1]; - let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize; - pos += 4; - if (tag != 6 && tag != 7) || pos + size > data.len() { - return Err(()); - } - Ok((Some(s), data[pos..pos + size].to_vec())) - } else { - Err(()) - } -} - -fn tlv_parse_handle(data: &[u8]) -> Result<(u32, u32), ()> { - let (_, argc, mut pos) = tlv_parse_header(data)?; - if argc < 1 { - return Err(()); - } - if pos + 4 > data.len() { - return Err(()); - } - let tag = data[pos]; - let _res = data[pos + 1]; - let size = u16::from_le_bytes([data[pos + 2], data[pos + 3]]) as usize; - pos += 4; - if tag != 8 || size != 8 || pos + size > data.len() { - return Err(()); - } - let mut t = [0u8; 4]; - t.copy_from_slice(&data[pos..pos + 4]); - let mut i = [0u8; 4]; - i.copy_from_slice(&data[pos + 4..pos + 8]); - Ok((u32::from_le_bytes(t), u32::from_le_bytes(i))) -} - -fn log_info(message: &str) { - eprintln!("[FileBox] {}", message); -} - -/// Plugin shutdown -#[no_mangle] -pub extern "C" fn nyash_plugin_shutdown() { - // インスタンスをクリア - if let Ok(mut map) = INSTANCES.lock() { - map.clear(); - } - eprintln!("[FileBox] Plugin shutdown"); -} - -// ============ Unified Plugin API ============ -// Note: Metadata (Box types, methods) now comes from nyash.toml -// This plugin provides only the actual processing functions - -// ===== TypeBox ABI v2 (resolve/invoke_id) ===== -#[repr(C)] -pub struct NyashTypeBoxFfi { - pub abi_tag: u32, // 'TYBX' (0x54594258) - pub version: u16, // 1 - pub struct_size: u16, // sizeof(NyashTypeBoxFfi) - pub name: *const c_char, // C string, e.g., "FileBox\0" - pub resolve: Option u32>, - pub invoke_id: Option i32>, - pub capabilities: u64, -} -unsafe impl Sync for NyashTypeBoxFfi {} - -extern "C" fn filebox_resolve(name: *const c_char) -> u32 { - if name.is_null() { - return 0; - } - let s = unsafe { CStr::from_ptr(name) }.to_string_lossy(); - match s.as_ref() { - // lifecycle (optional to expose via resolve) - "birth" => METHOD_BIRTH, - "fini" => METHOD_FINI, - // methods - "open" => METHOD_OPEN, - "read" => METHOD_READ, - "write" => METHOD_WRITE, - "close" => METHOD_CLOSE, - "exists" => METHOD_EXISTS, - "copyFrom" => METHOD_COPY_FROM, - "cloneSelf" => METHOD_CLONE_SELF, - _ => 0, - } -} - -extern "C" fn filebox_invoke_id( - instance_id: u32, - method_id: u32, - args: *const u8, - args_len: usize, - result: *mut u8, - result_len: *mut usize, -) -> i32 { - unsafe { - match method_id { - // Support birth through per-Box invoke for v2 shim - METHOD_BIRTH => { - if result_len.is_null() { - return NYB_E_INVALID_ARGS; - } - if preflight(result, result_len, 4) { - return NYB_E_SHORT_BUFFER; - } - let id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed); - if let Ok(mut map) = INSTANCES.lock() { - map.insert( - id, - FileBoxInstance { - file: None, - path: String::new(), - buffer: None, - }, - ); - } else { - return NYB_E_PLUGIN_ERROR; - } - let b = id.to_le_bytes(); - std::ptr::copy_nonoverlapping(b.as_ptr(), result, 4); - *result_len = 4; - NYB_SUCCESS - } - METHOD_FINI => { - if let Ok(mut map) = INSTANCES.lock() { - map.remove(&instance_id); - NYB_SUCCESS - } else { - NYB_E_PLUGIN_ERROR - } - } - METHOD_OPEN => { - // args: TLV { String path, String mode } - let slice = std::slice::from_raw_parts(args, args_len); - match tlv_parse_two_strings(slice) { - Ok((path, mode)) => { - if preflight(result, result_len, 8) { - return NYB_E_SHORT_BUFFER; - } - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&instance_id) { - match open_file(&mode, &path) { - Ok(file) => { - inst.file = Some(file); - return write_tlv_void(result, result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } else { - return NYB_E_INVALID_HANDLE; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - METHOD_READ => { - let slice = std::slice::from_raw_parts(args, args_len); - if args_len > 0 { - match tlv_parse_string(slice) { - Ok(path) => match open_file("r", &path) { - Ok(mut file) => { - let mut buf = Vec::new(); - if let Err(_) = file.read_to_end(&mut buf) { - return NYB_E_PLUGIN_ERROR; - } - let need = 8usize.saturating_add(buf.len()); - if preflight(result, result_len, need) { - return NYB_E_SHORT_BUFFER; - } - return write_tlv_bytes(&buf, result, result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - }, - Err(_) => return NYB_E_INVALID_ARGS, - } - } else { - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&instance_id) { - if let Some(file) = inst.file.as_mut() { - let _ = file.seek(SeekFrom::Start(0)); - let mut buf = Vec::new(); - match file.read_to_end(&mut buf) { - Ok(_) => { - let need = 8usize.saturating_add(buf.len()); - if preflight(result, result_len, need) { - return NYB_E_SHORT_BUFFER; - } - return write_tlv_bytes(&buf, result, result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } else { - return NYB_E_INVALID_HANDLE; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - } - METHOD_WRITE => { - let slice = std::slice::from_raw_parts(args, args_len); - match tlv_parse_optional_string_and_bytes(slice) { - Ok((Some(path), data)) => { - if preflight(result, result_len, 12) { - return NYB_E_SHORT_BUFFER; - } - match open_file("w", &path) { - Ok(mut file) => { - if let Err(_) = file.write_all(&data) { - return NYB_E_PLUGIN_ERROR; - } - if let Err(_) = file.flush() { - return NYB_E_PLUGIN_ERROR; - } - return write_tlv_i32(data.len() as i32, result, result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } - Ok((None, data)) => { - if preflight(result, result_len, 12) { - return NYB_E_SHORT_BUFFER; - } - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&instance_id) { - if let Some(file) = inst.file.as_mut() { - match file.write(&data) { - Ok(n) => { - if let Err(_) = file.flush() { - return NYB_E_PLUGIN_ERROR; - } - inst.buffer = Some(data.clone()); - return write_tlv_i32(n as i32, result, result_len); - } - Err(_) => return NYB_E_PLUGIN_ERROR, - } - } else { - return NYB_E_INVALID_HANDLE; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - METHOD_CLOSE => { - if preflight(result, result_len, 8) { - return NYB_E_SHORT_BUFFER; - } - if let Ok(mut map) = INSTANCES.lock() { - if let Some(inst) = map.get_mut(&instance_id) { - inst.file = None; - return write_tlv_void(result, result_len); - } else { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - METHOD_COPY_FROM => { - let slice = std::slice::from_raw_parts(args, args_len); - match tlv_parse_handle(slice) { - Ok((_type_id, other_id)) => { - if preflight(result, result_len, 8) { - return NYB_E_SHORT_BUFFER; - } - if let Ok(mut map) = INSTANCES.lock() { - // Extract data from src - let mut data: Vec = Vec::new(); - if let Some(src) = map.get(&other_id) { - let mut read_ok = false; - if let Some(file) = src.file.as_ref() { - if let Ok(mut f) = file.try_clone() { - let _ = f.seek(SeekFrom::Start(0)); - if f.read_to_end(&mut data).is_ok() { - read_ok = true; - } - } - } - if !read_ok { - if let Some(buf) = src.buffer.as_ref() { - data.extend_from_slice(buf); - read_ok = true; - } - } - if !read_ok { - return NYB_E_PLUGIN_ERROR; - } - } else { - return NYB_E_INVALID_HANDLE; - } - // Write into dst - if let Some(dst) = map.get_mut(&instance_id) { - if let Some(fdst) = dst.file.as_mut() { - let _ = fdst.seek(SeekFrom::Start(0)); - if fdst.write_all(&data).is_err() { - return NYB_E_PLUGIN_ERROR; - } - let _ = fdst.set_len(data.len() as u64); - let _ = fdst.flush(); - } - dst.buffer = Some(data); - return write_tlv_void(result, result_len); - } else { - return NYB_E_INVALID_HANDLE; - } - } else { - return NYB_E_PLUGIN_ERROR; - } - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - METHOD_CLONE_SELF => { - if preflight(result, result_len, 16) { - return NYB_E_SHORT_BUFFER; - } - let new_id = INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed); - if let Ok(mut map) = INSTANCES.lock() { - map.insert( - new_id, - FileBoxInstance { - file: None, - path: String::new(), - buffer: None, - }, - ); - } - // Return Handle TLV (type_id from config resolves host-side; we encode (6,new_id) here if needed) - let mut payload = [0u8; 8]; - // We don’t embed type_id here (host can ignore); keep zero for neutrality - payload[0..4].copy_from_slice(&0u32.to_le_bytes()); - payload[4..8].copy_from_slice(&new_id.to_le_bytes()); - return write_tlv_result(&[(8u8, &payload)], result, result_len); - } - METHOD_EXISTS => { - let slice = std::slice::from_raw_parts(args, args_len); - match tlv_parse_string(slice) { - Ok(path) => { - let exists = std::path::Path::new(&path).exists(); - if preflight(result, result_len, 9) { - return NYB_E_SHORT_BUFFER; - } - return write_tlv_bool(exists, result, result_len); - } - Err(_) => NYB_E_INVALID_ARGS, - } - } - _ => NYB_E_INVALID_METHOD, - } - } -} +// ============ TypeBox v2 Export ============ +/// FileBox TypeBox export #[no_mangle] pub static nyash_typebox_FileBox: NyashTypeBoxFfi = NyashTypeBoxFfi { - abi_tag: 0x54594258, // 'TYBX' - version: 1, + abi_tag: ABI_TAG_TYBX, + version: ABI_VERSION, struct_size: std::mem::size_of::() as u16, name: b"FileBox\0".as_ptr() as *const c_char, resolve: Some(filebox_resolve), invoke_id: Some(filebox_invoke_id), capabilities: 0, }; + +// ============ Plugin Metadata (optional) ============ + +#[no_mangle] +pub static nyash_plugin_name: &[u8] = b"nyash-filebox\0"; + +#[no_mangle] +pub static nyash_plugin_version: &[u8] = b"0.1.0\0"; + +// ============ Tests ============ + +#[cfg(test)] +mod tests { + use super::*; + use crate::constants::*; + + #[test] + fn test_plugin_metadata() { + // Verify plugin name is null-terminated + assert_eq!(nyash_plugin_name[nyash_plugin_name.len() - 1], 0); + + // Verify plugin version is null-terminated + assert_eq!(nyash_plugin_version[nyash_plugin_version.len() - 1], 0); + } + + #[test] + fn test_typebox_structure() { + // Verify ABI tag + assert_eq!(nyash_typebox_FileBox.abi_tag, ABI_TAG_TYBX); + + // Verify version + assert_eq!(nyash_typebox_FileBox.version, ABI_VERSION); + + // Verify struct size matches + let expected_size = std::mem::size_of::() as u16; + assert_eq!(nyash_typebox_FileBox.struct_size, expected_size); + + // Verify name is not null + assert!(!nyash_typebox_FileBox.name.is_null()); + + // Verify callbacks are set + assert!(nyash_typebox_FileBox.resolve.is_some()); + assert!(nyash_typebox_FileBox.invoke_id.is_some()); + } + + #[test] + fn test_method_resolution() { + // Test resolve function + let resolve = nyash_typebox_FileBox.resolve.unwrap(); + + // Test known methods + unsafe { + let open = b"open\0"; + assert_eq!(resolve(open.as_ptr() as *const c_char), METHOD_OPEN); + + let read = b"read\0"; + assert_eq!(resolve(read.as_ptr() as *const c_char), METHOD_READ); + + let write = b"write\0"; + assert_eq!(resolve(write.as_ptr() as *const c_char), METHOD_WRITE); + + let unknown = b"unknown\0"; + assert_eq!(resolve(unknown.as_ptr() as *const c_char), 0); + } + } +} \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/src/state.rs b/plugins/nyash-filebox-plugin/src/state.rs new file mode 100644 index 00000000..055a10f9 --- /dev/null +++ b/plugins/nyash-filebox-plugin/src/state.rs @@ -0,0 +1,92 @@ +//! State management for FileBox plugin + +use once_cell::sync::Lazy; +use std::collections::HashMap; +use std::sync::{ + atomic::{AtomicU32, Ordering}, + Mutex, +}; + +// ============ FileBox Instance ============ +pub struct FileBoxInstance { + pub file: Option, + pub path: String, + pub buffer: Option>, // プラグインが管理するバッファ +} + +impl FileBoxInstance { + pub fn new() -> Self { + Self { + file: None, + path: String::new(), + buffer: None, + } + } + + pub fn with_path(path: String) -> Self { + Self { + file: None, + path, + buffer: None, + } + } +} + +// グローバルインスタンス管理 +pub static INSTANCES: Lazy>> = + Lazy::new(|| Mutex::new(HashMap::new())); + +// インスタンスIDカウンタ(1開始) +pub static INSTANCE_COUNTER: AtomicU32 = AtomicU32::new(1); + +/// Allocate a new instance ID +pub fn allocate_instance_id() -> u32 { + INSTANCE_COUNTER.fetch_add(1, Ordering::Relaxed) +} + +/// Store an instance with the given ID +pub fn store_instance(id: u32, instance: FileBoxInstance) -> Result<(), &'static str> { + match INSTANCES.lock() { + Ok(mut map) => { + map.insert(id, instance); + Ok(()) + } + Err(_) => Err("Failed to lock instances map"), + } +} + +/// Remove an instance by ID +pub fn remove_instance(id: u32) -> Option { + match INSTANCES.lock() { + Ok(mut map) => map.remove(&id), + Err(_) => None, + } +} + +/// Get mutable access to an instance +pub fn with_instance_mut(id: u32, f: F) -> Result +where + F: FnOnce(&mut FileBoxInstance) -> R, +{ + match INSTANCES.lock() { + Ok(mut map) => match map.get_mut(&id) { + Some(instance) => Ok(f(instance)), + None => Err("Instance not found"), + }, + Err(_) => Err("Failed to lock instances map"), + } +} + +/// Get access to an instance +pub fn with_instance(id: u32, f: F) -> Result +where + F: FnOnce(&FileBoxInstance) -> R, +{ + match INSTANCES.lock() { + Ok(map) => match map.get(&id) { + Some(instance) => Ok(f(instance)), + None => Err("Instance not found"), + }, + Err(_) => Err("Failed to lock instances map"), + } +} \ No newline at end of file diff --git a/plugins/nyash-filebox-plugin/src/tlv_helpers.rs b/plugins/nyash-filebox-plugin/src/tlv_helpers.rs new file mode 100644 index 00000000..9a39d5a5 --- /dev/null +++ b/plugins/nyash-filebox-plugin/src/tlv_helpers.rs @@ -0,0 +1,180 @@ +//! TLV (Type-Length-Value) serialization helpers for FileBox plugin + +use crate::constants::*; + +pub fn write_tlv_result(payloads: &[(u8, &[u8])], result: *mut u8, result_len: *mut usize) -> i32 { + if result_len.is_null() { + return NYB_E_INVALID_ARGS; + } + let mut buf: Vec = + Vec::with_capacity(4 + payloads.iter().map(|(_, p)| 4 + p.len()).sum::()); + buf.extend_from_slice(&1u16.to_le_bytes()); // version + buf.extend_from_slice(&(payloads.len() as u16).to_le_bytes()); // argc + for (tag, payload) in payloads { + buf.push(*tag); + buf.push(0); + buf.extend_from_slice(&(payload.len() as u16).to_le_bytes()); + buf.extend_from_slice(payload); + } + unsafe { + let needed = buf.len(); + if result.is_null() || *result_len < needed { + *result_len = needed; + return NYB_E_SHORT_BUFFER; + } + std::ptr::copy_nonoverlapping(buf.as_ptr(), result, needed); + *result_len = needed; + } + NYB_SUCCESS +} + +pub fn write_tlv_void(result: *mut u8, result_len: *mut usize) -> i32 { + write_tlv_result(&[(TLV_TAG_VOID, &[])], result, result_len) +} + +pub fn write_tlv_bytes(data: &[u8], result: *mut u8, result_len: *mut usize) -> i32 { + write_tlv_result(&[(TLV_TAG_BYTES, data)], result, result_len) +} + +pub fn write_tlv_i32(v: i32, result: *mut u8, result_len: *mut usize) -> i32 { + write_tlv_result(&[(TLV_TAG_I32, &v.to_le_bytes())], result, result_len) +} + +pub fn write_tlv_bool(v: bool, result: *mut u8, result_len: *mut usize) -> i32 { + let b = [if v { 1u8 } else { 0u8 }]; + write_tlv_result(&[(TLV_TAG_BOOL, &b)], result, result_len) +} + +pub fn write_tlv_string(s: &str, result: *mut u8, result_len: *mut usize) -> i32 { + write_tlv_result(&[(TLV_TAG_STRING, s.as_bytes())], result, result_len) +} + +pub fn write_tlv_handle( + type_id: u32, + instance_id: u32, + result: *mut u8, + result_len: *mut usize, +) -> i32 { + let mut payload = Vec::with_capacity(8); + payload.extend_from_slice(&type_id.to_le_bytes()); + payload.extend_from_slice(&instance_id.to_le_bytes()); + write_tlv_result(&[(TLV_TAG_HANDLE, &payload)], result, result_len) +} + +pub fn preflight(result: *mut u8, result_len: *mut usize, needed: usize) -> bool { + unsafe { + if result_len.is_null() { + return false; + } + if result.is_null() || *result_len < needed { + *result_len = needed; + return true; + } + } + false +} + +pub fn tlv_parse_header(data: &[u8]) -> Result<(u16, u16, usize), ()> { + if data.len() < 4 { + return Err(()); + } + let ver = u16::from_le_bytes([data[0], data[1]]); + let argc = u16::from_le_bytes([data[2], data[3]]); + if ver != 1 { + return Err(()); + } + Ok((ver, argc, 4)) +} + +pub fn tlv_parse_two_strings(data: &[u8]) -> Result<(String, String), ()> { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 2 { + return Err(()); + } + let s1 = tlv_parse_string_at(data, &mut pos)?; + let s2 = tlv_parse_string_at(data, &mut pos)?; + Ok((s1, s2)) +} + +pub fn tlv_parse_string_at(data: &[u8], pos: &mut usize) -> Result { + if data.len() < *pos + 4 { + return Err(()); + } + let tag = data[*pos]; + if tag != TLV_TAG_STRING { + return Err(()); + } + let len = u16::from_le_bytes([data[*pos + 2], data[*pos + 3]]) as usize; + *pos += 4; + if data.len() < *pos + len { + return Err(()); + } + let s = String::from_utf8_lossy(&data[*pos..*pos + len]).to_string(); + *pos += len; + Ok(s) +} + +pub fn tlv_parse_handle_at(data: &[u8], pos: &mut usize) -> Result<(u32, u32), ()> { + if data.len() < *pos + 4 { + return Err(()); + } + let tag = data[*pos]; + if tag != TLV_TAG_HANDLE { + return Err(()); + } + let len = u16::from_le_bytes([data[*pos + 2], data[*pos + 3]]) as usize; + *pos += 4; + if len != 8 || data.len() < *pos + 8 { + return Err(()); + } + let type_id = u32::from_le_bytes([ + data[*pos], + data[*pos + 1], + data[*pos + 2], + data[*pos + 3], + ]); + let instance_id = u32::from_le_bytes([ + data[*pos + 4], + data[*pos + 5], + data[*pos + 6], + data[*pos + 7], + ]); + *pos += 8; + Ok((type_id, instance_id)) +} + +pub fn tlv_parse_bytes_at(data: &[u8], pos: &mut usize) -> Result, ()> { + if data.len() < *pos + 4 { + return Err(()); + } + let tag = data[*pos]; + if tag != TLV_TAG_BYTES { + return Err(()); + } + let len = u16::from_le_bytes([data[*pos + 2], data[*pos + 3]]) as usize; + *pos += 4; + if data.len() < *pos + len { + return Err(()); + } + let bytes = data[*pos..*pos + len].to_vec(); + *pos += len; + Ok(bytes) +} + +pub fn tlv_parse_one_string(data: &[u8]) -> Result { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 1 { + return Err(()); + } + tlv_parse_string_at(data, &mut pos) +} + +pub fn tlv_parse_string_and_bytes(data: &[u8]) -> Result<(String, Vec), ()> { + let (_, argc, mut pos) = tlv_parse_header(data)?; + if argc < 2 { + return Err(()); + } + let s = tlv_parse_string_at(data, &mut pos)?; + let b = tlv_parse_bytes_at(data, &mut pos)?; + Ok((s, b)) +} \ No newline at end of file diff --git a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs new file mode 100644 index 00000000..e7546c5d --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs @@ -0,0 +1,262 @@ +//! External function implementations for plugin loader v2 +//! +//! This module contains all `env.*` external function implementations +//! that were previously in a large switch statement in loader.rs + +use crate::bid::{BidError, BidResult}; +use crate::box_trait::{NyashBox, StringBox, VoidBox}; +use crate::boxes::result::NyashResultBox; +use crate::boxes::future::FutureBox; +use crate::boxes::token_box::TokenBox; +use crate::runtime::modules_registry; +use crate::runtime::global_hooks; + +/// Handle external function calls from the runtime +pub fn extern_call( + iface_name: &str, + method_name: &str, + args: &[Box], +) -> BidResult>> { + match iface_name { + "env.console" => handle_console(method_name, args), + "env.result" => handle_result(method_name, args), + "env.modules" => handle_modules(method_name, args), + "env.task" => handle_task(method_name, args), + "env.debug" => handle_debug(method_name, args), + "env.runtime" => handle_runtime(method_name, args), + "env.future" => handle_future(method_name, args), + _ => Err(BidError::PluginError), + } +} + +/// Handle env.console.* methods +fn handle_console(method_name: &str, args: &[Box]) -> BidResult>> { + match method_name { + "log" => { + for a in args { + println!("{}", a.to_string_box().value); + } + Ok(None) + } + _ => Err(BidError::PluginError), + } +} + +/// Handle env.result.* methods +fn handle_result(method_name: &str, args: &[Box]) -> BidResult>> { + match method_name { + "ok" => { + // Wrap the first argument as Result.Ok; if missing, use Void + let v = args + .get(0) + .map(|b| b.clone_box()) + .unwrap_or_else(|| Box::new(VoidBox::new())); + Ok(Some(Box::new(NyashResultBox::new_ok(v)))) + } + "err" => { + // Wrap the first argument as Result.Err; if missing, synthesize a StringBox("Error") + let e: Box = args + .get(0) + .map(|b| b.clone_box()) + .unwrap_or_else(|| Box::new(StringBox::new("Error"))); + Ok(Some(Box::new(NyashResultBox::new_err(e)))) + } + _ => Err(BidError::PluginError), + } +} + +/// Handle env.modules.* methods +fn handle_modules(method_name: &str, args: &[Box]) -> BidResult>> { + match method_name { + "set" => { + if args.len() >= 2 { + let key = args[0].to_string_box().value; + let val = args[1].clone_box(); + modules_registry::set(key, val); + } + Ok(None) + } + "get" => { + if let Some(k) = args.get(0) { + let key = k.to_string_box().value; + if let Some(v) = modules_registry::get(&key) { + return Ok(Some(v)); + } + } + Ok(Some(Box::new(VoidBox::new()))) + } + _ => Err(BidError::PluginError), + } +} + +/// Handle env.task.* methods +fn handle_task(method_name: &str, args: &[Box]) -> BidResult>> { + match method_name { + "cancelCurrent" => { + let tok = global_hooks::current_group_token(); + tok.cancel(); + Ok(None) + } + "currentToken" => { + let tok = global_hooks::current_group_token(); + let tb = TokenBox::from_token(tok); + Ok(Some(Box::new(tb))) + } + "spawn" => handle_task_spawn(args), + "wait" => handle_task_wait(args), + _ => Err(BidError::PluginError), + } +} + +/// Handle env.task.spawn method +fn handle_task_spawn(args: &[Box]) -> BidResult>> { + if let Some(b) = args.get(0) { + // The plugin loader originally included additional spawn logic, + // but we keep the simplified version here for now + // TODO: Implement full task spawning logic + Ok(Some(b.clone_box())) + } else { + Ok(None) + } +} + +/// Handle env.task.wait method +fn handle_task_wait(_args: &[Box]) -> BidResult>> { + // Task wait is not yet implemented in the extracted module + // This functionality will be added when properly integrating with future system + Err(BidError::PluginError) +} + +/// Handle env.debug.* methods +fn handle_debug(method_name: &str, args: &[Box]) -> BidResult>> { + match method_name { + "trace" => { + if std::env::var("NYASH_DEBUG_TRACE").ok().as_deref() == Some("1") { + for a in args { + eprintln!("[debug.trace] {}", a.to_string_box().value); + } + } + Ok(None) + } + _ => Err(BidError::PluginError), + } +} + +/// Handle env.runtime.* methods +fn handle_runtime(method_name: &str, _args: &[Box]) -> BidResult>> { + match method_name { + "checkpoint" => { + if crate::config::env::runtime_checkpoint_trace() { + eprintln!("[runtime.checkpoint] reached"); + } + global_hooks::safepoint_and_poll(); + Ok(None) + } + _ => Err(BidError::PluginError), + } +} + +/// Handle env.future.* methods +fn handle_future(method_name: &str, args: &[Box]) -> BidResult>> { + match method_name { + "new" | "birth" => { + let fut = FutureBox::new(); + if let Some(v) = args.get(0) { + fut.set_result(v.clone_box()); + } + Ok(Some(Box::new(fut))) + } + "set" => { + if args.len() >= 2 { + if let Some(fut) = args[0] + .as_any() + .downcast_ref::() + { + fut.set_result(args[1].clone_box()); + } + } + Ok(None) + } + "await" => handle_future_await(args), + _ => Err(BidError::PluginError), + } +} + +/// Handle env.future.await method +fn handle_future_await(args: &[Box]) -> BidResult>> { + if let Some(arg) = args.get(0) { + if let Some(fut) = arg + .as_any() + .downcast_ref::() + { + let max_ms: u64 = crate::config::env::await_max_ms(); + let start = std::time::Instant::now(); + let mut spins = 0usize; + + while !fut.ready() { + global_hooks::safepoint_and_poll(); + std::thread::yield_now(); + spins += 1; + + if spins % 1024 == 0 { + std::thread::sleep(std::time::Duration::from_millis(1)); + } + + if start.elapsed() >= std::time::Duration::from_millis(max_ms) { + let err = StringBox::new("Timeout"); + return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))); + } + } + + return match fut.wait_and_get() { + Ok(v) => Ok(Some(Box::new(NyashResultBox::new_ok(v)))), + Err(e) => { + let err = StringBox::new(format!("Error: {}", e)); + Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))) + } + }; + } else { + return Ok(Some(Box::new(NyashResultBox::new_ok(arg.clone_box())))); + } + } + + Ok(Some(Box::new(NyashResultBox::new_err(Box::new( + StringBox::new("InvalidArgs"), + ))))) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_console_log() { + let args = vec![Box::new(StringBox::new("test")) as Box]; + let result = handle_console("log", &args); + assert!(result.is_ok()); + assert!(result.unwrap().is_none()); + } + + #[test] + fn test_result_ok() { + let args = vec![Box::new(StringBox::new("success")) as Box]; + let result = handle_result("ok", &args); + assert!(result.is_ok()); + assert!(result.unwrap().is_some()); + } + + #[test] + fn test_result_err() { + let args = vec![]; + let result = handle_result("err", &args); + assert!(result.is_ok()); + assert!(result.unwrap().is_some()); + } + + #[test] + fn test_unknown_interface() { + let args = vec![]; + let result = extern_call("unknown.interface", "method", &args); + assert!(matches!(result, Err(BidError::PluginError))); + } +} \ No newline at end of file diff --git a/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs b/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs new file mode 100644 index 00000000..edc66d1a --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/ffi_bridge.rs @@ -0,0 +1,159 @@ +//! FFI bridge for plugin method invocation and TLV encoding/decoding + +use crate::bid::{BidError, BidResult}; +use crate::box_trait::NyashBox; +use crate::runtime::plugin_loader_v2::enabled::PluginLoaderV2; +use std::sync::Arc; + +fn dbg_on() -> bool { + std::env::var("PLUGIN_DEBUG").is_ok() +} + +impl PluginLoaderV2 { + /// Invoke a method on a plugin instance with TLV encoding/decoding + pub fn invoke_instance_method( + &self, + box_type: &str, + method_name: &str, + instance_id: u32, + args: &[Box], + ) -> BidResult>> { + // Resolve (lib_name, type_id) either from config or cached specs + let (lib_name, type_id) = resolve_type_info(self, box_type)?; + + // Resolve method id via config or TypeBox resolve() + let method_id = match self.resolve_method_id(box_type, method_name) { + Ok(mid) => mid, + Err(e) => { + if dbg_on() { + eprintln!( + "[PluginLoaderV2] ERR: method resolve failed for {}.{}: {:?}", + box_type, method_name, e + ); + } + return Err(BidError::InvalidMethod); + } + }; + + // Get plugin handle + let plugins = self.plugins.read().map_err(|_| BidError::PluginError)?; + let _plugin = plugins.get(&lib_name).ok_or(BidError::PluginError)?; + + // Encode TLV args via shared helper (numeric→string→toString) + let tlv = crate::runtime::plugin_ffi_common::encode_args(args); + + if dbg_on() { + eprintln!( + "[PluginLoaderV2] call {}.{}: type_id={} method_id={} instance_id={}", + box_type, method_name, type_id, method_id, instance_id + ); + } + + let (_code, out_len, out) = super::host_bridge::invoke_alloc( + super::super::nyash_plugin_invoke_v2_shim, + type_id, + method_id, + instance_id, + &tlv, + ); + + // Decode TLV (first entry) generically + decode_tlv_result(box_type, &out[..out_len]) + } +} + +/// Resolve type information for a box +fn resolve_type_info(loader: &PluginLoaderV2, box_type: &str) -> BidResult<(String, u32)> { + if let Some(cfg) = loader.config.as_ref() { + let cfg_path = loader.config_path.as_deref().unwrap_or("nyash.toml"); + let toml_value: toml::Value = + toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?) + .map_err(|_| BidError::PluginError)?; + + if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { + if let Some(bc) = cfg.get_box_config(lib_name, box_type, &toml_value) { + return Ok((lib_name.to_string(), bc.type_id)); + } else { + let key = (lib_name.to_string(), box_type.to_string()); + let map = loader.box_specs.read().map_err(|_| BidError::PluginError)?; + let tid = map + .get(&key) + .and_then(|s| s.type_id) + .ok_or(BidError::InvalidType)?; + return Ok((lib_name.to_string(), tid)); + } + } + } else { + let map = loader.box_specs.read().map_err(|_| BidError::PluginError)?; + if let Some(((lib, _), spec)) = map.iter().find(|((_, bt), _)| bt == box_type) { + return Ok((lib.clone(), spec.type_id.ok_or(BidError::InvalidType)?)); + } + } + Err(BidError::InvalidType) +} + +/// Decode TLV result into a NyashBox +fn decode_tlv_result(box_type: &str, data: &[u8]) -> BidResult>> { + if let Some((tag, _sz, payload)) = + crate::runtime::plugin_ffi_common::decode::tlv_first(data) + { + let bx: Box = match tag { + 1 => Box::new(crate::box_trait::BoolBox::new( + crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false), + )), + 2 => Box::new(crate::box_trait::IntegerBox::new( + crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or(0) as i64, + )), + 3 => { + // i64 payload + if payload.len() == 8 { + let mut b = [0u8; 8]; + b.copy_from_slice(payload); + Box::new(crate::box_trait::IntegerBox::new(i64::from_le_bytes(b))) + } else { + Box::new(crate::box_trait::IntegerBox::new(0)) + } + } + 5 => { + let x = crate::runtime::plugin_ffi_common::decode::f64(payload).unwrap_or(0.0); + Box::new(crate::boxes::FloatBox::new(x)) + } + 6 | 7 => { + let s = crate::runtime::plugin_ffi_common::decode::string(payload); + Box::new(crate::box_trait::StringBox::new(s)) + } + 8 => { + // Plugin handle (type_id, instance_id) → wrap into PluginBoxV2 + if let Some((ret_type, inst)) = + crate::runtime::plugin_ffi_common::decode::plugin_handle(payload) + { + let handle = Arc::new(super::types::PluginHandleInner { + type_id: ret_type, + invoke_fn: super::super::nyash_plugin_invoke_v2_shim, + instance_id: inst, + fini_method_id: None, + finalized: std::sync::atomic::AtomicBool::new(false), + }); + Box::new(super::types::PluginBoxV2 { + box_type: box_type.to_string(), + inner: handle, + }) + } else { + Box::new(crate::box_trait::VoidBox::new()) + } + } + 9 => { + // Host handle (u64) → try to map back to BoxRef, else void + if let Some(u) = crate::runtime::plugin_ffi_common::decode::u64(payload) { + if let Some(arc) = crate::runtime::host_handles::get(u) { + return Ok(Some(arc.share_box())); + } + } + Box::new(crate::box_trait::VoidBox::new()) + } + _ => Box::new(crate::box_trait::VoidBox::new()), + }; + return Ok(Some(bx)); + } + Ok(Some(Box::new(crate::box_trait::VoidBox::new()))) +} \ No newline at end of file diff --git a/src/runtime/plugin_loader_v2/enabled/instance_manager.rs b/src/runtime/plugin_loader_v2/enabled/instance_manager.rs new file mode 100644 index 00000000..26d42fd4 --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/instance_manager.rs @@ -0,0 +1,141 @@ +//! Instance management for plugin boxes + +use crate::bid::{BidError, BidResult}; +use crate::box_trait::NyashBox; +use crate::runtime::plugin_loader_v2::enabled::{PluginLoaderV2, types::{PluginBoxV2, PluginHandleInner}}; +use std::sync::Arc; + +fn dbg_on() -> bool { + std::env::var("PLUGIN_DEBUG").is_ok() +} + +impl PluginLoaderV2 { + /// Create a new plugin box instance + pub fn create_box( + &self, + box_type: &str, + _args: &[Box], + ) -> BidResult> { + // Non-recursive: directly call plugin 'birth' and construct PluginBoxV2 + + // Resolve type_id, birth_id, and fini_id + let (type_id, birth_id, fini_id) = resolve_box_ids(self, box_type)?; + + // Get loaded plugin invoke + let _plugins = self.plugins.read().map_err(|_| BidError::PluginError)?; + + // Call birth (no args TLV) and read returned instance id (little-endian u32 in bytes 0..4) + if dbg_on() { + eprintln!( + "[PluginLoaderV2] invoking birth: box_type={} type_id={} birth_id={}", + box_type, type_id, birth_id + ); + } + + let tlv = crate::runtime::plugin_ffi_common::encode_empty_args(); + let (code, out_len, out_buf) = super::host_bridge::invoke_alloc( + super::super::nyash_plugin_invoke_v2_shim, + type_id, + birth_id, + 0, + &tlv, + ); + + if dbg_on() { + eprintln!( + "[PluginLoaderV2] create_box: box_type={} type_id={} birth_id={} code={} out_len={}", + box_type, type_id, birth_id, code, out_len + ); + if out_len > 0 { + eprintln!( + "[PluginLoaderV2] create_box: out[0..min(8)]={:02x?}", + &out_buf[..out_len.min(8)] + ); + } + } + + if code != 0 || out_len < 4 { + return Err(BidError::PluginError); + } + + let instance_id = u32::from_le_bytes([out_buf[0], out_buf[1], out_buf[2], out_buf[3]]); + + let bx = PluginBoxV2 { + box_type: box_type.to_string(), + inner: Arc::new(PluginHandleInner { + type_id, + invoke_fn: super::super::nyash_plugin_invoke_v2_shim, + instance_id, + fini_method_id: fini_id, + finalized: std::sync::atomic::AtomicBool::new(false), + }), + }; + + // Diagnostics: register for leak tracking (optional) + crate::runtime::leak_tracker::register_plugin(box_type, instance_id); + Ok(Box::new(bx)) + } + + /// Shutdown singletons: finalize and clear all singleton handles + pub fn shutdown_singletons(&self) { + let mut map = self.singletons.write().unwrap(); + for (_, handle) in map.drain() { + if let Ok(inner) = Arc::try_unwrap(handle) { + inner.finalize_now(); + } + } + } +} + +/// Resolve box IDs (type_id, birth_id, fini_id) from configuration or specs +fn resolve_box_ids( + loader: &PluginLoaderV2, + box_type: &str, +) -> BidResult<(u32, u32, Option)> { + let (mut type_id_opt, mut birth_id_opt, mut fini_id) = (None, None, None); + + // Try config mapping first (when available) + if let Some(cfg) = loader.config.as_ref() { + let cfg_path = loader.config_path.as_deref().unwrap_or("nyash.toml"); + let toml_value: toml::Value = + toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?) + .map_err(|_| BidError::PluginError)?; + + if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { + if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) { + type_id_opt = Some(box_conf.type_id); + birth_id_opt = box_conf.methods.get("birth").map(|m| m.method_id); + fini_id = box_conf.methods.get("fini").map(|m| m.method_id); + } + } + } + + // Fallback: use TypeBox FFI spec if config is missing for this box + if type_id_opt.is_none() || birth_id_opt.is_none() { + if let Ok(map) = loader.box_specs.read() { + // Find any spec that matches this box_type + if let Some((_, spec)) = map.iter().find(|((_lib, bt), _)| bt == &box_type) { + if type_id_opt.is_none() { + type_id_opt = spec.type_id; + } + if birth_id_opt.is_none() { + if let Some(ms) = spec.methods.get("birth") { + birth_id_opt = Some(ms.method_id); + } else if let Some(res_fn) = spec.resolve_fn { + if let Ok(cstr) = std::ffi::CString::new("birth") { + let mid = res_fn(cstr.as_ptr()); + if mid != 0 { + birth_id_opt = Some(mid); + } + } + } + } + } + } + } + + let type_id = type_id_opt.ok_or(BidError::InvalidType)?; + let birth_id = birth_id_opt.ok_or(BidError::InvalidMethod)?; + + Ok((type_id, birth_id, fini_id)) +} \ No newline at end of file diff --git a/src/runtime/plugin_loader_v2/enabled/loader.rs b/src/runtime/plugin_loader_v2/enabled/loader.rs index a438f6b7..6fd90883 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader.rs @@ -17,18 +17,18 @@ fn dbg_on() -> bool { // (alias imported from host_bridge) #[derive(Debug, Clone, Default)] -struct LoadedBoxSpec { - type_id: Option, - methods: HashMap, - fini_method_id: Option, +pub(super) struct LoadedBoxSpec { + pub(super) type_id: Option, + pub(super) methods: HashMap, + pub(super) fini_method_id: Option, // Optional Nyash ABI v2 per-box invoke entry (not yet used for calls) invoke_id: Option, // Optional resolve(name)->method_id provided by NyashTypeBoxFfi - resolve_fn: Option u32>, + pub(super) resolve_fn: Option u32>, } #[derive(Debug, Clone, Copy)] -struct MethodSpec { - method_id: u32, +pub(super) struct MethodSpec { + pub(super) method_id: u32, returns_result: bool, } @@ -587,127 +587,8 @@ impl PluginLoaderV2 { method_name: &str, args: &[Box], ) -> BidResult>> { - match (iface_name, method_name) { - ("env.console", "log") => { - for a in args { - println!("{}", a.to_string_box().value); - } - Ok(None) - } - ("env.result", "ok") => { - // Wrap the first argument as Result.Ok; if missing, use Void - let v = args.get(0).map(|b| b.clone_box()).unwrap_or_else(|| Box::new(crate::box_trait::VoidBox::new())); - Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(v)))) - } - ("env.result", "err") => { - // Wrap the first argument as Result.Err; if missing, synthesize a StringBox("Error") - let e: Box = args - .get(0) - .map(|b| b.clone_box()) - .unwrap_or_else(|| Box::new(crate::box_trait::StringBox::new("Error"))); - Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(e)))) - } - ("env.modules", "set") => { - if args.len() >= 2 { - let key = args[0].to_string_box().value; - let val = args[1].clone_box(); - crate::runtime::modules_registry::set(key, val); - } - Ok(None) - } - ("env.modules", "get") => { - if let Some(k) = args.get(0) { - let key = k.to_string_box().value; - if let Some(v) = crate::runtime::modules_registry::get(&key) { - return Ok(Some(v)); - } - } - Ok(Some(Box::new(crate::box_trait::VoidBox::new()))) - } - ("env.task", "cancelCurrent") => { - let tok = crate::runtime::global_hooks::current_group_token(); - tok.cancel(); - Ok(None) - } - ("env.task", "currentToken") => { - let tok = crate::runtime::global_hooks::current_group_token(); - let tb = crate::boxes::token_box::TokenBox::from_token(tok); - Ok(Some(Box::new(tb))) - } - ("env.debug", "trace") => { - if std::env::var("NYASH_DEBUG_TRACE").ok().as_deref() == Some("1") { - for a in args { - eprintln!("[debug.trace] {}", a.to_string_box().value); - } - } - Ok(None) - } - ("env.runtime", "checkpoint") => { - if crate::config::env::runtime_checkpoint_trace() { - eprintln!("[runtime.checkpoint] reached"); - } - crate::runtime::global_hooks::safepoint_and_poll(); - Ok(None) - } - ("env.future", "new") | ("env.future", "birth") => { - let fut = crate::boxes::future::FutureBox::new(); - if let Some(v) = args.get(0) { - fut.set_result(v.clone_box()); - } - Ok(Some(Box::new(fut))) - } - ("env.future", "set") => { - if args.len() >= 2 { - if let Some(fut) = args[0] - .as_any() - .downcast_ref::() - { - fut.set_result(args[1].clone_box()); - } - } - Ok(None) - } - ("env.future", "await") => { - use crate::boxes::result::NyashResultBox; - if let Some(arg) = args.get(0) { - if let Some(fut) = arg - .as_any() - .downcast_ref::() - { - let max_ms: u64 = crate::config::env::await_max_ms(); - let start = std::time::Instant::now(); - let mut spins = 0usize; - while !fut.ready() { - crate::runtime::global_hooks::safepoint_and_poll(); - std::thread::yield_now(); - spins += 1; - if spins % 1024 == 0 { - std::thread::sleep(std::time::Duration::from_millis(1)); - } - if start.elapsed() >= std::time::Duration::from_millis(max_ms) { - let err = crate::box_trait::StringBox::new("Timeout"); - return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))); - } - } - return match fut.wait_and_get() { - Ok(v) => Ok(Some(Box::new(NyashResultBox::new_ok(v)))), - Err(e) => { - let err = crate::box_trait::StringBox::new(format!("Error: {}", e)); - Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))) - } - }; - } else { - return Ok(Some(Box::new(NyashResultBox::new_ok(arg.clone_box())))); - } - } - Ok(Some(Box::new( - crate::boxes::result::NyashResultBox::new_err(Box::new( - crate::box_trait::StringBox::new("InvalidArgs"), - )), - ))) - } - _ => Err(BidError::PluginError), - } + // Delegate to the extracted extern_functions module + super::extern_functions::extern_call(iface_name, method_name, args) } fn resolve_method_id_from_file(&self, box_type: &str, method_name: &str) -> BidResult { @@ -768,6 +649,8 @@ impl PluginLoaderV2 { Ok((bc.type_id, m.method_id, m.returns_result)) } + // Moved to ffi_bridge.rs + #[cfg(never)] pub fn invoke_instance_method( &self, box_type: &str, @@ -900,6 +783,8 @@ impl PluginLoaderV2 { Ok(Some(Box::new(crate::box_trait::VoidBox::new()))) } + // Moved to instance_manager.rs + #[cfg(never)] pub fn create_box( &self, box_type: &str, @@ -996,6 +881,8 @@ impl PluginLoaderV2 { Ok(Box::new(bx)) } + // Moved to instance_manager.rs + #[cfg(never)] /// Shutdown singletons: finalize and clear all singleton handles pub fn shutdown_singletons(&self) { let mut map = self.singletons.write().unwrap(); diff --git a/src/runtime/plugin_loader_v2/enabled/mod.rs b/src/runtime/plugin_loader_v2/enabled/mod.rs index e73948dc..015001c6 100644 --- a/src/runtime/plugin_loader_v2/enabled/mod.rs +++ b/src/runtime/plugin_loader_v2/enabled/mod.rs @@ -1,6 +1,9 @@ mod errors; +mod extern_functions; +mod ffi_bridge; mod globals; mod host_bridge; +mod instance_manager; mod loader; mod types;