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

7
Cargo.lock generated
View File

@ -4316,8 +4316,13 @@ version = "0.1.0"
dependencies = [
"cfg-if 1.0.0",
"console_error_panic_hook",
"futures-util",
"js-sys",
"lazy_static",
"log",
"send_wrapper",
"serde 1.0.136",
"serde_json",
"veilid-core",
"wasm-bindgen",
"wasm-bindgen-futures",
@ -4374,6 +4379,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
dependencies = [
"cfg-if 1.0.0",
"serde 1.0.136",
"serde_json",
"wasm-bindgen-macro",
]

View File

@ -63,6 +63,6 @@ cargo install cargo-ndk
cargo install cargo-apk
# Ensure packages are installed
sudo apt-get install libc6-dev-i386 libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 openjdk-11-jdk
sudo apt-get install libc6-dev-i386 libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 openjdk-11-jdk llvm

View File

@ -74,11 +74,8 @@ libc = "^0"
# Dependencies for WASM builds only
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "^0"
console_error_panic_hook = "^0"
wee_alloc = "^0"
js-sys = "^0"
wasm-bindgen-futures = "^0"
wasm-logger = "^0"
hashbrown = "^0"
lru = {version = "^0", features = ["hashbrown"] }
no-std-net = { path = "../external/no-std-net", features = ["serde"] }
@ -149,6 +146,9 @@ simplelog = { version = "^0", features=["test"] }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = "^0"
console_error_panic_hook = "^0"
wee_alloc = "^0"
wasm-logger = "^0"
### BUILD OPTIONS

View File

@ -1,4 +1,3 @@
use crate::api_logger::*;
use crate::attachment_manager::*;
use crate::dht::crypto::Crypto;
use crate::intf::*;
@ -10,6 +9,8 @@ cfg_if! {
if #[cfg(target_arch = "wasm32")] {
pub type UpdateCallback = Arc<dyn Fn(VeilidUpdate)>;
} else {
use crate::api_logger::*;
pub type UpdateCallback = Arc<dyn Fn(VeilidUpdate) + Send + Sync>;
}
}
@ -59,18 +60,23 @@ impl ServicesContext {
}
pub async fn startup(&mut self) -> Result<(), VeilidAPIError> {
let api_log_level: VeilidConfigLogLevel = self.config.get().api_log_level;
if api_log_level != VeilidConfigLogLevel::Off {
let log_level: VeilidConfigLogLevel = self.config.get().log_level;
if log_level != VeilidConfigLogLevel::Off {
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
// Logging is managed by application
} else {
ApiLogger::init(
api_log_level.to_level_filter(),
log_level.to_level_filter(),
self.update_callback.clone(),
)
.await;
for ig in crate::DEFAULT_LOG_IGNORE_LIST {
ApiLogger::add_filter_ignore_str(ig);
}
info!("Veilid API logging initialized");
info!("Veilid logging initialized");
}
}
}
info!("Veilid API starting up");
@ -165,7 +171,13 @@ impl ServicesContext {
info!("Veilid API shutdown complete");
// api logger terminate is idempotent
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
// Logging is managed by application
} else {
ApiLogger::terminate().await;
}
}
// send final shutdown update
(self.update_callback)(VeilidUpdate::Shutdown);

View File

@ -4,16 +4,6 @@ use crate::xx::*;
use core::sync::atomic::{AtomicI8, Ordering};
use js_sys::{global, Reflect};
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" {
// Use `js_namespace` here to bind `console.log(..)` instead of just

View File

@ -4,7 +4,9 @@
#[macro_use]
extern crate alloc;
#[cfg(not(target_arch = "wasm32"))]
mod api_logger;
mod attachment_manager;
mod callback_state_machine;
mod connection_manager;

View File

@ -169,7 +169,7 @@ fn config_callback(key: String) -> ConfigCallbackReturn {
match key.as_str() {
"program_name" => Ok(Box::new(String::from("Veilid"))),
"namespace" => Ok(Box::new(String::from(""))),
"api_log_level" => Ok(Box::new(VeilidConfigLogLevel::Off)),
"log_level" => Ok(Box::new(VeilidConfigLogLevel::Off)),
"capabilities.protocol_udp" => Ok(Box::new(true)),
"capabilities.protocol_connect_tcp" => Ok(Box::new(true)),
"capabilities.protocol_accept_tcp" => Ok(Box::new(true)),
@ -270,7 +270,7 @@ pub async fn test_config() {
let inner = vc.get();
assert_eq!(inner.program_name, String::from("Veilid"));
assert_eq!(inner.namespace, String::from(""));
assert_eq!(inner.api_log_level, VeilidConfigLogLevel::Off);
assert_eq!(inner.log_level, VeilidConfigLogLevel::Off);
assert_eq!(inner.capabilities.protocol_udp, true);
assert_eq!(inner.capabilities.protocol_connect_tcp, true);
assert_eq!(inner.capabilities.protocol_accept_tcp, true);

View File

@ -21,7 +21,6 @@ pub use network_manager::NetworkManager;
pub use routing_table::RoutingTable;
pub use rpc_processor::InfoAnswer;
use api_logger::*;
use core::fmt;
use core_context::{api_shutdown, VeilidCoreContext};
use rpc_processor::{RPCError, RPCProcessor};
@ -1219,9 +1218,16 @@ impl VeilidAPI {
}
// Change api logging level if it is enabled
pub async fn change_api_log_level(&self, log_level: VeilidConfigLogLevel) {
pub async fn change_log_level(&self, log_level: VeilidConfigLogLevel) {
cfg_if! {
if #[cfg(target_arch = "wasm32")] {
set_max_level(log_level.to_level_filter());
} else {
use api_logger::ApiLogger;
ApiLogger::change_log_level(log_level.to_level_filter());
}
}
}
////////////////////////////////////////////////////////////////
// Direct Node Access (pretty much for testing only)

View File

@ -206,7 +206,7 @@ impl Default for VeilidConfigLogLevel {
pub struct VeilidConfigInner {
pub program_name: String,
pub namespace: String,
pub api_log_level: VeilidConfigLogLevel,
pub log_level: VeilidConfigLogLevel,
pub capabilities: VeilidConfigCapabilities,
pub protected_store: VeilidConfigProtectedStore,
pub table_store: VeilidConfigTableStore,
@ -262,7 +262,7 @@ impl VeilidConfig {
let mut inner = self.inner.write();
get_config!(inner.program_name);
get_config!(inner.namespace);
get_config!(inner.api_log_level);
get_config!(inner.log_level);
get_config!(inner.capabilities.protocol_udp);
get_config!(inner.capabilities.protocol_connect_tcp);
get_config!(inner.capabilities.protocol_accept_tcp);

View File

@ -8,6 +8,10 @@ use wasm_bindgen_test::*;
wasm_bindgen_test_configure!();
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
static SETUP_ONCE: Once = Once::new();
pub fn setup() -> () {
SETUP_ONCE.call_once(|| {

View File

@ -8,6 +8,10 @@ use wasm_bindgen_test::*;
wasm_bindgen_test_configure!(run_in_browser);
extern crate wee_alloc;
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
static SETUP_ONCE: Once = Once::new();
pub fn setup() -> () {
SETUP_ONCE.call_once(|| {

View File

@ -7,7 +7,7 @@ Future<VeilidConfig> getDefaultVeilidConfig() async {
return VeilidConfig(
programName: "Veilid Plugin Test",
namespace: "",
apiLogLevel: VeilidConfigLogLevel.info,
logLevel: VeilidConfigLogLevel.info,
capabilities: VeilidConfigCapabilities(
protocolUDP: !kIsWeb,
protocolConnectTCP: !kIsWeb,
@ -24,12 +24,16 @@ Future<VeilidConfig> getDefaultVeilidConfig() async {
delete: false,
),
tableStore: VeilidConfigTableStore(
directory: p.join((await getApplicationSupportDirectory()).absolute.path,
directory: kIsWeb
? ""
: p.join((await getApplicationSupportDirectory()).absolute.path,
"table_store"),
delete: false,
),
blockStore: VeilidConfigBlockStore(
directory: p.join((await getApplicationSupportDirectory()).absolute.path,
directory: kIsWeb
? ""
: p.join((await getApplicationSupportDirectory()).absolute.path,
"block_store"),
delete: false,
),

View File

@ -705,7 +705,7 @@ class VeilidConfigCapabilities {
class VeilidConfig {
String programName;
String namespace;
VeilidConfigLogLevel apiLogLevel;
VeilidConfigLogLevel logLevel;
VeilidConfigCapabilities capabilities;
VeilidConfigProtectedStore protectedStore;
VeilidConfigTableStore tableStore;
@ -715,7 +715,7 @@ class VeilidConfig {
VeilidConfig({
required this.programName,
required this.namespace,
required this.apiLogLevel,
required this.logLevel,
required this.capabilities,
required this.protectedStore,
required this.tableStore,
@ -727,7 +727,7 @@ class VeilidConfig {
return {
'program_name': programName,
'namespace': namespace,
'api_log_level': apiLogLevel.json,
'log_level': logLevel.json,
'capabilities': capabilities.json,
'protected_store': protectedStore.json,
'table_store': tableStore.json,
@ -739,7 +739,7 @@ class VeilidConfig {
VeilidConfig.fromJson(Map<String, dynamic> json)
: programName = json['program_name'],
namespace = json['namespace'],
apiLogLevel = json['api_log_level'],
logLevel = json['log_level'],
capabilities = VeilidConfigCapabilities.fromJson(json['capabilities']),
protectedStore =
VeilidConfigProtectedStore.fromJson(json['protected_store']),
@ -993,7 +993,7 @@ abstract class Veilid {
Stream<VeilidUpdate> startupVeilidCore(VeilidConfig config);
Future<VeilidState> getVeilidState();
Future<void> changeApiLogLevel(VeilidConfigLogLevel logLevel);
Future<void> changeLogLevel(VeilidConfigLogLevel logLevel);
Future<void> shutdownVeilidCore();
Future<String> debug(String command);
String veilidVersionString();

View File

@ -35,9 +35,9 @@ typedef _StartupVeilidCoreDart = void Function(int, Pointer<Utf8>);
// fn get_veilid_state(port: i64)
typedef _GetVeilidStateC = Void Function(Int64);
typedef _GetVeilidStateDart = void Function(int);
// fn change_api_log_level(port: i64, log_level: FfiStr)
typedef _ChangeApiLogLevelC = Void Function(Int64, Pointer<Utf8>);
typedef _ChangeApiLogLevelDart = void Function(int, Pointer<Utf8>);
// fn change_log_level(port: i64, log_level: FfiStr)
typedef _ChangeLogLevelC = Void Function(Int64, Pointer<Utf8>);
typedef _ChangeLogLevelDart = void Function(int, Pointer<Utf8>);
// fn debug(port: i64, log_level: FfiStr)
typedef _DebugC = Void Function(Int64, Pointer<Utf8>);
typedef _DebugDart = void Function(int, Pointer<Utf8>);
@ -243,7 +243,7 @@ class VeilidFFI implements Veilid {
final _FreeStringDart _freeString;
final _StartupVeilidCoreDart _startupVeilidCore;
final _GetVeilidStateDart _getVeilidState;
final _ChangeApiLogLevelDart _changeApiLogLevel;
final _ChangeLogLevelDart _changeLogLevel;
final _ShutdownVeilidCoreDart _shutdownVeilidCore;
final _DebugDart _debug;
final _VeilidVersionStringDart _veilidVersionString;
@ -259,9 +259,9 @@ class VeilidFFI implements Veilid {
_getVeilidState =
dylib.lookupFunction<_GetVeilidStateC, _GetVeilidStateDart>(
'get_veilid_state'),
_changeApiLogLevel =
dylib.lookupFunction<_ChangeApiLogLevelC, _ChangeApiLogLevelDart>(
'change_api_log_level'),
_changeLogLevel =
dylib.lookupFunction<_ChangeLogLevelC, _ChangeLogLevelDart>(
'change_log_level'),
_shutdownVeilidCore =
dylib.lookupFunction<_ShutdownVeilidCoreC, _ShutdownVeilidCoreDart>(
'shutdown_veilid_core'),
@ -299,11 +299,11 @@ class VeilidFFI implements Veilid {
}
@override
Future<void> changeApiLogLevel(VeilidConfigLogLevel logLevel) async {
Future<void> changeLogLevel(VeilidConfigLogLevel logLevel) async {
var nativeLogLevel = logLevel.json.toNativeUtf8();
final recvPort = ReceivePort("change_api_log_level");
final recvPort = ReceivePort("change_log_level");
final sendPort = recvPort.sendPort;
_changeApiLogLevel(sendPort.nativePort, nativeLogLevel);
_changeLogLevel(sendPort.nativePort, nativeLogLevel);
malloc.free(nativeLogLevel);
return processFutureVoid(recvPort.first);
}

View File

@ -20,7 +20,7 @@ class VeilidJS implements Veilid {
}
@override
Future<void> changeApiLogLevel(VeilidConfigLogLevel logLevel) {
Future<void> changeLogLevel(VeilidConfigLogLevel logLevel) {
throw UnimplementedError();
}

View File

@ -48,7 +48,7 @@ flutter:
# To add assets to your plugin package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - assets/foo.wasm
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see

View File

@ -143,12 +143,12 @@ pub extern "C" fn get_veilid_state(port: i64) {
}
#[no_mangle]
pub extern "C" fn change_api_log_level(port: i64, log_level: FfiStr) {
pub extern "C" fn change_log_level(port: i64, log_level: FfiStr) {
let log_level = log_level.into_opt_string();
DartIsolateWrapper::new(port).spawn_result_json(async move {
let log_level: veilid_core::VeilidConfigLogLevel = deserialize_opt_json(log_level)?;
let veilid_api = get_veilid_api().await?;
veilid_api.change_api_log_level(log_level).await;
veilid_api.change_log_level(log_level).await;
APIRESULT_VOID
});
}

View File

@ -6,7 +6,7 @@ cfg_if! {
mod dart_isolate_wrapper;
mod dart_serialize;
} else {
mod wasm;
//mod wasm;
}
}

View File

@ -746,7 +746,7 @@ impl Settings {
} else {
format!("subnode{}", inner.testing.subnode_index)
})),
"api_log_level" => Ok(Box::new(veilid_core::VeilidConfigLogLevel::Off)),
"log_level" => Ok(Box::new(veilid_core::VeilidConfigLogLevel::Off)),
"capabilities.protocol_udp" => Ok(Box::new(true)),
"capabilities.protocol_connect_tcp" => Ok(Box::new(true)),
"capabilities.protocol_accept_tcp" => Ok(Box::new(true)),

View File

@ -9,7 +9,7 @@ license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)"
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "^0"
wasm-bindgen = { version = "^0", features = ["serde-serialize"] }
console_error_panic_hook = "^0"
wee_alloc = "^0"
wasm-logger = "^0"
@ -18,6 +18,11 @@ veilid-core = { path = "../veilid-core" }
cfg-if = "^1"
wasm-bindgen-futures = "^0"
js-sys = "^0"
serde_json = "^1"
serde = "^1"
lazy_static = "^1"
send_wrapper = "^0"
futures-util = { version = "^0", default_features = false, features = ["alloc"] }
[dev-dependencies]
wasm-bindgen-test = "^0"

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)
}

377
veilid-wasm/wasm-sourcemap.py Executable file
View File

@ -0,0 +1,377 @@
#!/usr/bin/env python3
# Copyright 2018 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
"""Utility tools that extracts DWARF information encoded in a wasm output
produced by the LLVM tools, and encodes it as a wasm source map. Additionally,
it can collect original sources, change files prefixes, and strip debug
sections from a wasm file.
"""
import argparse
from collections import OrderedDict
import json
import logging
from math import floor, log
import os
import re
from subprocess import Popen, PIPE
from pathlib import Path
import sys
__scriptdir__ = os.path.dirname(os.path.abspath(__file__))
__rootdir__ = os.path.dirname(__scriptdir__)
sys.path.append(__rootdir__)
logger = logging.getLogger('wasm-sourcemap')
def parse_args():
parser = argparse.ArgumentParser(
prog='wasm-sourcemap.py', description=__doc__)
parser.add_argument('wasm', help='wasm file')
parser.add_argument('-o', '--output', help='output source map')
parser.add_argument('-p', '--prefix', nargs='*',
help='replace source debug filename prefix for source map', default=[])
parser.add_argument('-s', '--sources', action='store_true',
help='read and embed source files from file system into source map')
parser.add_argument('-l', '--load-prefix', nargs='*',
help='replace source debug filename prefix for reading sources from file system (see also --sources)', default=[])
parser.add_argument('-w', nargs='?', help='set output wasm file')
parser.add_argument('-x', '--strip', action='store_true',
help='removes debug and linking sections')
parser.add_argument('-u', '--source-map-url', nargs='?',
help='specifies sourceMappingURL section contest')
parser.add_argument(
'--dwarfdump', help="path to llvm-dwarfdump executable")
parser.add_argument('--dwarfdump-output', nargs='?',
help=argparse.SUPPRESS)
parser.add_argument(
'--basepath', help='base path for source files, which will be relative to this')
return parser.parse_args()
class Prefixes:
def __init__(self, args):
prefixes = []
for p in args:
if '=' in p:
prefix, replacement = p.split('=')
prefixes.append({'prefix': prefix, 'replacement': replacement})
else:
prefixes.append({'prefix': p, 'replacement': None})
self.prefixes = prefixes
self.cache = {}
def resolve(self, name):
if name in self.cache:
return self.cache[name]
for p in self.prefixes:
if name.startswith(p['prefix']):
if p['replacement'] is None:
result = name[len(p['prefix'])::]
else:
result = p['replacement'] + name[len(p['prefix'])::]
break
self.cache[name] = result
return result
# SourceMapPrefixes contains resolver for file names that are:
# - "sources" is for names that output to source maps JSON
# - "load" is for paths that used to load source text
class SourceMapPrefixes:
def __init__(self, sources, load):
self.sources = sources
self.load = load
def provided(self):
return bool(self.sources.prefixes or self.load.prefixes)
def encode_vlq(n):
VLQ_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
x = (n << 1) if n >= 0 else ((-n << 1) + 1)
result = ""
while x > 31:
result = result + VLQ_CHARS[32 + (x & 31)]
x = x >> 5
return result + VLQ_CHARS[x]
def read_var_uint(wasm, pos):
n = 0
shift = 0
b = ord(wasm[pos:pos + 1])
pos = pos + 1
while b >= 128:
n = n | ((b - 128) << shift)
b = ord(wasm[pos:pos + 1])
pos = pos + 1
shift += 7
return n + (b << shift), pos
def strip_debug_sections(wasm):
logger.debug('Strip debug sections')
pos = 8
stripped = wasm[:pos]
while pos < len(wasm):
section_start = pos
section_id, pos_ = read_var_uint(wasm, pos)
section_size, section_body = read_var_uint(wasm, pos_)
pos = section_body + section_size
if section_id == 0:
name_len, name_pos = read_var_uint(wasm, section_body)
name_end = name_pos + name_len
name = wasm[name_pos:name_end]
if name == "linking" or name == "sourceMappingURL" or name.startswith("reloc..debug_") or name.startswith(".debug_"):
continue # skip debug related sections
stripped = stripped + wasm[section_start:pos]
return stripped
def encode_uint_var(n):
result = bytearray()
while n > 127:
result.append(128 | (n & 127))
n = n >> 7
result.append(n)
return bytes(result)
def append_source_mapping(wasm, url):
logger.debug('Append sourceMappingURL section')
section_name = "sourceMappingURL"
section_content = encode_uint_var(
len(section_name)) + section_name + encode_uint_var(len(url)) + url
return wasm + encode_uint_var(0) + encode_uint_var(len(section_content)) + section_content
def get_code_section_offset(wasm):
logger.debug('Read sections index')
pos = 8
while pos < len(wasm):
section_id, pos_ = read_var_uint(wasm, pos)
section_size, pos = read_var_uint(wasm, pos_)
if section_id == 10:
return pos
pos = pos + section_size
def remove_dead_entries(entries):
# Remove entries for dead functions. It is a heuristics to ignore data if the
# function starting address near to 0 (is equal to its size field length).
block_start = 0
cur_entry = 0
while cur_entry < len(entries):
if not entries[cur_entry]['eos']:
cur_entry += 1
continue
fn_start = entries[block_start]['address']
# Calculate the LEB encoded function size (including size field)
fn_size_length = floor(
log(entries[cur_entry]['address'] - fn_start + 1, 128)) + 1
min_live_offset = 1 + fn_size_length # 1 byte is for code section entries
if fn_start < min_live_offset:
# Remove dead code debug info block.
del entries[block_start:cur_entry + 1]
cur_entry = block_start
continue
cur_entry += 1
block_start = cur_entry
def read_dwarf_entries(wasm, options):
if options.dwarfdump_output:
output = Path(options.dwarfdump_output).read_bytes()
elif options.dwarfdump:
logger.debug('Reading DWARF information from %s' % wasm)
if not os.path.exists(options.dwarfdump):
logger.error('llvm-dwarfdump not found: ' + options.dwarfdump)
sys.exit(1)
process = Popen([options.dwarfdump, '-debug-info',
'-debug-line', '--recurse-depth=0', wasm], stdout=PIPE)
output, err = process.communicate()
exit_code = process.wait()
if exit_code != 0:
logger.error(
'Error during llvm-dwarfdump execution (%s)' % exit_code)
sys.exit(1)
else:
logger.error('Please specify either --dwarfdump or --dwarfdump-output')
sys.exit(1)
entries = []
debug_line_chunks = re.split(
r"debug_line\[(0x[0-9a-f]*)\]", output.decode('utf-8'))
maybe_debug_info_content = debug_line_chunks[0]
for i in range(1, len(debug_line_chunks), 2):
stmt_list = debug_line_chunks[i]
comp_dir_match = re.search(r"DW_AT_stmt_list\s+\(" + stmt_list + r"\)\s+" +
r"DW_AT_comp_dir\s+\(\"([^\"]+)", maybe_debug_info_content)
comp_dir = comp_dir_match.group(
1) if comp_dir_match is not None else ""
line_chunk = debug_line_chunks[i + 1]
# include_directories[ 1] = "/Users/yury/Work/junk/sqlite-playground/src"
# file_names[ 1]:
# name: "playground.c"
# dir_index: 1
# mod_time: 0x00000000
# length: 0x00000000
#
# Address Line Column File ISA Discriminator Flags
# ------------------ ------ ------ ------ --- ------------- -------------
# 0x0000000000000006 22 0 1 0 0 is_stmt
# 0x0000000000000007 23 10 1 0 0 is_stmt prologue_end
# 0x000000000000000f 23 3 1 0 0
# 0x0000000000000010 23 3 1 0 0 end_sequence
# 0x0000000000000011 28 0 1 0 0 is_stmt
include_directories = {'0': comp_dir}
for dir in re.finditer(r"include_directories\[\s*(\d+)\] = \"([^\"]*)", line_chunk):
include_directories[dir.group(1)] = dir.group(2)
files = {}
for file in re.finditer(r"file_names\[\s*(\d+)\]:\s+name: \"([^\"]*)\"\s+dir_index: (\d+)", line_chunk):
dir = include_directories[file.group(3)]
file_path = (dir + '/' if file.group(2)
[0] != '/' else '') + file.group(2)
files[file.group(1)] = file_path
for line in re.finditer(r"\n0x([0-9a-f]+)\s+(\d+)\s+(\d+)\s+(\d+)(.*?end_sequence)?", line_chunk):
entry = {'address': int(line.group(1), 16), 'line': int(line.group(2)), 'column': int(
line.group(3)), 'file': files[line.group(4)], 'eos': line.group(5) is not None}
if not entry['eos']:
entries.append(entry)
else:
# move end of function to the last END operator
entry['address'] -= 1
if entries[-1]['address'] == entry['address']:
# last entry has the same address, reusing
entries[-1]['eos'] = True
else:
entries.append(entry)
remove_dead_entries(entries)
# return entries sorted by the address field
return sorted(entries, key=lambda entry: entry['address'])
def normalize_path(path):
return path.replace('\\', '/').replace('//', '/')
def build_sourcemap(entries, code_section_offset, prefixes, collect_sources, base_path):
sources = []
sources_content = [] if collect_sources else None
mappings = []
sources_map = {}
last_address = 0
last_source_id = 0
last_line = 1
last_column = 1
for entry in entries:
line = entry['line']
column = entry['column']
# ignore entries with line 0
if line == 0:
continue
# start at least at column 1
if column == 0:
column = 1
address = entry['address'] + code_section_offset
file_name = entry['file']
file_name = normalize_path(file_name)
# if prefixes were provided, we use that; otherwise, we emit a relative
# path
if prefixes.provided():
source_name = prefixes.sources.resolve(file_name)
else:
try:
file_name = os.path.relpath(file_name, base_path)
except ValueError:
file_name = os.path.abspath(file_name)
file_name = normalize_path(file_name)
source_name = file_name
if source_name not in sources_map:
source_id = len(sources)
sources_map[source_name] = source_id
sources.append(source_name)
if collect_sources:
load_name = prefixes.load.resolve(file_name)
try:
with open(load_name, 'r') as infile:
source_content = infile.read()
sources_content.append(source_content)
except IOError:
print('Failed to read source: %s' % load_name)
sources_content.append(None)
else:
source_id = sources_map[source_name]
address_delta = address - last_address
source_id_delta = source_id - last_source_id
line_delta = line - last_line
column_delta = column - last_column
mappings.append(encode_vlq(address_delta) + encode_vlq(source_id_delta) +
encode_vlq(line_delta) + encode_vlq(column_delta))
last_address = address
last_source_id = source_id
last_line = line
last_column = column
return OrderedDict([('version', 3),
('names', []),
('sources', sources),
('sourcesContent', sources_content),
('mappings', ','.join(mappings))])
def main():
options = parse_args()
wasm_input = options.wasm
with open(wasm_input, 'rb') as infile:
wasm = infile.read()
entries = read_dwarf_entries(wasm_input, options)
code_section_offset = get_code_section_offset(wasm)
prefixes = SourceMapPrefixes(sources=Prefixes(
options.prefix), load=Prefixes(options.load_prefix))
logger.debug('Saving to %s' % options.output)
map = build_sourcemap(entries, code_section_offset,
prefixes, options.sources, options.basepath)
with open(options.output, 'w') as outfile:
json.dump(map, outfile, separators=(',', ':'))
if options.strip:
wasm = strip_debug_sections(wasm)
if options.source_map_url:
wasm = append_source_mapping(wasm, options.source_map_url)
if options.w:
logger.debug('Saving wasm to %s' % options.w)
with open(options.w, 'wb') as outfile:
outfile.write(wasm)
logger.debug('Done')
return 0
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG if os.environ.get(
'EMCC_DEBUG') else logging.INFO)
sys.exit(main())

16
veilid-wasm/wasm_build.sh Executable file
View File

@ -0,0 +1,16 @@
#!/bin/bash
SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
if [[ "$1" == "release" ]]; then
RELEASE=-r
GENERATE_SOURCE_MAP=
else
RELEASE=
RUSTFLAGS="-O -g"
GENERATE_SOURCE_MAP="./wasm-sourcemap.py ../target/wasm32-unknown-unknown/debug/veilid_flutter.wasm -o ../target/wasm32-unknown-unknown/debug/veilid_flutter.wasm.map --dwarfdump `which llvm-dwarfdump`"
fi
pushd $SCRIPTDIR 2> /dev/null
cargo build --target wasm32-unknown-unknown $RELEASE
$GENERATE_SOURCE_MAP
popd 2> /dev/null