#![warn(missing_docs)]
use std::sync::Arc;
use std::sync::RwLock;
use loader::Handler;
use serde::Deserialize;
use serde::Serialize;
use super::MessageEndpoint;
use crate::backend::types::BackendMessage;
use crate::error::Error;
use crate::error::Result;
use crate::prelude::reqwest;
use crate::prelude::*;
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
pub enum Path {
Local(String),
Remote(String),
}
#[derive(Deserialize, Serialize, Debug, Clone, Default, PartialEq, Eq)]
pub struct ExtensionConfig {
pub paths: Vec<Path>,
}
pub struct Extension {
handlers: Vec<Handler>,
}
pub trait ExtensionHandlerCaller {
fn call(&self, msg: BackendMessage) -> Result<BackendMessage>;
}
#[derive(Clone, Debug, Default)]
pub struct MaybeBackendMessage(Arc<RwLock<Option<Box<BackendMessage>>>>);
impl MaybeBackendMessage {
pub fn wrap(&self, msg: BackendMessage) -> Result<()> {
let mut guard = self
.0
.write()
.map_err(|_| Error::WasmBackendMessageRwLockError)?;
*guard = Some(Box::new(msg));
Ok(())
}
}
impl From<BackendMessage> for MaybeBackendMessage {
fn from(msg: BackendMessage) -> Self {
Self(Arc::new(RwLock::new(Some(Box::new(msg)))))
}
}
impl Extension {
async fn load(path: &Path) -> Result<Handler> {
match path {
Path::Local(path) => loader::load_from_fs(path.to_string()).await,
Path::Remote(path) => {
let data: String = reqwest::get(path)
.await
.map_err(|e| Error::HttpRequestError(e.to_string()))?
.text()
.await
.map_err(|e| Error::HttpRequestError(e.to_string()))?;
loader::load(data).await
}
}
}
pub async fn new(config: &ExtensionConfig) -> Result<Self> {
let mut handlers = vec![];
for p in &config.paths {
if let Ok(h) = Self::load(p).await {
handlers.push(h)
} else {
log::error!("Failed on loading extension {:?}", p)
}
}
Ok(Self { handlers })
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl MessageEndpoint for Extension {
async fn handle_message(
&self,
ctx: &MessagePayload<Message>,
data: &BackendMessage,
) -> Result<Vec<MessageHandlerEvent>> {
let mut ret = vec![];
for h in &self.handlers {
let resp = h.call(data.clone())?;
if resp != data.clone() {
let resp_bytes: bytes::Bytes = resp.into();
let ev = MessageHandlerEvent::SendReportMessage(
ctx.clone(),
Message::custom(&resp_bytes).map_err(|_| Error::InvalidMessage)?,
);
ret.push(ev)
}
}
Ok(ret)
}
}
pub mod loader {
use core::any::Any;
use std::fs;
use std::sync::Arc;
use std::sync::RwLock;
use lazy_static::lazy_static;
use wasmer::imports;
use wasmer::AsStoreMut;
use wasmer::ExternRef;
use wasmer::FromToNativeWasmType;
use wasmer::FunctionEnv;
use wasmer::FunctionEnvMut;
use wasmer::FunctionType;
use wasmer::Type;
use wasmer::TypedFunction;
use wasmer::Value;
use super::MaybeBackendMessage;
use crate::backend::types::BackendMessage;
use crate::error::Error;
use crate::error::Result;
lazy_static! {
static ref WASM_MEM: Arc<RwLock<wasmer::Store>> =
Arc::new(RwLock::new(wasmer::Store::default()));
}
impl TryFrom<MaybeBackendMessage> for FunctionEnv<MaybeBackendMessage> {
type Error = Error;
fn try_from(data: MaybeBackendMessage) -> Result<Self> {
let mut mem = WASM_MEM
.write()
.map_err(|_| Error::WasmGlobalMemoryLockError)?;
Ok(FunctionEnv::new(&mut mem, data))
}
}
pub trait WasmABILander: Sized + Any + Send + 'static {
fn land_abi(env: &FunctionEnv<Self>, store: &mut impl AsStoreMut) -> wasmer::Imports;
}
impl WasmABILander for MaybeBackendMessage {
fn land_abi(env: &FunctionEnv<Self>, store: &mut impl AsStoreMut) -> wasmer::Imports {
let msg_type = wasmer::Function::new_with_env(
store,
env,
FunctionType::new(vec![Type::ExternRef], vec![Type::I32]),
MaybeBackendMessage::msg_type,
);
let extra = wasmer::Function::new_with_env(
store,
env,
FunctionType::new(vec![Type::ExternRef], vec![Type::I32]),
MaybeBackendMessage::extra,
);
let data = wasmer::Function::new_with_env(
store,
env,
FunctionType::new(vec![Type::ExternRef], vec![Type::I32]),
MaybeBackendMessage::extra,
);
let read_at = wasmer::Function::new_with_env(
store,
env,
FunctionType::new(vec![Type::ExternRef, Type::I32], vec![Type::I32]),
MaybeBackendMessage::read_at,
);
let write_at = wasmer::Function::new_with_env(
store,
env,
FunctionType::new(vec![Type::ExternRef, Type::I32, Type::I32], vec![Type::I32]),
MaybeBackendMessage::write_at,
);
#[rustfmt::skip]
imports! {
"message_abi" => {
"message_type" => msg_type,
"extra" => extra,
"data" => data,
"read_at" => read_at,
"write_at" => write_at
}
}
}
}
impl MaybeBackendMessage {
pub fn msg_type(
env: FunctionEnvMut<MaybeBackendMessage>,
v: &[Value],
) -> core::result::Result<Vec<Value>, wasmer::RuntimeError> {
match v {
[Value::ExternRef(_)] => {
let msg = env.data();
if let Ok(m_guard) = msg.0.read() {
if let Some(m) = &*m_guard {
let ty: i32 = m.message_type.into();
Ok(vec![Value::I32(ty)])
} else {
Err(wasmer::RuntimeError::new("ExternalRef is NULL"))
}
} else {
Err(wasmer::RuntimeError::new(
"Failed on lock memory of external ref",
))
}
}
x => Err(wasmer::RuntimeError::new(format!(
"Expect Externef, got {:?}",
x
))),
}
}
pub fn extra(
env: FunctionEnvMut<MaybeBackendMessage>,
v: &[Value],
) -> core::result::Result<Vec<Value>, wasmer::RuntimeError> {
match v {
[Value::ExternRef(_)] => {
let msg = env.data();
if let Ok(m_guard) = msg.0.read() {
if let Some(m) = &*m_guard {
let extra = m.extra.map(|e| Value::I32(e as i32)).to_vec();
Ok(extra)
} else {
Err(wasmer::RuntimeError::new("ExternalRef is NULL"))
}
} else {
Err(wasmer::RuntimeError::new(
"Failed on lock memory of external ref",
))
}
}
x => Err(wasmer::RuntimeError::new(format!(
"Expect Externef, got {:?}",
x
))),
}
}
pub fn data(
env: FunctionEnvMut<MaybeBackendMessage>,
v: &[Value],
) -> core::result::Result<Vec<Value>, wasmer::RuntimeError> {
match v {
[Value::ExternRef(_)] => {
let msg = env.data();
if let Ok(m_guard) = msg.0.read() {
if let Some(m) = &*m_guard {
let data = m
.data
.clone()
.into_iter()
.map(|e| Value::I32(e as i32))
.collect();
Ok(data)
} else {
Err(wasmer::RuntimeError::new("ExternalRef is NULL"))
}
} else {
Err(wasmer::RuntimeError::new(
"Failed on lock memory of external ref",
))
}
}
x => Err(wasmer::RuntimeError::new(format!(
"Expect Externef, got {:?}",
x
))),
}
}
pub fn read_at(
env: FunctionEnvMut<MaybeBackendMessage>,
params: &[Value],
) -> core::result::Result<Vec<Value>, wasmer::RuntimeError> {
match params {
[Value::ExternRef(_), Value::I32(idx)] => {
let msg = env.data();
if let Ok(m_guard) = msg.0.read() {
if let Some(m) = &*m_guard {
let data_len = m.data.len() + 31;
match idx {
..=-1 => Err(wasmer::RuntimeError::new("Index overflow")),
0 => Ok(vec![Value::I32(m.message_type as i32)]),
1..=31 => Ok(vec![Value::I32(m.extra[*idx as usize] as i32)]),
32.. => {
if *idx > (1 + data_len as i32) {
Err(wasmer::RuntimeError::new("Index overflow"))
} else {
Ok(vec![Value::I32(m.data[*idx as usize] as i32)])
}
}
}
} else {
Err(wasmer::RuntimeError::new("ExternalRef is NULL"))
}
} else {
Err(wasmer::RuntimeError::new(
"Failed on lock memory of external ref",
))
}
}
x => Err(wasmer::RuntimeError::new(format!(
"Expect [Externef, i32], got {:?}",
x
))),
}
}
pub fn write_at(
env: FunctionEnvMut<MaybeBackendMessage>,
params: &[Value],
) -> core::result::Result<Vec<Value>, wasmer::RuntimeError> {
match params {
[Value::ExternRef(_), Value::I32(idx), Value::I32(value)] => {
let msg = env.data();
if let Ok(mut m_guard) = msg.0.write() {
if let Some(ref mut m) = *m_guard {
let data_len = m.data.len() + 31;
match idx {
..=-1 => Err(wasmer::RuntimeError::new("Index overflow")),
0 => {
m.message_type = *value as u16;
Ok(vec![Value::I32(1i32)])
}
1..=31 => {
let i = *idx as usize - 1;
m.extra[i] = *value as u8;
Ok(vec![Value::I32(1i32)])
}
32.. => {
if *idx > (1 + data_len as i32) {
Err(wasmer::RuntimeError::new("Index overflow"))
} else {
let i = *idx as usize - 31;
m.data[i] = *value as u8;
Ok(vec![Value::I32(1i32)])
}
}
}
} else {
Err(wasmer::RuntimeError::new(
"Failed on call func `write_at`:: ExternalRef is NULL",
))
}
} else {
Err(wasmer::RuntimeError::new(
"Failed on lock memory of external ref",
))
}
}
x => Err(wasmer::RuntimeError::new(format!(
"Expect [Externef, i32], got {:?}",
x
))),
}
}
}
unsafe impl FromToNativeWasmType for MaybeBackendMessage {
type Native = Option<ExternRef>;
fn from_native(native: Self::Native) -> Self {
if native.is_none() {
return Self::default();
}
match WASM_MEM
.read()
.map_err(|_| Error::WasmGlobalMemoryLockError)
{
Ok(mem) => {
if let Some(m) = native.unwrap().downcast::<Self>(&mem) {
m.clone()
} else {
Self::default()
}
}
Err(e) => {
log::error!("{:?}", e);
Self::default()
}
}
}
fn to_native(self) -> Self::Native {
match WASM_MEM
.write()
.map_err(|_| Error::WasmGlobalMemoryLockError)
{
Ok(mut mem) => {
let ext_ref = ExternRef::new::<Self>(&mut mem, self);
if ext_ref.is_from_store(&mem) {
Some(ext_ref)
} else {
None
}
}
Err(e) => {
log::error!("{:?}", e);
None
}
}
}
}
type TyHandler = TypedFunction<Option<ExternRef>, Option<ExternRef>>;
pub struct Handler {
pub func: TyHandler,
pub msg_ref: MaybeBackendMessage,
}
impl super::ExtensionHandlerCaller for Handler {
fn call(&self, msg: BackendMessage) -> Result<BackendMessage> {
self.msg_ref.wrap(msg)?;
let native_msg = self.msg_ref.clone().to_native();
let r = {
let mut mem = WASM_MEM
.write()
.map_err(|_| Error::WasmGlobalMemoryLockError)?;
self.func
.call(&mut mem, native_msg)
.map_err(|e| Error::WasmRuntimeError(e.to_string()))?
};
let ret = MaybeBackendMessage::from_native(r);
let data = ret.0.read().map_err(|_| Error::WasmGlobalMemoryLockError)?;
if let Some(m) = &*data {
Ok(*m.clone())
} else {
Err(Error::WasmRuntimeError("Result data is NULL".to_string()))
}
}
}
pub async fn load(bytes: impl AsRef<[u8]>) -> Result<Handler> {
let message = MaybeBackendMessage::default();
let env: FunctionEnv<MaybeBackendMessage> = message.clone().try_into()?;
let mut store = WASM_MEM
.write()
.map_err(|_| Error::WasmGlobalMemoryLockError)?;
let module = wasmer::Module::new(&store, &bytes)
.map_err(|e| Error::WasmCompileError(e.to_string()))?;
let import_object = MaybeBackendMessage::land_abi(&env, &mut store);
let ins = wasmer::Instance::new(&mut store, &module, &import_object)
.map_err(|_| Error::WasmInstantiationError)?;
let exports: wasmer::Exports = ins.exports;
let handler: TyHandler = exports
.get_function("handler")
.map_err(|_| Error::WasmExportError)?
.typed(&store)
.map_err(|_| Error::WasmExportError)?;
Ok(Handler {
func: handler,
msg_ref: message,
})
}
pub async fn load_from_fs(path: String) -> Result<Handler> {
if let Ok(wat) = fs::read_to_string(path) {
load(wat).await
} else {
Err(Error::WasmFailedToLoadFile)
}
}
}
#[cfg(not(feature = "browser"))]
#[cfg(test)]
mod test {
use crate::backend::extension::loader::load;
use crate::backend::extension::ExtensionHandlerCaller;
use crate::backend::types::BackendMessage;
#[tokio::test]
async fn test_load_wasm() {
let wasm = r#"
(module
;; fn handler(param: ExternRef) -> ExternRef
(func $handler (param externref) (result externref)
(return (local.get 0))
)
(export "handler" (func $handler))
)
"#;
let data = "hello extension";
let handler = load(wasm.to_string()).await.unwrap();
let msg = BackendMessage::from((2u16, data.as_bytes()));
let ret = handler.call(msg.clone()).unwrap();
assert_eq!(ret, msg);
}
#[tokio::test]
async fn test_complex_handler() {
let wasm = r#"
(module
;; Let's import message_type from message_abi
(type $ty_message_type (func (param externref) (result i32)))
(import "message_abi" "message_type" (func $message_type (type $ty_message_type)))
;; fn handler(param: ExternRef) -> ExternRef
(func $handler (param $input externref) (result externref)
(return (local.get 0))
)
(export "handler" (func $handler))
)
"#;
let data = "hello extension";
let handler = load(wasm.to_string()).await.unwrap();
let msg = BackendMessage::from((2u16, data.as_bytes()));
let ret = handler.call(msg.clone()).unwrap();
assert_eq!(ret, msg);
}
#[tokio::test]
async fn test_handle_write() {
let wasm = r#"
(module
;; Let's import write_at from message_abi
(type $ty_write_at (func (param externref i32 i32) (result i32)))
(import "message_abi" "write_at" (func $write_at (type $ty_write_at)))
;; fn handler(param: ExternRef) -> ExternRef
(func $handler (param $input externref) (result externref)
(call $write_at (local.get 0) (i32.const 0) (i32.const 42))
(return (local.get 0))
)
(export "handler" (func $handler))
)
"#;
let data = "hello extension";
let handler = load(wasm.to_string()).await.unwrap();
let msg = BackendMessage::from((2u16, data.as_bytes()));
assert_eq!(msg.message_type, 2u16, "{:?}", msg);
let ret = handler.call(msg.clone()).unwrap();
assert_eq!(ret.message_type, 42u16, "{:?}{:?}", msg, ret);
}
}