use std::collections::HashMap;
use tokio::sync::mpsc::UnboundedSender;
use wasmtime::{AsContext, AsContextMut, Engine, Instance, Linker, Module, Store};
use oo_protocol::{HostRequest, Message, Request, Response, ResponsePayload};
use crate::operation::Operation;
use crate::prelude::*;
use super::op_bridge;
pub(crate) struct HostState {
pub op_tx: UnboundedSender<Vec<Operation>>,
pub config: HashMap<String, String>,
pub languages: Vec<String>,
}
pub(crate) struct ExtensionInstance {
store: Store<HostState>,
instance: Instance,
}
impl ExtensionInstance {
pub(crate) fn new(
engine: &Engine,
wasm_bytes: &[u8],
op_tx: UnboundedSender<Vec<Operation>>,
config: HashMap<String, String>,
languages: Vec<String>,
) -> Result<Self> {
let module = Module::new(engine, wasm_bytes).map_err(|e| anyhow::anyhow!("compiling extension WASM: {}", e))?;
let mut linker: Linker<HostState> = Linker::new(engine);
linker
.func_wrap(
"oo",
"__oo_host_call",
|mut caller: wasmtime::Caller<'_, HostState>, ptr: i32, len: i32| {
let bytes = read_bytes(&mut caller, ptr, len).unwrap_or_default();
let msg: Message = match rmp_serde::from_slice(&bytes) {
Ok(m) => m,
Err(e) => {
log::warn!("extension sent invalid MessagePack: {}", e);
return;
}
};
if let Message::Request(req) = msg {
let id = req.id;
let result = handle_host_request(req, caller.data());
let response = Response { id, result };
send_response_to_wasm(&mut caller, response);
}
},
)
.map_err(|e| anyhow::anyhow!("registering __oo_host_call: {}", e))?;
linker
.func_wrap(
"oo",
"__oo_alloc",
|_caller: wasmtime::Caller<'_, HostState>, _size: i32| -> i32 { 0 },
)
.map_err(|e| anyhow::anyhow!("registering __oo_alloc stub: {}", e))?;
let host_state = HostState { op_tx, config, languages };
let mut store = Store::new(engine, host_state);
let instance = linker
.instantiate(&mut store, &module)
.map_err(|e| anyhow::anyhow!("instantiating extension WASM: {}", e))?;
if let Ok(init_fn) = instance.get_typed_func::<(), ()>(&mut store, "oo_init") {
init_fn.call(&mut store, ()).map_err(|e| anyhow::anyhow!("calling oo_init: {}", e))?;
}
Ok(Self { store, instance })
}
pub(crate) fn detect_project(&mut self) -> bool {
let Ok(f) = self
.instance
.get_typed_func::<(), i32>(&mut self.store, "detect_project")
else {
return false;
};
matches!(f.call(&mut self.store, ()), Ok(1))
}
pub(crate) fn call_export(&mut self, export_name: &str, payload_yaml: &str) -> Result<()> {
let payload_bytes = payload_yaml.as_bytes();
let alloc_fn = self
.instance
.get_typed_func::<i32, i32>(&mut self.store, "oo_alloc")
.map_err(|e| anyhow!("extension must export oo_alloc: {}", e))?;
let ptr = alloc_fn
.call(&mut self.store, payload_bytes.len() as i32)
.map_err(|e| anyhow::anyhow!("oo_alloc failed: {}", e))?;
let mem = self
.instance
.get_memory(&mut self.store, "memory")
.ok_or_else(|| anyhow!("extension must export 'memory'"))?;
mem.write(&mut self.store, ptr as usize, payload_bytes)
.map_err(|e| anyhow::anyhow!("writing payload to WASM memory: {}", e))?;
let target = self
.instance
.get_typed_func::<(i32, i32), ()>(&mut self.store, export_name)
.map_err(|e| anyhow!("extension does not export {:?}: {}", export_name, e))?;
target
.call(&mut self.store, (ptr, payload_bytes.len() as i32))
.map_err(|e| anyhow::anyhow!("calling {}: {}", export_name, e))?;
let free_fn = self
.instance
.get_typed_func::<(i32, i32), ()>(&mut self.store, "oo_free")
.map_err(|e| anyhow!("extension must export oo_free: {}", e))?;
free_fn
.call(&mut self.store, (ptr, payload_bytes.len() as i32))
.map_err(|e| anyhow::anyhow!("oo_free failed: {}", e))?;
Ok(())
}
}
fn handle_host_request(req: Request, state: &HostState) -> ResponsePayload {
match req.payload {
HostRequest::Log { message } => {
log::info!("[ext] {}", message);
ResponsePayload::None
}
HostRequest::GetConfig { key } => match state.config.get(&key).cloned() {
Some(val) => ResponsePayload::String(val),
None => ResponsePayload::Error("not found".into()),
},
HostRequest::HasLanguage { lang } => {
let found = state.languages.iter().any(|l| l == &lang);
ResponsePayload::String(found.to_string())
}
HostRequest::Operation(op) => match op_bridge::to_operation(op) {
Ok(ide_op) => {
let _ = state.op_tx.send(vec![ide_op]);
ResponsePayload::None
}
Err(e) => {
log::warn!("extension op error: {}", e);
ResponsePayload::Error(e.to_string())
}
},
}
}
fn send_response_to_wasm(caller: &mut wasmtime::Caller<'_, HostState>, response: Response) {
let msg = Message::Response(response);
let bytes = match rmp_serde::to_vec(&msg) {
Ok(b) => b,
Err(e) => {
log::warn!("failed to serialize response: {}", e);
return;
}
};
let alloc_fn = match caller.get_export("oo_alloc") {
Some(wasmtime::Extern::Func(f)) => f,
_ => {
log::warn!("extension missing oo_alloc export");
return;
}
};
let mut results = [wasmtime::Val::I32(0)];
if alloc_fn
.call(
caller.as_context_mut(),
&[wasmtime::Val::I32(bytes.len() as i32)],
&mut results,
)
.is_err()
{
return;
}
let ptr = match &results[0] {
wasmtime::Val::I32(p) => *p as usize,
_ => return,
};
let mem = match caller.get_export("memory") {
Some(wasmtime::Extern::Memory(m)) => m,
_ => return,
};
if mem.write(caller.as_context_mut(), ptr, &bytes).is_err() {
return;
}
let on_msg = match caller.get_export("oo_on_message") {
Some(wasmtime::Extern::Func(f)) => f,
_ => return, };
let mut results = [];
let _ = on_msg.call(
caller.as_context_mut(),
&[
wasmtime::Val::I32(ptr as i32),
wasmtime::Val::I32(bytes.len() as i32),
],
&mut results,
);
let free_fn = match caller.get_export("oo_free") {
Some(wasmtime::Extern::Func(f)) => f,
_ => return,
};
let mut results = [];
let _ = free_fn.call(
caller.as_context_mut(),
&[
wasmtime::Val::I32(ptr as i32),
wasmtime::Val::I32(bytes.len() as i32),
],
&mut results,
);
}
fn read_bytes(caller: &mut wasmtime::Caller<'_, HostState>, ptr: i32, len: i32) -> Result<Vec<u8>> {
let mem = caller
.get_export("memory")
.and_then(|e| {
if let wasmtime::Extern::Memory(m) = e {
Some(m)
} else {
None
}
})
.ok_or_else(|| anyhow!("extension must export 'memory'"))?;
let data = mem.data(caller.as_context());
let start = ptr as usize;
let end = start + len as usize;
data.get(start..end)
.ok_or_else(|| anyhow!("out-of-bounds memory read"))
.map(|s| s.to_vec())
}