initial import of main veilid core
This commit is contained in:
1
veilid-core/src/intf/wasm/block_store.rs
Normal file
1
veilid-core/src/intf/wasm/block_store.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
12
veilid-core/src/intf/wasm/mod.rs
Normal file
12
veilid-core/src/intf/wasm/mod.rs
Normal file
@@ -0,0 +1,12 @@
|
||||
mod block_store;
|
||||
mod network;
|
||||
mod protected_store;
|
||||
mod system;
|
||||
mod table_store;
|
||||
pub mod utils;
|
||||
|
||||
pub use block_store::*;
|
||||
pub use network::*;
|
||||
pub use protected_store::*;
|
||||
pub use system::*;
|
||||
pub use table_store::*;
|
179
veilid-core/src/intf/wasm/network/mod.rs
Normal file
179
veilid-core/src/intf/wasm/network/mod.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
mod protocol;
|
||||
|
||||
use crate::intf::*;
|
||||
use crate::network_manager::*;
|
||||
use crate::routing_table::*;
|
||||
use crate::*;
|
||||
use protocol::ws::WebsocketProtocolHandler;
|
||||
pub use protocol::*;
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
struct NetworkInner {
|
||||
network_manager: NetworkManager,
|
||||
stop_network: Eventual,
|
||||
network_needs_restart: bool,
|
||||
//join_handle: TryJoin?
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Network {
|
||||
inner: Arc<Mutex<NetworkInner>>,
|
||||
}
|
||||
|
||||
impl Network {
|
||||
fn new_inner(network_manager: NetworkManager) -> NetworkInner {
|
||||
NetworkInner {
|
||||
network_manager: network_manager,
|
||||
stop_network: Eventual::new(),
|
||||
network_needs_restart: false,
|
||||
//join_handle: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(network_manager: NetworkManager) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(Self::new_inner(network_manager))),
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
async fn send_data_to_existing_connection(
|
||||
&self,
|
||||
descriptor: &ConnectionDescriptor,
|
||||
data: Vec<u8>,
|
||||
) -> Result<Option<Vec<u8>>, String> {
|
||||
match descriptor.protocol_type() {
|
||||
ProtocolType::UDP => return Err("no support for udp protocol".to_owned()),
|
||||
ProtocolType::TCP => return Err("no support for tcp protocol".to_owned()),
|
||||
ProtocolType::WS | ProtocolType::WSS => {
|
||||
// find an existing connection in the connection table if one exists
|
||||
let network_manager = self.inner.lock().network_manager.clone();
|
||||
if let Some(entry) = network_manager
|
||||
.connection_table()
|
||||
.get_connection(&descriptor)
|
||||
{
|
||||
// connection exists, send over it
|
||||
entry
|
||||
.conn
|
||||
.send(data)
|
||||
.await
|
||||
.map_err(|_| "failed to send ws message".to_owned())?;
|
||||
// Data was consumed
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
// connection or local socket didn't exist, we'll need to use dialinfo to create one
|
||||
// Pass the data back out so we don't own it any more
|
||||
Ok(Some(data))
|
||||
}
|
||||
|
||||
pub async fn send_data_unbound_to_dial_info(
|
||||
&self,
|
||||
dial_info: &DialInfo,
|
||||
data: Vec<u8>,
|
||||
) -> Result<(), String> {
|
||||
let network_manager = self.inner.lock().network_manager.clone();
|
||||
|
||||
match &dial_info {
|
||||
DialInfo::UDP(_) => return Err("no support for UDP protocol".to_owned()),
|
||||
DialInfo::TCP(_) => return Err("no support for TCP protocol".to_owned()),
|
||||
DialInfo::WS(_) => Err("WS protocol does not support unbound messages".to_owned()),
|
||||
DialInfo::WSS(_) => Err("WSS protocol does not support unbound messages".to_owned()),
|
||||
}
|
||||
}
|
||||
pub async fn send_data_to_dial_info(
|
||||
&self,
|
||||
dial_info: &DialInfo,
|
||||
data: Vec<u8>,
|
||||
) -> Result<(), String> {
|
||||
let network_manager = self.inner.lock().network_manager.clone();
|
||||
|
||||
let conn = match &dial_info {
|
||||
DialInfo::UDP(_) => return Err("no support for UDP protocol".to_owned()),
|
||||
DialInfo::TCP(_) => return Err("no support for TCP protocol".to_owned()),
|
||||
DialInfo::WS(_) => WebsocketProtocolHandler::connect(network_manager, dial_info)
|
||||
.await
|
||||
.map_err(|_| "failed to connect to WS dial info".to_owned())?,
|
||||
DialInfo::WSS(_) => WebsocketProtocolHandler::connect(network_manager, dial_info)
|
||||
.await
|
||||
.map_err(|_| "failed to connect to WSS dial info".to_owned())?,
|
||||
};
|
||||
|
||||
conn.send(data)
|
||||
.await
|
||||
.map_err(|_| "failed to send data to dial info".to_owned())
|
||||
}
|
||||
|
||||
pub async fn send_data(&self, node_ref: NodeRef, data: Vec<u8>) -> Result<(), String> {
|
||||
let dial_info = node_ref.dial_info();
|
||||
let descriptor = node_ref.last_connection();
|
||||
|
||||
// First try to send data to the last socket we've seen this peer on
|
||||
let di_data = if let Some(descriptor) = descriptor {
|
||||
match self
|
||||
.clone()
|
||||
.send_data_to_existing_connection(&descriptor, data)
|
||||
.await?
|
||||
{
|
||||
None => {
|
||||
return Ok(());
|
||||
}
|
||||
Some(d) => d,
|
||||
}
|
||||
} else {
|
||||
data
|
||||
};
|
||||
|
||||
// If that fails, try to make a connection or reach out to the peer via its dial info
|
||||
if let Some(di) = dial_info {
|
||||
self.clone().send_data_to_dial_info(&di, di_data).await
|
||||
} else {
|
||||
Err("couldn't send data, no dial info or peer address".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////
|
||||
|
||||
pub async fn startup(&self) -> Result<(), String> {
|
||||
//let network_manager = self.inner.lock().network_manager.clone();
|
||||
//let config_shared = network_manager.core().config();
|
||||
//let config = config_shared.get();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn needs_restart(&self) -> bool {
|
||||
self.inner.lock().network_needs_restart
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) {
|
||||
trace!("stopping network");
|
||||
|
||||
// Reset state
|
||||
let network_manager = self.inner.lock().network_manager.clone();
|
||||
let routing_table = network_manager.routing_table();
|
||||
|
||||
// Drop all dial info
|
||||
routing_table.clear_local_dial_info();
|
||||
routing_table.clear_public_dial_info();
|
||||
|
||||
// Cancels all async background tasks by dropping join handles
|
||||
*self.inner.lock() = Self::new_inner(network_manager);
|
||||
|
||||
trace!("network stopped");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
pub fn get_network_class(&self) -> NetworkClass {
|
||||
// xxx eventually detect tor browser?
|
||||
return NetworkClass::WebApp;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
pub async fn tick(&self) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
48
veilid-core/src/intf/wasm/network/protocol/mod.rs
Normal file
48
veilid-core/src/intf/wasm/network/protocol/mod.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
pub mod wrtc;
|
||||
pub mod ws;
|
||||
|
||||
use crate::veilid_api::ProtocolType;
|
||||
use crate::xx::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DummyNetworkConnection {}
|
||||
|
||||
impl DummyNetworkConnection {
|
||||
pub fn protocol_type(&self) -> ProtocolType {
|
||||
ProtocolType::UDP
|
||||
}
|
||||
pub fn send(&self, _message: Vec<u8>) -> SystemPinBoxFuture<Result<(), ()>> {
|
||||
Box::pin(async { Ok(()) })
|
||||
}
|
||||
pub fn recv(&self) -> SystemPinBoxFuture<Result<Vec<u8>, ()>> {
|
||||
Box::pin(async { Ok(Vec::new()) })
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum NetworkConnection {
|
||||
Dummy(DummyNetworkConnection),
|
||||
WS(ws::WebsocketNetworkConnection),
|
||||
//WebRTC(wrtc::WebRTCNetworkConnection),
|
||||
}
|
||||
|
||||
impl NetworkConnection {
|
||||
pub fn protocol_type(&self) -> ProtocolType {
|
||||
match self {
|
||||
Self::Dummy(d) => d.protocol_type(),
|
||||
Self::WS(w) => w.protocol_type(),
|
||||
}
|
||||
}
|
||||
pub fn send(&self, message: Vec<u8>) -> SystemPinBoxFuture<Result<(), ()>> {
|
||||
match self {
|
||||
Self::Dummy(d) => d.send(message),
|
||||
Self::WS(w) => w.send(message),
|
||||
}
|
||||
}
|
||||
pub fn recv(&self) -> SystemPinBoxFuture<Result<Vec<u8>, ()>> {
|
||||
match self {
|
||||
Self::Dummy(d) => d.recv(),
|
||||
Self::WS(w) => w.recv(),
|
||||
}
|
||||
}
|
||||
}
|
0
veilid-core/src/intf/wasm/network/protocol/wrtc.rs
Normal file
0
veilid-core/src/intf/wasm/network/protocol/wrtc.rs
Normal file
116
veilid-core/src/intf/wasm/network/protocol/ws.rs
Normal file
116
veilid-core/src/intf/wasm/network/protocol/ws.rs
Normal file
@@ -0,0 +1,116 @@
|
||||
use crate::intf::*;
|
||||
use crate::network_manager::{NetworkManager, MAX_MESSAGE_SIZE};
|
||||
use crate::*;
|
||||
use alloc::fmt;
|
||||
use futures_util::stream::StreamExt;
|
||||
use web_sys::WebSocket;
|
||||
use ws_stream_wasm::*;
|
||||
|
||||
struct WebsocketNetworkConnectionInner {
|
||||
_ws_meta: WsMeta,
|
||||
ws_stream: WsStream,
|
||||
ws: WebSocket,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WebsocketNetworkConnection {
|
||||
tls: bool,
|
||||
inner: Arc<Mutex<WebsocketNetworkConnectionInner>>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for WebsocketNetworkConnection {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", core::any::type_name::<Self>())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for WebsocketNetworkConnection {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.tls == other.tls && Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for WebsocketNetworkConnection {}
|
||||
|
||||
impl WebsocketNetworkConnection {
|
||||
pub fn new(tls: bool, ws_meta: WsMeta, ws_stream: WsStream) -> Self {
|
||||
let ws = ws_stream.wrapped().clone();
|
||||
Self {
|
||||
tls: tls,
|
||||
inner: Arc::new(Mutex::new(WebsocketNetworkConnectionInner {
|
||||
_ws_meta: ws_meta,
|
||||
ws_stream: ws_stream,
|
||||
ws: ws,
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WebsocketNetworkConnection {
|
||||
pub fn protocol_type(&self) -> ProtocolType {
|
||||
if self.tls {
|
||||
ProtocolType::WSS
|
||||
} else {
|
||||
ProtocolType::WS
|
||||
}
|
||||
}
|
||||
pub fn send(&self, message: Vec<u8>) -> SystemPinBoxFuture<Result<(), ()>> {
|
||||
let inner = self.inner.clone();
|
||||
Box::pin(async move {
|
||||
if message.len() > MAX_MESSAGE_SIZE {
|
||||
return Err(());
|
||||
}
|
||||
inner.lock().ws.send_with_u8_array(&message).map_err(drop)
|
||||
})
|
||||
}
|
||||
pub fn recv(&self) -> SystemPinBoxFuture<Result<Vec<u8>, ()>> {
|
||||
let inner = self.inner.clone();
|
||||
Box::pin(async move {
|
||||
let out = match inner.lock().ws_stream.next().await {
|
||||
Some(WsMessage::Binary(v)) => v,
|
||||
_ => {
|
||||
trace!("websocket recv failed");
|
||||
return Err(());
|
||||
}
|
||||
};
|
||||
if out.len() > MAX_MESSAGE_SIZE {
|
||||
Err(())
|
||||
} else {
|
||||
Ok(out)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
///
|
||||
|
||||
pub struct WebsocketProtocolHandler {}
|
||||
|
||||
impl WebsocketProtocolHandler {
|
||||
pub async fn connect(
|
||||
network_manager: NetworkManager,
|
||||
dial_info: &DialInfo,
|
||||
) -> Result<NetworkConnection, ()> {
|
||||
let url = dial_info.to_url_string(None);
|
||||
let (tls, host, port, protocol_type) = match dial_info {
|
||||
DialInfo::WS(ws) => (false, ws.fqdn.clone(), ws.port, ProtocolType::WS),
|
||||
DialInfo::WSS(wss) => (true, wss.fqdn.clone(), wss.port, ProtocolType::WSS),
|
||||
_ => return Err(()),
|
||||
};
|
||||
|
||||
let peer_addr = PeerAddress::new(Address::from_str(&host)?, port, protocol_type);
|
||||
|
||||
let (ws, wsio) = match WsMeta::connect(url, None).await {
|
||||
Ok(conn) => conn,
|
||||
Err(_) => return Err(()),
|
||||
};
|
||||
|
||||
let conn = NetworkConnection::WS(WebsocketNetworkConnection::new(tls, ws, wsio));
|
||||
network_manager
|
||||
.on_new_connection(ConnectionDescriptor::new_no_local(peer_addr), conn.clone())
|
||||
.await?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
}
|
185
veilid-core/src/intf/wasm/protected_store.rs
Normal file
185
veilid-core/src/intf/wasm/protected_store.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
use super::utils;
|
||||
use crate::xx::*;
|
||||
use js_sys::*;
|
||||
use wasm_bindgen_futures::*;
|
||||
use web_sys::*;
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch, js_name = setPassword, js_namespace = ["global", "wasmhost", "keytar"])]
|
||||
fn keytar_setPassword(service: &str, account: &str, password: &str)
|
||||
-> Result<Promise, JsValue>;
|
||||
#[wasm_bindgen(catch, js_name = getPassword, js_namespace = ["global", "wasmhost", "keytar"])]
|
||||
fn keytar_getPassword(service: &str, account: &str) -> Result<Promise, JsValue>;
|
||||
#[wasm_bindgen(catch, js_name = deletePassword, js_namespace = ["global", "wasmhost", "keytar"])]
|
||||
fn keytar_deletePassword(service: &str, account: &str) -> Result<Promise, JsValue>;
|
||||
}
|
||||
|
||||
fn keyring_name(namespace: &str) -> String {
|
||||
if namespace.len() == 0 {
|
||||
"veilid".to_owned()
|
||||
} else {
|
||||
format!("veilid_{}", namespace)
|
||||
}
|
||||
}
|
||||
|
||||
fn browser_key_name(namespace: &str, key: &str) -> String {
|
||||
if namespace.len() == 0 {
|
||||
format!("__veilid_secret_{}", key)
|
||||
} else {
|
||||
format!("__veilid_{}_secret_{}", namespace, key)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn save_user_secret_string(
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
value: &str,
|
||||
) -> Result<bool, String> {
|
||||
if utils::is_nodejs() {
|
||||
let prev = match JsFuture::from(
|
||||
keytar_getPassword(keyring_name(namespace).as_str(), key)
|
||||
.map_err(|_| "exception thrown".to_owned())?,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => v.is_truthy(),
|
||||
Err(_) => false,
|
||||
};
|
||||
|
||||
match JsFuture::from(
|
||||
keytar_setPassword(keyring_name(namespace).as_str(), key, value)
|
||||
.map_err(|_| "exception thrown".to_owned())?,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(_) => return Err("Failed to set password".to_owned()),
|
||||
}
|
||||
|
||||
Ok(prev)
|
||||
} else if utils::is_browser() {
|
||||
let win = match window() {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
return Err("failed to get window".to_owned());
|
||||
}
|
||||
};
|
||||
|
||||
let ls = match win
|
||||
.local_storage()
|
||||
.map_err(|_| "exception getting local storage".to_owned())?
|
||||
{
|
||||
Some(l) => l,
|
||||
None => {
|
||||
return Err("failed to get local storage".to_owned());
|
||||
}
|
||||
};
|
||||
|
||||
let vkey = browser_key_name(namespace, key);
|
||||
|
||||
let prev = match ls
|
||||
.get_item(&vkey)
|
||||
.map_err(|_| "exception_thrown".to_owned())?
|
||||
{
|
||||
Some(_) => true,
|
||||
None => false,
|
||||
};
|
||||
|
||||
ls.set_item(&vkey, value)
|
||||
.map_err(|_| "exception_thrown".to_owned())?;
|
||||
|
||||
Ok(prev)
|
||||
} else {
|
||||
Err("unimplemented".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn load_user_secret_string(namespace: &str, key: &str) -> Result<Option<String>, String> {
|
||||
if utils::is_nodejs() {
|
||||
let prev = match JsFuture::from(
|
||||
keytar_getPassword(keyring_name(namespace).as_str(), key)
|
||||
.map_err(|_| "exception thrown".to_owned())?,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(p) => p,
|
||||
Err(_) => JsValue::UNDEFINED,
|
||||
};
|
||||
|
||||
if prev.is_undefined() || prev.is_null() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(prev.as_string())
|
||||
} else if utils::is_browser() {
|
||||
let win = match window() {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
return Err("failed to get window".to_owned());
|
||||
}
|
||||
};
|
||||
|
||||
let ls = match win
|
||||
.local_storage()
|
||||
.map_err(|_| "exception getting local storage".to_owned())?
|
||||
{
|
||||
Some(l) => l,
|
||||
None => {
|
||||
return Err("failed to get local storage".to_owned());
|
||||
}
|
||||
};
|
||||
|
||||
let vkey = browser_key_name(namespace, key);
|
||||
|
||||
ls.get_item(&vkey)
|
||||
.map_err(|_| "exception_thrown".to_owned())
|
||||
} else {
|
||||
Err("unimplemented".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn remove_user_secret_string(namespace: &str, key: &str) -> Result<bool, String> {
|
||||
if utils::is_nodejs() {
|
||||
match JsFuture::from(
|
||||
keytar_deletePassword("veilid", key).map_err(|_| "exception thrown".to_owned())?,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(v) => Ok(v.is_truthy()),
|
||||
Err(_) => Err("Failed to delete".to_owned()),
|
||||
}
|
||||
} else if utils::is_browser() {
|
||||
let win = match window() {
|
||||
Some(w) => w,
|
||||
None => {
|
||||
return Err("failed to get window".to_owned());
|
||||
}
|
||||
};
|
||||
|
||||
let ls = match win
|
||||
.local_storage()
|
||||
.map_err(|_| "exception getting local storage".to_owned())?
|
||||
{
|
||||
Some(l) => l,
|
||||
None => {
|
||||
return Err("failed to get local storage".to_owned());
|
||||
}
|
||||
};
|
||||
|
||||
let vkey = browser_key_name(namespace, key);
|
||||
|
||||
match ls
|
||||
.get_item(&vkey)
|
||||
.map_err(|_| "exception_thrown".to_owned())?
|
||||
{
|
||||
Some(_) => {
|
||||
ls.delete(&vkey)
|
||||
.map_err(|_| "exception_thrown".to_owned())?;
|
||||
Ok(true)
|
||||
}
|
||||
None => Ok(false),
|
||||
}
|
||||
} else {
|
||||
Err("unimplemented".to_owned())
|
||||
}
|
||||
}
|
154
veilid-core/src/intf/wasm/system.rs
Normal file
154
veilid-core/src/intf/wasm/system.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use super::utils;
|
||||
use crate::xx::*;
|
||||
use crate::*;
|
||||
pub use async_executors::JoinHandle;
|
||||
use async_executors::{Bindgen, LocalSpawnHandleExt /*, SpawnHandleExt*/};
|
||||
use core::fmt;
|
||||
use futures_util::future::{select, Either};
|
||||
use js_sys::*;
|
||||
use wasm_bindgen_futures::*;
|
||||
use web_sys::*;
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(catch, structural, js_namespace = global, js_name = setTimeout)]
|
||||
fn nodejs_global_set_timeout_with_callback_and_timeout_and_arguments_0(
|
||||
handler: &::js_sys::Function,
|
||||
timeout: u32,
|
||||
) -> Result<JsValue, JsValue>;
|
||||
}
|
||||
|
||||
pub fn get_timestamp() -> u64 {
|
||||
if utils::is_browser() {
|
||||
return (Date::now() * 1000.0f64) as u64;
|
||||
} else if utils::is_nodejs() {
|
||||
return (Date::now() * 1000.0f64) as u64;
|
||||
} else {
|
||||
panic!("WASM requires browser or nodejs environment");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_bytes(dest: &mut [u8]) -> Result<(), String> {
|
||||
let len = dest.len();
|
||||
let u32len = len / 4;
|
||||
let remlen = len % 4;
|
||||
|
||||
for n in 0..u32len {
|
||||
let r = (Math::random() * (u32::max_value() as f64)) as u32;
|
||||
|
||||
dest[n * 4 + 0] = (r & 0xFF) as u8;
|
||||
dest[n * 4 + 1] = ((r >> 8) & 0xFF) as u8;
|
||||
dest[n * 4 + 2] = ((r >> 16) & 0xFF) as u8;
|
||||
dest[n * 4 + 3] = ((r >> 24) & 0xFF) as u8;
|
||||
}
|
||||
if remlen > 0 {
|
||||
let r = (Math::random() * (u32::max_value() as f64)) as u32;
|
||||
for n in 0..remlen {
|
||||
dest[u32len * 4 + n] = ((r >> (n * 8)) & 0xFF) as u8;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_random_u32() -> u32 {
|
||||
(Math::random() * (u32::max_value() as f64)) as u32
|
||||
}
|
||||
|
||||
pub fn get_random_u64() -> u64 {
|
||||
let v1: u32 = get_random_u32();
|
||||
let v2: u32 = get_random_u32();
|
||||
((v1 as u64) << 32) | ((v2 as u32) as u64)
|
||||
}
|
||||
|
||||
pub async fn sleep(millis: u32) {
|
||||
if utils::is_browser() {
|
||||
let wait_millis = if millis > u32::MAX {
|
||||
i32::MAX
|
||||
} else {
|
||||
millis as i32
|
||||
};
|
||||
let promise = Promise::new(&mut |yes, _| {
|
||||
let win = window().unwrap();
|
||||
win.set_timeout_with_callback_and_timeout_and_arguments_0(&yes, wait_millis)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
JsFuture::from(promise).await.unwrap();
|
||||
} else if utils::is_nodejs() {
|
||||
let promise = Promise::new(&mut |yes, _| {
|
||||
nodejs_global_set_timeout_with_callback_and_timeout_and_arguments_0(&yes, millis)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
JsFuture::from(promise).await.unwrap();
|
||||
} else {
|
||||
panic!("WASM requires browser or nodejs environment");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn spawn<Out>(future: impl Future<Output = Out> + 'static) -> JoinHandle<Out>
|
||||
where
|
||||
Out: Send + 'static,
|
||||
{
|
||||
Bindgen
|
||||
.spawn_handle_local(future)
|
||||
.expect("wasm-bindgen-futures spawn should never error out")
|
||||
}
|
||||
|
||||
pub fn spawn_local<Out>(future: impl Future<Output = Out> + 'static) -> JoinHandle<Out>
|
||||
where
|
||||
Out: 'static,
|
||||
{
|
||||
Bindgen
|
||||
.spawn_handle_local(future)
|
||||
.expect("wasm-bindgen-futures spawn_local should never error out")
|
||||
}
|
||||
|
||||
pub fn interval<F, FUT>(freq_ms: u32, callback: F) -> SystemPinBoxFuture<()>
|
||||
where
|
||||
F: Fn() -> FUT + 'static,
|
||||
FUT: Future<Output = ()>,
|
||||
{
|
||||
let e = Eventual::new();
|
||||
|
||||
let ie = e.clone();
|
||||
let jh = spawn_local(Box::pin(async move {
|
||||
while timeout(freq_ms, ie.instance_clone(())).await.is_err() {
|
||||
callback().await;
|
||||
}
|
||||
}));
|
||||
|
||||
Box::pin(async move {
|
||||
e.resolve().await;
|
||||
jh.await;
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct TimeoutError {
|
||||
_private: (),
|
||||
}
|
||||
|
||||
//impl Error for TimeoutError {}
|
||||
|
||||
impl fmt::Display for TimeoutError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
"future has timed out".fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn timeout<F, T>(dur_ms: u32, f: F) -> Result<T, TimeoutError>
|
||||
where
|
||||
F: Future<Output = T>,
|
||||
{
|
||||
match select(Box::pin(intf::sleep(dur_ms)), Box::pin(f)).await {
|
||||
Either::Left((_x, _b)) => Err(TimeoutError { _private: () }),
|
||||
Either::Right((y, _a)) => Ok(y),
|
||||
}
|
||||
}
|
||||
|
||||
// xxx: for now until wasm threads are more stable, and/or we bother with web workers
|
||||
pub fn get_concurrency() -> u32 {
|
||||
1
|
||||
}
|
118
veilid-core/src/intf/wasm/table_store.rs
Normal file
118
veilid-core/src/intf/wasm/table_store.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use crate::intf::table_db::*;
|
||||
use crate::intf::*;
|
||||
use crate::*;
|
||||
use keyvaluedb_web::*;
|
||||
|
||||
struct TableStoreInner {
|
||||
config: VeilidConfig,
|
||||
opened: BTreeMap<String, Weak<Mutex<TableDBInner>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TableStore {
|
||||
inner: Arc<Mutex<TableStoreInner>>,
|
||||
}
|
||||
|
||||
impl TableStore {
|
||||
fn new_inner(config: VeilidConfig) -> TableStoreInner {
|
||||
TableStoreInner {
|
||||
config: config,
|
||||
opened: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
pub fn new(config: VeilidConfig) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(Self::new_inner(config))),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn init(&self) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn terminate(&self) {
|
||||
assert!(
|
||||
self.inner.lock().opened.len() == 0,
|
||||
"all open databases should have been closed"
|
||||
);
|
||||
}
|
||||
|
||||
pub fn on_table_db_drop(&self, table: String) {
|
||||
let mut inner = self.inner.lock();
|
||||
match inner.opened.remove(&table) {
|
||||
Some(_) => (),
|
||||
None => {
|
||||
assert!(false, "should have removed an item");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_table_name(inner: &TableStoreInner, table: &str) -> Result<String, String> {
|
||||
if !table
|
||||
.chars()
|
||||
.all(|c| char::is_alphanumeric(c) || c == '_' || c == '-')
|
||||
{
|
||||
return Err(format!("table name '{}' is invalid", table));
|
||||
}
|
||||
let c = inner.config.get();
|
||||
let namespace = c.namespace.clone();
|
||||
Ok(if namespace.len() == 0 {
|
||||
format!("{}", table)
|
||||
} else {
|
||||
format!("_ns_{}_{}", namespace, table)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn open(&self, name: &str, column_count: u32) -> Result<TableDB, String> {
|
||||
let mut inner = self.inner.lock();
|
||||
let table_name = Self::get_table_name(&*inner, name)?;
|
||||
|
||||
if let Some(table_db_weak_inner) = inner.opened.get(&table_name) {
|
||||
match TableDB::try_new_from_weak_inner(table_db_weak_inner.clone()) {
|
||||
Some(tdb) => {
|
||||
return Ok(tdb);
|
||||
}
|
||||
None => {
|
||||
inner.opened.remove(&table_name);
|
||||
}
|
||||
};
|
||||
}
|
||||
let db = Database::open(table_name.clone(), column_count)
|
||||
.await
|
||||
.map_err(|e| format!("failed to open tabledb at: {} ({})", table_name, e))?;
|
||||
|
||||
let table_db = TableDB::new(table_name.clone(), self.clone(), db);
|
||||
|
||||
inner.opened.insert(table_name, table_db.weak_inner());
|
||||
|
||||
Ok(table_db)
|
||||
}
|
||||
|
||||
pub async fn delete(&self, name: &str) -> Result<bool, String> {
|
||||
trace!("TableStore::delete {}", name);
|
||||
let inner = self.inner.lock();
|
||||
let table_name = Self::get_table_name(&*inner, name)?;
|
||||
|
||||
if inner.opened.contains_key(&table_name) {
|
||||
trace!(
|
||||
"TableStore::delete {}: Not deleting, still open.",
|
||||
table_name
|
||||
);
|
||||
return Err("Not deleting table that is still opened".to_owned());
|
||||
}
|
||||
|
||||
if utils::is_nodejs() {
|
||||
Err("unimplemented".to_owned())
|
||||
} else if utils::is_browser() {
|
||||
let out = match Database::delete(table_name.clone()).await {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
};
|
||||
//.map_err(|e| format!("failed to delete tabledb at: {} ({})", table_name, e))?;
|
||||
trace!("TableStore::deleted {}", table_name);
|
||||
Ok(out)
|
||||
} else {
|
||||
Err("unimplemented".to_owned())
|
||||
}
|
||||
}
|
||||
}
|
140
veilid-core/src/intf/wasm/utils/channel.rs
Normal file
140
veilid-core/src/intf/wasm/utils/channel.rs
Normal file
@@ -0,0 +1,140 @@
|
||||
use crate::xx::*;
|
||||
use alloc::collections::VecDeque;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Channel<T> {
|
||||
items: VecDeque<T>,
|
||||
cap: usize,
|
||||
eventual: Eventual,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Sender<T> {
|
||||
imp: Arc<Mutex<Channel<T>>>,
|
||||
}
|
||||
|
||||
impl<T> Clone for Sender<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
imp: self.imp.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Receiver<T> {
|
||||
imp: Arc<Mutex<Channel<T>>>,
|
||||
}
|
||||
|
||||
impl<T> Clone for Receiver<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
imp: self.imp.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel<T>(cap: usize) -> (Sender<T>, Receiver<T>) {
|
||||
let imp = Channel {
|
||||
items: VecDeque::with_capacity(cap),
|
||||
cap: cap,
|
||||
eventual: Eventual::new(),
|
||||
};
|
||||
|
||||
let imparc = Arc::new(Mutex::new(imp));
|
||||
(
|
||||
Sender {
|
||||
imp: imparc.clone(),
|
||||
},
|
||||
Receiver {
|
||||
imp: imparc.clone(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TrySendError<T> {
|
||||
Full(T),
|
||||
Disconnected(T),
|
||||
}
|
||||
|
||||
impl<T> Sender<T> {
|
||||
// NOTE: This needs a timeout or you could block a very long time
|
||||
// pub async fn send(&self, msg: T) -> Result<(), SendError<T>> {
|
||||
// xxx
|
||||
// }
|
||||
|
||||
pub async fn try_send(&self, msg: T) -> Result<(), TrySendError<T>> {
|
||||
let eventual = {
|
||||
let mut inner = self.imp.lock();
|
||||
if inner.items.len() == inner.cap {
|
||||
return Err(TrySendError::Full(msg));
|
||||
}
|
||||
let empty = inner.items.is_empty();
|
||||
inner.items.push_back(msg);
|
||||
if empty {
|
||||
Some(inner.eventual.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(e) = eventual {
|
||||
e.resolve().await;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.imp.lock().cap
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.imp.lock().items.is_empty()
|
||||
}
|
||||
pub fn is_full(&self) -> bool {
|
||||
let inner = self.imp.lock();
|
||||
inner.items.len() == inner.cap
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.imp.lock().items.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct RecvError;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum TryRecvError {
|
||||
Empty,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
impl<T> Receiver<T> {
|
||||
pub async fn recv(&self) -> Result<T, RecvError> {
|
||||
let eventual = {
|
||||
let inner = self.imp.lock();
|
||||
inner.eventual.clone()
|
||||
};
|
||||
while self.is_empty() {
|
||||
eventual.instance_clone(true).await;
|
||||
}
|
||||
Ok(self.imp.lock().items.pop_front().unwrap())
|
||||
}
|
||||
pub async fn try_recv(&self) -> Result<T, TryRecvError> {
|
||||
if self.is_empty() {
|
||||
return Err(TryRecvError::Empty);
|
||||
}
|
||||
Ok(self.imp.lock().items.pop_front().unwrap())
|
||||
}
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.imp.lock().cap
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.imp.lock().items.is_empty()
|
||||
}
|
||||
pub fn is_full(&self) -> bool {
|
||||
let inner = self.imp.lock();
|
||||
inner.items.len() == inner.cap
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.imp.lock().items.len()
|
||||
}
|
||||
}
|
91
veilid-core/src/intf/wasm/utils/mod.rs
Normal file
91
veilid-core/src/intf/wasm/utils/mod.rs
Normal file
@@ -0,0 +1,91 @@
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
pub mod channel;
|
||||
|
||||
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
|
||||
// `log(..)`
|
||||
#[wasm_bindgen(js_namespace = console, js_name = log)]
|
||||
pub fn console_log(s: &str);
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn alert(s: &str);
|
||||
}
|
||||
|
||||
pub fn is_nodejs() -> bool {
|
||||
static CACHE: AtomicI8 = AtomicI8::new(-1);
|
||||
let cache = CACHE.load(Ordering::Relaxed);
|
||||
if cache != -1 {
|
||||
return cache != 0;
|
||||
}
|
||||
|
||||
let res = js_sys::eval("process.release.name === 'node'")
|
||||
.map(|res| res.is_truthy())
|
||||
.unwrap_or_default();
|
||||
|
||||
CACHE.store(res as i8, Ordering::Relaxed);
|
||||
res
|
||||
}
|
||||
|
||||
pub fn is_browser() -> bool {
|
||||
static CACHE: AtomicI8 = AtomicI8::new(-1);
|
||||
let cache = CACHE.load(Ordering::Relaxed);
|
||||
if cache != -1 {
|
||||
return cache != 0;
|
||||
}
|
||||
|
||||
let res = Reflect::has(&global().as_ref(), &"window".into()).unwrap_or_default();
|
||||
|
||||
CACHE.store(res as i8, Ordering::Relaxed);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn is_browser_https() -> bool {
|
||||
static CACHE: AtomicI8 = AtomicI8::new(-1);
|
||||
let cache = CACHE.load(Ordering::Relaxed);
|
||||
if cache != -1 {
|
||||
return cache != 0;
|
||||
}
|
||||
|
||||
let res = js_sys::eval("window.location.protocol === 'https'")
|
||||
.map(|res| res.is_truthy())
|
||||
.unwrap_or_default();
|
||||
|
||||
CACHE.store(res as i8, Ordering::Relaxed);
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
pub fn node_require(module: &str) -> JsValue {
|
||||
if !is_nodejs() {
|
||||
return JsValue::UNDEFINED;
|
||||
}
|
||||
|
||||
let mut home = env!("CARGO_MANIFEST_DIR");
|
||||
if home.len() == 0 {
|
||||
home = ".";
|
||||
}
|
||||
|
||||
match js_sys::eval(format!("require(\"{}/{}\")", home, module).as_str()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
panic!("node_require failed: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user