//! Opaque session-handle bridge. The master key never leaves WASM linear //! memory; JS receives only a u32 handle that it passes back on every //! subsequent call. use std::cell::RefCell; use std::collections::HashMap; use zeroize::Zeroizing; pub struct SessionData { pub master_key: Zeroizing<[u8; 32]>, pub image_secret: Zeroizing<[u8; 32]>, } thread_local! { static SESSIONS: RefCell> = RefCell::new(HashMap::new()); static NEXT_HANDLE: RefCell = const { RefCell::new(1) }; } pub fn insert(master_key: Zeroizing<[u8; 32]>, image_secret: Zeroizing<[u8; 32]>) -> u32 { let handle = NEXT_HANDLE.with(|n| { let mut n = n.borrow_mut(); let h = *n; *n = n.wrapping_add(1); if *n == 0 { *n = 1; } // avoid reserving 0 as a valid handle h }); SESSIONS.with(|s| { s.borrow_mut().insert(handle, SessionData { master_key, image_secret }); }); handle } /// Access the master key for a handle. Preserves original `with` signature for all existing callers. pub fn with(handle: u32, f: F) -> Option where F: FnOnce(&Zeroizing<[u8; 32]>) -> R, { SESSIONS.with(|s| s.borrow().get(&handle).map(|d| f(&d.master_key))) } /// Access the image_secret for a handle (used by recovery QR). pub fn with_image_secret(handle: u32, f: F) -> Option where F: FnOnce(&Zeroizing<[u8; 32]>) -> R, { SESSIONS.with(|s| s.borrow().get(&handle).map(|d| f(&d.image_secret))) } pub fn remove(handle: u32) -> bool { SESSIONS.with(|s| s.borrow_mut().remove(&handle).is_some()) } /// For tests only — empty the table and wipe all sessions. #[cfg(test)] pub fn clear() { SESSIONS.with(|s| s.borrow_mut().clear()); }