diff --git a/crates/wasm-actor/tests/wasm_actor.rs b/crates/wasm-actor/tests/wasm_actor.rs index 9a550b0..a505e67 100644 --- a/crates/wasm-actor/tests/wasm_actor.rs +++ b/crates/wasm-actor/tests/wasm_actor.rs @@ -7643,4 +7643,129 @@ proptest! { while let Some(_) = inbox.try_recv() {} } +} + +// ── Guest with i32.xor for byte transformation ───────────────────────────── + +#[test] +fn guest_xor_transform_key() { + let wat = r#" + (module + (import "swactor" "send" (func $send (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "alloc") (param i32) (result i32) i32.const 4096) + (func (export "handle") (param $ptr i32) (param $len i32) + (local $i i32) + (local $payload_start i32) + (local $payload_len i32) + (local.set $payload_start (i32.add (local.get $ptr) (i32.const 32))) + (local.set $payload_len (i32.sub (local.get $len) (i32.const 32))) + (local.set $i (i32.const 0)) + (block $break + (loop $loop + (br_if $break (i32.ge_u (local.get $i) (local.get $payload_len))) + (i32.store8 + (i32.add (i32.const 200) (local.get $i)) + (i32.xor + (i32.load8_u (i32.add (local.get $payload_start) (local.get $i))) + (i32.const 0xAA) + ) + ) + (local.set $i (i32.add (local.get $i) (i32.const 1))) + (br $loop) + ) + ) + (call $send (local.get $ptr) (i32.const 200) (local.get $payload_len)) + ) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, wasm).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + rt.send_to(addr, framed_msg(inbox.addr(), &[0x00, 0xFF, 0x55, 0xAA])).unwrap(); + rt.tick(); + + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0, vec![0xAA, 0x55, 0xFF, 0x00], "XOR with 0xAA"); +} + +// ── ActorAddress reconstruction from raw bytes ────────────────────────────── + +#[test] +fn actor_address_reconstructible_from_bytes() { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("echo")).build().unwrap(); + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let addr = rt.spawn(actor).unwrap(); + + let reconstructed = ActorAddress(addr.0); + assert_eq!(addr, reconstructed); + + rt.send_to(reconstructed, framed_msg(inbox.addr(), b"reconstructed")).unwrap(); + rt.tick(); + let msg = inbox.try_recv().unwrap(); + assert_eq!(msg.0, b"reconstructed"); +} + +// ── Double guest + watch fires once on stop ───────────────────────────────── + +#[test] +fn double_guest_watch_fires_once_on_stop() { + let engine = SharedEngine::new().unwrap(); + let actor = WasmActorBuilder::new(engine, guest_wasm("double")).build().unwrap(); + + let exit_count = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); + let ec = exit_count.clone(); + + struct WatchCounter3 { + target: Option, + count: std::sync::Arc, + } + impl ActorInterface for WatchCounter3 { + type Incoming = ByteMessage; + type Response = (); + fn handle(&mut self, ctx: &Ctx, _msg: ByteMessage) { + if let Some(t) = self.target.take() { ctx.watch(t); } + } + fn on_actor_exit(&mut self, _ctx: &Ctx, _exited: swactor::actor::ActorExited) { + self.count.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + } + } + + let rt = Runtime::new(RuntimeConfig::default()); + let inbox = rt.new_inbox::().unwrap(); + let double_addr = rt.spawn(actor).unwrap(); + let w_addr = rt.spawn(WatchCounter3 { target: Some(double_addr), count: ec }).unwrap(); + + rt.send_to(w_addr, ByteMessage(vec![])).unwrap(); + rt.tick(); + + rt.send_to(double_addr, framed_msg(inbox.addr(), b"D")).unwrap(); + rt.tick(); + assert_eq!(inbox.try_recv().unwrap().0, b"D"); + assert_eq!(inbox.try_recv().unwrap().0, b"D"); + + rt.stop_actor(double_addr); + rt.tick(); + rt.tick(); + assert_eq!(exit_count.load(std::sync::atomic::Ordering::SeqCst), 1); +} + +// ── ByteMessage constructors ──────────────────────────────────────────────── + +#[test] +fn byte_message_various_constructors() { + let msg1 = ByteMessage("hello".as_bytes().to_vec()); + assert_eq!(msg1.0, b"hello"); + let msg2 = ByteMessage(Vec::new()); + assert!(msg2.0.is_empty()); + let msg3 = ByteMessage(vec![0; 1024]); + assert_eq!(msg3.0.len(), 1024); } \ No newline at end of file