WASM work

This commit is contained in:
John Smith
2022-03-15 09:33:34 -04:00
parent 031c998cfc
commit ca85b555aa
25 changed files with 672 additions and 395 deletions

View File

@@ -1,278 +0,0 @@
use crate::*;
pub use wasm_bindgen_futures::*;
#[wasm_bindgen(js_name = VeilidStateChange)]
pub struct JsVeilidStateChange {
kind: String, // "attachment" => AttachmentState(String)
from: JsValue,
to: JsValue,
}
#[wasm_bindgen(js_name = VeilidState)]
pub struct JsVeilidState {
kind: String, // "attachment" => AttachmentState(String)
state: JsValue,
}
#[wasm_bindgen(js_name = VeilidCore)]
pub struct JsVeilidCore {
core: VeilidCore,
}
#[wasm_bindgen(js_class = VeilidCore)]
impl JsVeilidCore {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
set_panic_hook();
JsVeilidCore {
core: VeilidCore::new(),
}
}
fn value_to_string(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(val.as_string().ok_or(())?))
}
fn value_to_option_string(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
if val.is_null() || val.is_undefined() {
return Ok(Box::new(Option::<String>::None));
}
Ok(Box::new(Some(val.as_string().ok_or(())?)))
}
fn value_to_bool(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(val.is_truthy()))
}
fn value_to_u8(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(f64_try_to_unsigned::<u8>(
val.as_f64().ok_or(())?,
)?))
}
fn value_to_u32(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(f64_try_to_unsigned::<u32>(
val.as_f64().ok_or(())?,
)?))
}
fn value_to_u64(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(f64_try_to_unsigned::<u64>(
val.as_f64().ok_or(())?,
)?))
}
fn value_to_option_u64(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
if val.is_null() || val.is_undefined() {
return Ok(Box::new(Option::<u64>::None));
}
Ok(Box::new(Some(f64_try_to_unsigned::<u64>(
val.as_f64().ok_or(())?,
)?)))
}
fn value_to_dht_key(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(
DHTKey::try_decode(val.as_string().ok_or(())?.as_str()).map_err(drop)?,
))
}
fn value_to_dht_key_secret(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
Ok(Box::new(
DHTKeySecret::try_decode(val.as_string().ok_or(())?.as_str()).map_err(drop)?,
))
}
fn value_to_vec_string(val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
let arrval = val.dyn_into::<Array>().map_err(drop)?.to_vec();
let mut out = Vec::<String>::with_capacity(arrval.len());
for v in arrval {
out.push(v.as_string().ok_or(())?);
}
Ok(Box::new(out))
}
fn translate_config_callback(key: &str, val: JsValue) -> Result<Box<dyn core::any::Any>, ()> {
match key {
// xxx: lots of missing keys here
"namespace" => Self::value_to_string(val),
"capabilities.protocol_udp" => Self::value_to_bool(val),
"capabilities.protocol_connect_tcp" => Self::value_to_bool(val),
"capabilities.protocol_accept_tcp" => Self::value_to_bool(val),
"capabilities.protocol_connect_ws" => Self::value_to_bool(val),
"capabilities.protocol_accept_ws" => Self::value_to_bool(val),
"capabilities.protocol_connect_wss" => Self::value_to_bool(val),
"capabilities.protocol_accept_wss" => Self::value_to_bool(val),
"tablestore.directory" => Self::value_to_string(val),
"network.max_connections" => Self::value_to_u32(val),
"network.node_id" => Self::value_to_dht_key(val),
"network.node_id_secret" => Self::value_to_dht_key_secret(val),
"network.bootstrap" => Self::value_to_vec_string(val),
"network.rpc.concurrency" => Self::value_to_u32(val),
"network.rpc.queue_size" => Self::value_to_u32(val),
"network.rpc.max_timestamp_behind" => Self::value_to_option_u64(val),
"network.rpc.max_timestamp_ahead" => Self::value_to_option_u64(val),
"network.rpc.timeout" => Self::value_to_u64(val),
"network.rpc.max_route_hop_count" => Self::value_to_u8(val),
"network.dht.resolve_node_timeout" => Self::value_to_option_u64(val),
"network.dht.resolve_node_count" => Self::value_to_u32(val),
"network.dht.resolve_node_fanout" => Self::value_to_u32(val),
"network.dht.max_find_node_count" => Self::value_to_u32(val),
"network.dht.get_value_timeout" => Self::value_to_option_u64(val),
"network.dht.get_value_count" => Self::value_to_u32(val),
"network.dht.get_value_fanout" => Self::value_to_u32(val),
"network.dht.set_value_timeout" => Self::value_to_option_u64(val),
"network.dht.set_value_count" => Self::value_to_u32(val),
"network.dht.set_value_fanout" => Self::value_to_u32(val),
"network.dht.min_peer_count" => Self::value_to_u32(val),
"network.dht.min_peer_refresh_time" => Self::value_to_u64(val),
"network.dht.validate_dial_info_receipt_time" => Self::value_to_u64(val),
"network.upnp" => Self::value_to_bool(val),
"network.natpmp" => Self::value_to_bool(val),
"network.address_filter" => Self::value_to_bool(val),
"network.restricted_nat_retries" => Self::value_to_u32(val),
"network.tls.certificate_path" => Self::value_to_string(val),
"network.tls.private_key_path" => Self::value_to_string(val),
"network.application.path" => Self::value_to_string(val),
"network.application.https.enabled" => Self::value_to_bool(val),
"network.application.https.listen_address" => Self::value_to_string(val),
"network.application.http.enabled" => Self::value_to_bool(val),
"network.application.http.listen_address" => Self::value_to_string(val),
"network.protocol.udp.enabled" => Self::value_to_bool(val),
"network.protocol.udp.socket_pool_size" => Self::value_to_u32(val),
"network.protocol.udp.listen_address" => Self::value_to_string(val),
"network.protocol.udp.public_address" => Self::value_to_option_string(val),
"network.protocol.tcp.connect" => Self::value_to_bool(val),
"network.protocol.tcp.listen" => Self::value_to_bool(val),
"network.protocol.tcp.max_connections" => Self::value_to_u32(val),
"network.protocol.tcp.listen_address" => Self::value_to_string(val),
"network.protocol.tcp.public_address" => Self::value_to_option_string(val),
"network.protocol.ws.connect" => Self::value_to_bool(val),
"network.protocol.ws.listen" => Self::value_to_bool(val),
"network.protocol.ws.max_connections" => Self::value_to_u32(val),
"network.protocol.ws.listen_address" => Self::value_to_string(val),
"network.protocol.ws.path" => Self::value_to_string(val),
"network.protocol.ws.public_address" => Self::value_to_option_string(val),
"network.protocol.wss.connect" => Self::value_to_bool(val),
"network.protocol.wss.listen" => Self::value_to_bool(val),
"network.protocol.wss.max_connections" => Self::value_to_u32(val),
"network.protocol.wss.listen_address" => Self::value_to_string(val),
"network.protocol.wss.path" => Self::value_to_string(val),
"network.protocol.wss.public_address" => Self::value_to_option_string(val),
_ => return Err(()),
}
}
fn translate_veilid_state(state: JsVeilidState) -> Result<VeilidState, JsValue> {
Ok(match state.kind.as_str() {
"attachment" => {
let state_string = state
.state
.as_string()
.ok_or(JsValue::from_str("state should be a string"))?;
let astate = AttachmentState::try_from(state_string)
.map_err(|e| JsValue::from_str(format!("invalid state: {:?}", e).as_str()))?;
VeilidState::Attachment(astate)
}
_ => return Err(JsValue::from_str("unknown state kind")),
})
}
// xxx rework this for new veilid_api mechanism which should be its own js object now
pub fn startup(
&self,
js_state_change_callback: Function,
js_config_callback: Function,
) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
let vcs = VeilidCoreSetup {
state_change_callback: Arc::new(
move |change: VeilidStateChange| -> SystemPinBoxFuture<()> {
let js_state_change_callback = js_state_change_callback.clone();
Box::pin(async move {
let js_change = match change {
VeilidStateChange::Attachment {
old_state,
new_state,
} => JsVeilidStateChange {
kind: "attachment".to_owned(),
from: JsValue::from_str(old_state.to_string().as_str()),
to: JsValue::from_str(new_state.to_string().as_str()),
},
};
let ret = match Function::call1(
&js_state_change_callback,
&JsValue::UNDEFINED,
&JsValue::from(js_change),
) {
Ok(v) => v,
Err(e) => {
error!("calling state change callback failed: {:?}", e);
return;
}
};
let retp: Promise = match ret.dyn_into() {
Ok(v) => v,
Err(e) => {
error!(
"state change callback did not return a promise: {:?}",
e
);
return;
}
};
match JsFuture::from(retp).await {
Ok(_) => (),
Err(e) => {
error!("state change callback returned an error: {:?}", e);
return;
}
};
})
},
),
config_callback: Arc::new(
move |key: String| -> Result<Box<dyn core::any::Any>, String> {
let val = Function::call1(
&js_config_callback,
&JsValue::UNDEFINED,
&JsValue::from_str(key.as_str()),
)
.map_err(|_| {
format!("Failed to get config from callback for key '{}'", key)
})?;
Self::translate_config_callback(key.as_str(), val)
.map_err(|_| format!("invalid value type for config key '{}'", key))
},
),
};
match core.startup(vcs).await {
Ok(_) => Ok(JsValue::UNDEFINED),
Err(e) => Err(JsValue::from_str(
format!("VeilidCore startup() failed: {}", e.to_string()).as_str(),
)),
}
})
}
pub fn send_state_update(&self) {
self.core.send_state_update();
}
pub fn shutdown(&self) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
core.shutdown().await;
Ok(JsValue::UNDEFINED)
})
}
pub fn attach(&self) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
core.attach();
Ok(JsValue::UNDEFINED)
})
}
pub fn detach(&self) -> Promise {
let core = self.core.clone();
future_to_promise(async move {
core.detach();
Ok(JsValue::UNDEFINED)
})
}
}

View File

@@ -1,25 +1,191 @@
// wasm-bindgen and clippy don't play well together yet
#![allow(clippy::all)]
#![cfg(target_arch = "wasm32")]
#![no_std]
#[macro_use]
extern crate alloc;
use alloc::string::String;
use alloc::sync::Arc;
use core::any::{Any, TypeId};
use core::cell::RefCell;
use futures_util::FutureExt;
use js_sys::*;
use lazy_static::*;
use log::*;
use send_wrapper::*;
use serde::*;
use veilid_core::xx::*;
use veilid_core::*;
use wasm_bindgen_futures::*;
pub use log::*;
pub use wasm_bindgen::prelude::*;
pub use wasm_bindgen::JsCast;
// Allocator
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
pub use alloc::boxed::Box;
pub use alloc::string::String;
pub use alloc::sync::Arc;
pub use alloc::vec::Vec;
pub use core::convert::TryFrom;
pub use js_sys::*;
pub use js_veilid_core::*;
pub use utils::*;
pub use veilid_core::dht::key::*;
pub use veilid_core::xx::*;
pub use veilid_core::*;
pub use wasm_logger::*;
static SETUP_ONCE: Once = Once::new();
pub fn setup() -> () {
SETUP_ONCE.call_once(|| {});
}
mod js_veilid_core;
mod utils;
// API Singleton
lazy_static! {
static ref VEILID_API: SendWrapper<RefCell<Option<veilid_core::VeilidAPI>>> =
SendWrapper::new(RefCell::new(None));
}
fn get_veilid_api() -> Result<veilid_core::VeilidAPI, veilid_core::VeilidAPIError> {
(*VEILID_API)
.borrow()
.clone()
.ok_or(veilid_core::VeilidAPIError::NotInitialized)
}
fn take_veilid_api() -> Result<veilid_core::VeilidAPI, veilid_core::VeilidAPIError> {
(**VEILID_API)
.take()
.ok_or(veilid_core::VeilidAPIError::NotInitialized)
}
// JSON Marshalling
pub fn serialize_json<T: Serialize>(val: T) -> String {
serde_json::to_string(&val).expect("failed to serialize json value")
}
pub fn deserialize_json<T: de::DeserializeOwned>(
arg: &str,
) -> Result<T, veilid_core::VeilidAPIError> {
serde_json::from_str(arg).map_err(|e| veilid_core::VeilidAPIError::ParseError {
message: e.to_string(),
value: String::new(),
})
}
pub fn to_json<T: Serialize>(val: T) -> JsValue {
JsValue::from_str(&serialize_json(val))
}
pub fn from_json<T: de::DeserializeOwned>(val: JsValue) -> Result<T, veilid_core::VeilidAPIError> {
let s = val
.as_string()
.ok_or_else(|| veilid_core::VeilidAPIError::ParseError {
message: "Value is not String".to_owned(),
value: String::new(),
})?;
deserialize_json(&s)
}
// Utility types for async API results
type APIResult<T> = Result<T, veilid_core::VeilidAPIError>;
const APIRESULT_UNDEFINED: APIResult<()> = APIResult::Ok(());
pub fn wrap_api_future<F, T>(future: F) -> Promise
where
F: Future<Output = APIResult<T>> + 'static,
T: Serialize + 'static,
{
future_to_promise(future.map(|res| {
res.map(|v| {
if TypeId::of::<()>() == v.type_id() {
JsValue::UNDEFINED
} else {
to_json(v)
}
})
.map_err(|e| to_json(e))
}))
}
// WASM Bindings
#[wasm_bindgen(js_namespace = veilid)]
pub fn initialize_veilid_wasm() {
console_error_panic_hook::set_once();
wasm_logger::init(wasm_logger::Config::new(Level::Info));
}
#[wasm_bindgen(js_namespace = veilid)]
pub fn startup_veilid_core(update_callback: Function, json_config: String) -> Promise {
wrap_api_future(async move {
let update_callback = Arc::new(move |update: VeilidUpdate| {
let _ret =
match Function::call1(&update_callback, &JsValue::UNDEFINED, &to_json(update)) {
Ok(v) => v,
Err(e) => {
error!("calling update callback failed: {:?}", e);
return;
}
};
});
if VEILID_API.borrow().is_some() {
return Err(veilid_core::VeilidAPIError::AlreadyInitialized);
}
let veilid_api = veilid_core::api_startup_json(update_callback, json_config).await?;
VEILID_API.replace(Some(veilid_api));
APIRESULT_UNDEFINED
})
}
#[wasm_bindgen(js_namespace = veilid)]
pub fn get_veilid_state() -> Promise {
wrap_api_future(async move {
let veilid_api = get_veilid_api()?;
let core_state = veilid_api.get_state().await?;
Ok(core_state)
})
}
#[wasm_bindgen(js_namespace = veilid)]
pub fn change_log_level(log_level: String) -> Promise {
wrap_api_future(async move {
let veilid_api = get_veilid_api()?;
let log_level: veilid_core::VeilidConfigLogLevel = deserialize_json(&log_level)?;
veilid_api.change_log_level(log_level).await;
APIRESULT_UNDEFINED
})
}
#[wasm_bindgen(js_namespace = veilid)]
pub fn shutdown_veilid_core() -> Promise {
wrap_api_future(async move {
let veilid_api = take_veilid_api()?;
veilid_api.shutdown().await;
APIRESULT_UNDEFINED
})
}
#[wasm_bindgen(js_namespace = veilid)]
pub fn debug(command: String) -> Promise {
wrap_api_future(async move {
let veilid_api = get_veilid_api()?;
let out = veilid_api.debug(command).await?;
Ok(out)
})
}
#[wasm_bindgen(js_namespace = veilid)]
pub fn veilid_version_string() -> String {
veilid_core::veilid_version_string()
}
#[derive(Serialize)]
pub struct VeilidVersion {
pub major: u32,
pub minor: u32,
pub patch: u32,
}
#[wasm_bindgen(js_namespace = veilid)]
pub fn veilid_version() -> JsValue {
let (major, minor, patch) = veilid_core::veilid_version();
let vv = VeilidVersion {
major,
minor,
patch,
};
JsValue::from_serde(&vv).unwrap()
}

View File

@@ -1,38 +0,0 @@
use cfg_if::*;
use wasm_bindgen::prelude::*;
//use wasm_bindgen_futures::*;
cfg_if! {
if #[cfg(feature = "wee_alloc")] {
// When the `wee_alloc` feature is enabled, use `wee_alloc` as the global
// allocator.
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
}
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console, js_name = log)]
pub fn console_log(s: &str);
#[wasm_bindgen]
pub fn alert(s: &str);
}
pub fn set_panic_hook() {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
}
pub fn f64_try_to_unsigned<T>(f: f64) -> Result<T, ()>
where
T: core::convert::TryFrom<u64>,
{
let rf = f.floor();
if rf < 0.0 {
return Err(());
}
T::try_from(rf as u64).map_err(drop)
}