use crate::types::{Buffer, Closure};
use std::cell::RefCell;
#[derive(Debug, Default, Clone)]
pub struct CapturedStrings {
pub from_str_calls: Vec<String>,
pub to_string_results: Vec<String>,
}
thread_local! {
static ORIGINAL_DISPATCH: RefCell<Option<OriginalDispatch>> = const { RefCell::new(None) };
static CAPTURED: RefCell<CapturedStrings> = RefCell::new(CapturedStrings::default());
}
struct OriginalDispatch {
call: unsafe extern "C" fn(*mut u8, Buffer) -> Buffer,
env: *mut u8,
}
unsafe impl Send for OriginalDispatch {}
const METHOD_TOKEN_STREAM: u8 = 0x01;
const TS_FROM_STR: u8 = 0x04;
const TS_TO_STRING: u8 = 0x05;
fn read_usize_le(data: &[u8], offset: usize) -> Option<usize> {
let size = std::mem::size_of::<usize>();
if offset + size > data.len() {
return None;
}
let mut bytes = [0u8; 8];
bytes[..size].copy_from_slice(&data[offset..offset + size]);
Some(usize::from_le_bytes(bytes))
}
fn extract_string(data: &[u8], offset: usize) -> Option<String> {
let len = read_usize_le(data, offset)?;
let size = std::mem::size_of::<usize>();
let start = offset + size;
if len > 10_000_000 || start + len > data.len() {
return None;
}
String::from_utf8(data[start..start + len].to_vec()).ok()
}
unsafe extern "C" fn wrapped_dispatch(env: *mut u8, request: Buffer) -> Buffer {
let request_data = request.as_slice().to_vec();
let method_group = request_data.first().copied();
let method_index = request_data.get(1).copied();
let response = ORIGINAL_DISPATCH.with(|orig| {
let orig = orig.borrow();
let orig = orig.as_ref().expect("original dispatch not set");
unsafe { (orig.call)(orig.env, request) }
});
if method_group == Some(METHOD_TOKEN_STREAM) {
if method_index == Some(TS_FROM_STR) {
if let Some(s) = extract_string(&request_data, 2) {
CAPTURED.with(|c| {
c.borrow_mut().from_str_calls.push(s);
});
}
} else if method_index == Some(TS_TO_STRING) {
let response_data = response.as_slice();
if response_data.first() == Some(&0x00) {
if let Some(s) = extract_string(response_data, 1) {
CAPTURED.with(|c| {
c.borrow_mut().to_string_results.push(s);
});
}
}
}
}
let _ = env;
response
}
pub unsafe fn install_dispatch_interceptor<'a>(
original: &Closure<'a, Buffer, Buffer>,
) -> Closure<'a, Buffer, Buffer> {
ORIGINAL_DISPATCH.with(|orig| {
*orig.borrow_mut() = Some(OriginalDispatch {
call: original.call,
env: original.env,
});
});
CAPTURED.with(|c| {
*c.borrow_mut() = CapturedStrings::default();
});
Closure {
call: wrapped_dispatch,
env: std::ptr::null_mut(),
_marker: std::marker::PhantomData,
}
}
pub unsafe fn call_to_string_on_handle(handle: u32) -> Option<String> {
let mut request = vec![METHOD_TOKEN_STREAM, TS_TO_STRING];
request.extend_from_slice(&handle.to_le_bytes());
let buf = Buffer::from_vec(request);
let response = ORIGINAL_DISPATCH.with(|orig| {
let orig = orig.borrow();
let orig = orig.as_ref()?;
Some(unsafe { (orig.call)(orig.env, buf) })
})?;
let resp_data = response.as_slice();
if resp_data.first() == Some(&0x00) {
extract_string(resp_data, 1)
} else {
None
}
}
pub fn take_captured() -> CapturedStrings {
CAPTURED.with(|c| std::mem::take(&mut *c.borrow_mut()))
}
pub fn cleanup_dispatch() {
ORIGINAL_DISPATCH.with(|orig| {
*orig.borrow_mut() = None;
});
}