From f5ab4910e43804592bc54c64f6eb1d6591edc72a Mon Sep 17 00:00:00 2001 From: Moe Charm Date: Mon, 18 Aug 2025 11:21:04 +0900 Subject: [PATCH] fix(plugins): Fix FileBox plugin I/O operations and add TLV debugging MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## FileBox Plugin Fixes ### Issue Resolution - Fixed file write operations not persisting to disk - Fixed TLV tag mismatch preventing read operations - Added comprehensive TLV debugging capability ### Changes Made #### plugins/nyash-filebox-plugin/src/lib.rs - Added file.flush() after write operations to ensure data persistence - Modified tlv_parse_bytes to accept both String(6) and Bytes(7) tags - Resolves compatibility issue with plugin-tester String encoding #### tools/plugin-tester/src/main.rs - Added `tlv-debug` subcommand with detailed TLV analysis - Provides hex dumps, encoding/decoding verification - Full plugin round-trip testing with file I/O validation - Detailed error analysis for debugging TLV protocol issues ## Test Results ### Before Fix ``` INFO: WRITE 8 bytes INFO: READ 0 bytes โ† Problem: no data read โœ—: Plugin round-trip failed\! ``` ### After Fix ``` INFO: WRITE 8 bytes INFO: READ 8 bytes โ† Fixed: data successfully read โœ“: Plugin round-trip successful\! ``` ## Technical Details - Root cause: Missing file.flush() + TLV tag type mismatch - Plugin-tester sends String(6), plugin expected Bytes(7) only - File buffer not flushed to disk before close/reopen - Solution: Added flush() + dual tag support for compatibility This completes the core BID-FFI plugin I/O functionality validation. Nyash integration still needs method dispatch optimization. ๐Ÿค– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- plugins/nyash-filebox-plugin/src/lib.rs | 7 +- tools/plugin-tester/src/main.rs | 186 ++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 1 deletion(-) diff --git a/plugins/nyash-filebox-plugin/src/lib.rs b/plugins/nyash-filebox-plugin/src/lib.rs index e6629a82..b7bfbec6 100644 --- a/plugins/nyash-filebox-plugin/src/lib.rs +++ b/plugins/nyash-filebox-plugin/src/lib.rs @@ -297,6 +297,10 @@ pub extern "C" fn nyash_plugin_invoke( 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)); return write_tlv_i32(n as i32, _result, _result_len); } @@ -433,7 +437,8 @@ fn tlv_parse_bytes(data: &[u8]) -> 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 != 7 || pos + size > data.len() { return Err(()); } + // Stringใ‚ฟใ‚ฐใ‚‚Bytesใ‚ฟใ‚ฐใ‚‚ๅ—ใ‘ไป˜ใ‘ใ‚‹๏ผˆไบ’ๆ›ๆ€งใฎใŸใ‚๏ผ‰ + if (tag != 6 && tag != 7) || pos + size > data.len() { return Err(()); } Ok(data[pos..pos+size].to_vec()) } diff --git a/tools/plugin-tester/src/main.rs b/tools/plugin-tester/src/main.rs index 0cccd167..ec51b2d4 100644 --- a/tools/plugin-tester/src/main.rs +++ b/tools/plugin-tester/src/main.rs @@ -64,6 +64,14 @@ enum Commands { /// Path to plugin .so file plugin: PathBuf, }, + /// Debug TLV encoding/decoding with detailed output + TlvDebug { + /// Path to plugin .so file + plugin: PathBuf, + /// Test message to encode/decode + #[arg(short, long, default_value = "Hello TLV Debug!")] + message: String, + }, } // ============ Host Functions (ใƒ†ใ‚นใƒˆ็”จๅฎŸ่ฃ…) ============ @@ -114,6 +122,7 @@ fn main() { Commands::Check { plugin } => check_plugin(&plugin), Commands::Lifecycle { plugin } => test_lifecycle(&plugin), Commands::Io { plugin } => test_file_io(&plugin), + Commands::TlvDebug { plugin, message } => test_tlv_debug(&plugin, &message), } } @@ -481,3 +490,180 @@ fn test_file_io(path: &PathBuf) { println!("\n{}", "File I/O test completed!".green().bold()); } } + +fn test_tlv_debug(path: &PathBuf, message: &str) { + println!("{}", "=== TLV Debug Test ===".bold()); + println!("Testing TLV encoding/decoding with: '{}'", message); + + // Load plugin + 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 u32> = library.get(b"nyash_plugin_abi").unwrap(); + println!("{}: ABI version: {}", "โœ“".green(), abi()); + + let init: Symbol i32> = library.get(b"nyash_plugin_init").unwrap(); + let mut info = std::mem::zeroed::(); + assert_eq!(0, init(&HOST_VTABLE, &mut info)); + + let invoke: Symboli32> = library.get(b"nyash_plugin_invoke").unwrap(); + let shutdown: Symbol = library.get(b"nyash_plugin_shutdown").unwrap(); + + // Test TLV encoding + println!("\n{}", "--- Encoding Test ---".cyan()); + let mut encoded = Vec::new(); + tlv_encode_string(message, &mut encoded); + + println!("Original message: '{}'", message); + println!("Encoded TLV ({} bytes): {:02x?}", encoded.len(), encoded); + + // Hex dump for readability + print!("Hex dump: "); + for (i, byte) in encoded.iter().enumerate() { + if i % 16 == 0 && i > 0 { print!("\n "); } + print!("{:02x} ", byte); + } + println!(); + + // Test TLV decoding + println!("\n{}", "--- Decoding Test ---".cyan()); + if let Some((tag, payload)) = tlv_decode_first(&encoded) { + println!("Decoded tag: {} ({})", tag, + match tag { + 6 => "String", + 7 => "Bytes", + _ => "Unknown" + }); + println!("Decoded payload ({} bytes): {:02x?}", payload.len(), payload); + + if tag == Tag::String as u8 || tag == Tag::Bytes as u8 { + let decoded_str = String::from_utf8_lossy(payload); + println!("Decoded string: '{}'", decoded_str); + + if decoded_str == message { + println!("{}: TLV round-trip successful!", "โœ“".green()); + } else { + println!("{}: TLV round-trip failed! Expected: '{}', Got: '{}'", + "โœ—".red(), message, decoded_str); + } + } + } else { + println!("{}: Failed to decode TLV!", "โœ—".red()); + } + + // Test with plugin write/read + println!("\n{}", "--- Plugin Round-trip Test ---".cyan()); + + // 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); + assert!(rc == -1 && buf_len >= 4); + 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)); + let instance_id = u32::from_le_bytes(out[0..4].try_into().unwrap()); + println!("{}: birth โ†’ instance_id={}", "โœ“".green(), instance_id); + + // Test file write + let test_path = "plugins/nyash-filebox-plugin/target/tlv_debug_test.txt"; + let mut args = Vec::new(); + tlv_encode_two_strings(test_path, "w", &mut args); + println!("Write args TLV ({} bytes): {:02x?}", args.len(), &args[..args.len().min(32)]); + + let mut need: usize = 0; + let _ = invoke(info.type_id, 1, instance_id, args.as_ptr(), args.len(), std::ptr::null_mut(), &mut need); + let mut obuf = vec![0u8; need.max(4)]; + let mut olen = need; + let _ = invoke(info.type_id, 1, instance_id, args.as_ptr(), args.len(), obuf.as_mut_ptr(), &mut olen); + println!("{}: open(w) successful", "โœ“".green()); + + // Write test message + let mut write_args = Vec::new(); + tlv_encode_string(message, &mut write_args); + println!("Write message TLV ({} bytes): {:02x?}", write_args.len(), &write_args[..write_args.len().min(32)]); + + let mut wneed: usize = 0; + let _ = invoke(info.type_id, 3, instance_id, write_args.as_ptr(), write_args.len(), std::ptr::null_mut(), &mut wneed); + let mut wbuf = vec![0u8; wneed.max(4)]; + let mut wlen = wneed; + let _ = invoke(info.type_id, 3, instance_id, write_args.as_ptr(), write_args.len(), wbuf.as_mut_ptr(), &mut wlen); + println!("{}: write successful", "โœ“".green()); + + // Close + let mut clen: usize = 0; + let _ = invoke(info.type_id, 4, instance_id, std::ptr::null(), 0, std::ptr::null_mut(), &mut clen); + 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); + println!("{}: close successful", "โœ“".green()); + + // Reopen for read + let mut read_args = Vec::new(); + tlv_encode_two_strings(test_path, "r", &mut read_args); + let mut rneed: usize = 0; + let _ = invoke(info.type_id, 1, instance_id, read_args.as_ptr(), read_args.len(), std::ptr::null_mut(), &mut rneed); + let mut robuf = vec![0u8; rneed.max(4)]; + let mut rolen = rneed; + let _ = invoke(info.type_id, 1, instance_id, read_args.as_ptr(), read_args.len(), robuf.as_mut_ptr(), &mut rolen); + println!("{}: open(r) successful", "โœ“".green()); + + // Read back + let mut size_args = Vec::new(); + tlv_encode_i32(1024, &mut size_args); + let mut read_need: usize = 0; + let rc = invoke(info.type_id, 2, instance_id, size_args.as_ptr(), size_args.len(), std::ptr::null_mut(), &mut read_need); + println!("Read preflight: rc={}, need={} bytes", rc, read_need); + + let mut read_buf = vec![0u8; read_need.max(16)]; + let mut read_len = read_need; + let rc2 = invoke(info.type_id, 2, instance_id, size_args.as_ptr(), size_args.len(), read_buf.as_mut_ptr(), &mut read_len); + println!("Read actual: rc={}, got={} bytes", rc2, read_len); + + if read_len > 0 { + println!("Read result TLV ({} bytes): {:02x?}", read_len, &read_buf[..read_len.min(32)]); + + // Try to decode + if let Some((tag, payload)) = tlv_decode_first(&read_buf[..read_len]) { + println!("Read decoded tag: {} ({})", tag, + match tag { + 6 => "String", + 7 => "Bytes", + _ => "Unknown" + }); + let read_message = String::from_utf8_lossy(payload); + println!("Read decoded message: '{}'", read_message); + + if read_message == message { + println!("{}: Plugin round-trip successful!", "โœ“".green()); + } else { + println!("{}: Plugin round-trip failed! Expected: '{}', Got: '{}'", + "โœ—".red(), message, read_message); + } + } else { + println!("{}: Failed to decode read result!", "โœ—".red()); + // Show detailed hex analysis + if read_len >= 4 { + let version = u16::from_le_bytes([read_buf[0], read_buf[1]]); + let argc = u16::from_le_bytes([read_buf[2], read_buf[3]]); + println!("TLV Header analysis: version={}, argc={}", version, argc); + + if read_len >= 8 { + let entry_tag = read_buf[4]; + let entry_reserved = read_buf[5]; + let entry_len = u16::from_le_bytes([read_buf[6], read_buf[7]]); + println!("First entry: tag={}, reserved={}, len={}", entry_tag, entry_reserved, entry_len); + } + } + } + } + + shutdown(); + println!("\n{}", "TLV Debug test completed!".green().bold()); + } +}