Merge branch 'feature/bytes-as-uint8arrays' into 'main'
(wasm) Treat arbitrary byte data as Uint8Array, instead of base64url marshalling. See merge request veilid/veilid!196
This commit is contained in:
commit
38ff6f81c5
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -4155,6 +4155,15 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_cbor"
|
||||
version = "0.11.2"
|
||||
@ -5314,6 +5323,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde-big-array",
|
||||
"serde-wasm-bindgen 0.6.0",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"shell-words",
|
||||
@ -5521,6 +5531,7 @@ dependencies = [
|
||||
"send_wrapper 0.6.0",
|
||||
"serde",
|
||||
"serde-wasm-bindgen 0.6.0",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
@ -198,6 +198,9 @@ wasm-bindgen = "0.2.87"
|
||||
js-sys = "0.3.64"
|
||||
wasm-bindgen-futures = "0.4.37"
|
||||
send_wrapper = { version = "0.6.0", features = ["futures"] }
|
||||
serde_bytes = { version = "0.11", default_features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
tsify = { version = "0.4.5", features = ["js"] }
|
||||
serde-wasm-bindgen = "0.6.0"
|
||||
|
||||
|
@ -215,7 +215,6 @@ impl VeilidAPIError {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", declare)]
|
||||
pub type VeilidAPIResult<T> = Result<T, VeilidAPIError>;
|
||||
|
||||
impl From<std::io::Error> for VeilidAPIError {
|
||||
|
@ -9,9 +9,13 @@ pub struct VeilidAppMessage {
|
||||
#[cfg_attr(target_arch = "wasm32", tsify(optional, type = "string"))]
|
||||
sender: Option<TypedKey>,
|
||||
|
||||
#[serde(with = "as_human_base64")]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "as_human_base64"))]
|
||||
#[schemars(with = "String")]
|
||||
#[cfg_attr(target_arch = "wasm32", tsify(type = "string"))]
|
||||
#[cfg_attr(
|
||||
target_arch = "wasm32",
|
||||
serde(with = "serde_bytes"),
|
||||
tsify(type = "Uint8Array")
|
||||
)]
|
||||
message: Vec<u8>,
|
||||
}
|
||||
|
||||
@ -40,8 +44,13 @@ pub struct VeilidAppCall {
|
||||
#[cfg_attr(target_arch = "wasm32", tsify(optional))]
|
||||
sender: Option<TypedKey>,
|
||||
|
||||
#[serde(with = "as_human_base64")]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "as_human_base64"))]
|
||||
#[schemars(with = "String")]
|
||||
#[cfg_attr(
|
||||
target_arch = "wasm32",
|
||||
serde(with = "serde_bytes"),
|
||||
tsify(type = "Uint8Array")
|
||||
)]
|
||||
message: Vec<u8>,
|
||||
|
||||
#[serde(with = "as_human_string")]
|
||||
|
@ -8,9 +8,13 @@ pub struct ValueData {
|
||||
seq: ValueSeqNum,
|
||||
|
||||
/// The contents of a DHT Record
|
||||
#[serde(with = "as_human_base64")]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), serde(with = "as_human_base64"))]
|
||||
#[schemars(with = "String")]
|
||||
#[cfg_attr(target_arch = "wasm32", tsify(type = "string"))]
|
||||
#[cfg_attr(
|
||||
target_arch = "wasm32",
|
||||
serde(with = "serde_bytes"),
|
||||
tsify(type = "Uint8Array")
|
||||
)]
|
||||
data: Vec<u8>,
|
||||
|
||||
/// The public identity key of the writer of the data
|
||||
|
@ -1,9 +1,7 @@
|
||||
use crate::*;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
#[cfg_attr(target_arch = "wasm32", declare)]
|
||||
pub type ConfigCallbackReturn = VeilidAPIResult<Box<dyn core::any::Any + Send>>;
|
||||
#[cfg_attr(target_arch = "wasm32", declare)]
|
||||
pub type ConfigCallback = Arc<dyn Fn(String) -> ConfigCallbackReturn + Send + Sync>;
|
||||
|
||||
/// Enable and configure HTTPS access to the Veilid node
|
||||
|
@ -28,6 +28,9 @@ cfg-if = "^1"
|
||||
wasm-bindgen-futures = "^0"
|
||||
js-sys = "^0"
|
||||
serde_json = "^1"
|
||||
serde_bytes = { version = "0.11", default_features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
serde = "^1"
|
||||
lazy_static = "^1"
|
||||
send_wrapper = "^0"
|
||||
|
@ -58,7 +58,7 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out.to_string())
|
||||
}
|
||||
|
||||
pub fn randomBytes(kind: String, len: u32) -> APIResult<String> {
|
||||
pub fn randomBytes(kind: String, len: u32) -> APIResult<Box<[u8]>> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
@ -71,7 +71,7 @@ impl VeilidCrypto {
|
||||
)
|
||||
})?;
|
||||
let out = crypto_system.random_bytes(len);
|
||||
let out = data_encoding::BASE64URL_NOPAD.encode(&out);
|
||||
let out = out.into_boxed_slice();
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
@ -91,10 +91,8 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
pub fn hashPassword(kind: String, password: String, salt: String) -> APIResult<String> {
|
||||
pub fn hashPassword(kind: String, password: Box<[u8]>, salt: Box<[u8]>) -> APIResult<String> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
let password = unmarshall(password)?;
|
||||
let salt = unmarshall(salt)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
@ -111,11 +109,10 @@ impl VeilidCrypto {
|
||||
|
||||
pub fn verifyPassword(
|
||||
kind: String,
|
||||
password: String,
|
||||
password: Box<[u8]>,
|
||||
password_hash: String,
|
||||
) -> APIResult<bool> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
let password = unmarshall(password)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
@ -130,10 +127,12 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
pub fn deriveSharedSecret(kind: String, password: String, salt: String) -> APIResult<String> {
|
||||
pub fn deriveSharedSecret(
|
||||
kind: String,
|
||||
password: Box<[u8]>,
|
||||
salt: Box<[u8]>,
|
||||
) -> APIResult<String> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
let password = unmarshall(password)?;
|
||||
let salt = unmarshall(salt)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
@ -182,7 +181,7 @@ impl VeilidCrypto {
|
||||
|
||||
pub fn verifySignatures(
|
||||
node_ids: StringArray,
|
||||
data: String,
|
||||
data: Box<[u8]>,
|
||||
signatures: StringArray,
|
||||
) -> VeilidAPIResult<StringArray> {
|
||||
let node_ids = into_unchecked_string_vec(node_ids);
|
||||
@ -199,8 +198,6 @@ impl VeilidCrypto {
|
||||
})
|
||||
.collect::<APIResult<Vec<TypedKey>>>()?;
|
||||
|
||||
let data: Vec<u8> = unmarshall(data)?;
|
||||
|
||||
let typed_signatures = into_unchecked_string_vec(signatures);
|
||||
let typed_signatures: Vec<TypedSignature> = typed_signatures
|
||||
.iter()
|
||||
@ -226,9 +223,7 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
pub fn generateSignatures(data: String, key_pairs: StringArray) -> APIResult<StringArray> {
|
||||
let data = unmarshall(data)?;
|
||||
|
||||
pub fn generateSignatures(data: Box<[u8]>, key_pairs: StringArray) -> APIResult<StringArray> {
|
||||
let key_pairs = into_unchecked_string_vec(key_pairs);
|
||||
let key_pairs: Vec<TypedKeyPair> = key_pairs
|
||||
.iter()
|
||||
@ -269,11 +264,9 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
pub fn generateHash(kind: String, data: String) -> APIResult<String> {
|
||||
pub fn generateHash(kind: String, data: Box<[u8]>) -> APIResult<String> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let data = unmarshall(data)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
let crypto_system = crypto.get(kind).ok_or_else(|| {
|
||||
@ -306,11 +299,9 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
pub fn validateHash(kind: String, data: String, hash: String) -> APIResult<bool> {
|
||||
pub fn validateHash(kind: String, data: Box<[u8]>, hash: String) -> APIResult<bool> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let data = unmarshall(data)?;
|
||||
|
||||
let hash: veilid_core::HashDigest = veilid_core::HashDigest::from_str(&hash)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
@ -345,14 +336,12 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out.to_string())
|
||||
}
|
||||
|
||||
pub fn sign(kind: String, key: String, secret: String, data: String) -> APIResult<String> {
|
||||
pub fn sign(kind: String, key: String, secret: String, data: Box<[u8]>) -> APIResult<String> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?;
|
||||
let secret: veilid_core::SecretKey = veilid_core::SecretKey::from_str(&secret)?;
|
||||
|
||||
let data = unmarshall(data)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
let crypto_system = crypto.get(kind).ok_or_else(|| {
|
||||
@ -362,11 +351,10 @@ impl VeilidCrypto {
|
||||
APIResult::Ok(out.to_string())
|
||||
}
|
||||
|
||||
pub fn verify(kind: String, key: String, data: String, signature: String) -> APIResult<()> {
|
||||
pub fn verify(kind: String, key: String, data: Box<[u8]>, signature: String) -> APIResult<()> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let key: veilid_core::PublicKey = veilid_core::PublicKey::from_str(&key)?;
|
||||
let data = unmarshall(data)?;
|
||||
let signature: veilid_core::Signature = veilid_core::Signature::from_str(&signature)?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
@ -396,24 +384,18 @@ impl VeilidCrypto {
|
||||
|
||||
pub fn decryptAead(
|
||||
kind: String,
|
||||
body: String,
|
||||
body: Box<[u8]>,
|
||||
nonce: String,
|
||||
shared_secret: String,
|
||||
associated_data: Option<String>,
|
||||
) -> APIResult<String> {
|
||||
associated_data: Option<Box<[u8]>>,
|
||||
) -> APIResult<Box<[u8]>> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let body = unmarshall(body)?;
|
||||
|
||||
let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?;
|
||||
|
||||
let shared_secret: veilid_core::SharedSecret =
|
||||
veilid_core::SharedSecret::from_str(&shared_secret)?;
|
||||
|
||||
let associated_data = associated_data
|
||||
.map(unmarshall)
|
||||
.map_or(APIResult::Ok(None), |r| r.map(Some))?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
let crypto_system = crypto.get(kind).ok_or_else(|| {
|
||||
@ -428,34 +410,28 @@ impl VeilidCrypto {
|
||||
&nonce,
|
||||
&shared_secret,
|
||||
match &associated_data {
|
||||
Some(ad) => Some(ad.as_slice()),
|
||||
Some(ad) => Some(ad),
|
||||
None => None,
|
||||
},
|
||||
)?;
|
||||
let out = data_encoding::BASE64URL_NOPAD.encode(&out);
|
||||
let out = out.into_boxed_slice();
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
pub fn encryptAead(
|
||||
kind: String,
|
||||
body: String,
|
||||
body: Box<[u8]>,
|
||||
nonce: String,
|
||||
shared_secret: String,
|
||||
associated_data: Option<String>,
|
||||
) -> APIResult<String> {
|
||||
associated_data: Option<Box<[u8]>>,
|
||||
) -> APIResult<Box<[u8]>> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let body = unmarshall(body)?;
|
||||
|
||||
let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?;
|
||||
|
||||
let shared_secret: veilid_core::SharedSecret =
|
||||
veilid_core::SharedSecret::from_str(&shared_secret)?;
|
||||
|
||||
let associated_data: Option<Vec<u8>> = associated_data
|
||||
.map(unmarshall)
|
||||
.map_or(APIResult::Ok(None), |r| r.map(Some))?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let crypto = veilid_api.crypto()?;
|
||||
let crypto_system = crypto.get(kind).ok_or_else(|| {
|
||||
@ -470,24 +446,21 @@ impl VeilidCrypto {
|
||||
&nonce,
|
||||
&shared_secret,
|
||||
match &associated_data {
|
||||
Some(ad) => Some(ad.as_slice()),
|
||||
Some(ad) => Some(ad),
|
||||
None => None,
|
||||
},
|
||||
)?;
|
||||
let out = data_encoding::BASE64URL_NOPAD.encode(&out);
|
||||
APIResult::Ok(out)
|
||||
APIResult::Ok(out.into_boxed_slice())
|
||||
}
|
||||
|
||||
pub fn cryptNoAuth(
|
||||
kind: String,
|
||||
body: String,
|
||||
mut body: Box<[u8]>,
|
||||
nonce: String,
|
||||
shared_secret: String,
|
||||
) -> APIResult<String> {
|
||||
) -> APIResult<Box<[u8]>> {
|
||||
let kind: veilid_core::CryptoKind = veilid_core::FourCC::from_str(&kind)?;
|
||||
|
||||
let mut body = unmarshall(body)?;
|
||||
|
||||
let nonce: veilid_core::Nonce = veilid_core::Nonce::from_str(&nonce)?;
|
||||
|
||||
let shared_secret: veilid_core::SharedSecret =
|
||||
@ -503,7 +476,6 @@ impl VeilidCrypto {
|
||||
)
|
||||
})?;
|
||||
crypto_system.crypt_in_place_no_auth(&mut body, &nonce, &shared_secret);
|
||||
let out = data_encoding::BASE64URL_NOPAD.encode(&body);
|
||||
APIResult::Ok(out)
|
||||
APIResult::Ok(body)
|
||||
}
|
||||
}
|
||||
|
@ -83,8 +83,8 @@ impl VeilidRoutingContext {
|
||||
///
|
||||
/// * `call_id` - specifies which call to reply to, and it comes from a VeilidUpdate::AppCall, specifically the VeilidAppCall::id() value.
|
||||
/// * `message` - is an answer blob to be returned by the remote node's RoutingContext::app_call() function, and may be up to 32768 bytes
|
||||
pub async fn appCallReply(call_id: String, message: String) -> APIResult<()> {
|
||||
let message = unmarshall(message)?;
|
||||
pub async fn appCallReply(call_id: String, message: Box<[u8]>) -> APIResult<()> {
|
||||
let message = message.into_vec();
|
||||
let call_id = match call_id.parse() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@ -148,10 +148,9 @@ impl VeilidRoutingContext {
|
||||
/// @param {string} target - can be either a direct node id or a private route.
|
||||
/// @param {string} message - an arbitrary message blob of up to `32768` bytes.
|
||||
#[wasm_bindgen(skip_jsdoc)]
|
||||
pub async fn appMessage(&self, target_string: String, message: String) -> APIResult<()> {
|
||||
pub async fn appMessage(&self, target_string: String, message: Box<[u8]>) -> APIResult<()> {
|
||||
let routing_context = self.getRoutingContext()?;
|
||||
let message = unmarshall(message)?;
|
||||
|
||||
let message = message.into_vec();
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let target = veilid_api.parse_as_target(target_string).await?;
|
||||
routing_context.app_message(target, message).await?;
|
||||
@ -162,18 +161,22 @@ impl VeilidRoutingContext {
|
||||
///
|
||||
/// Veilid apps may use this for arbitrary message passing.
|
||||
///
|
||||
/// @param {string} target_string - can be either a direct node id or a private route, base64Url encoded.
|
||||
/// @param {string} message - an arbitrary message blob of up to `32768` bytes, base64Url encoded.
|
||||
/// @returns an answer blob of up to `32768` bytes, base64Url encoded.
|
||||
/// @param {string} target_string - can be either a direct node id or a private route.
|
||||
/// @param {Uint8Array} message - an arbitrary message blob of up to `32768` bytes.
|
||||
/// @returns {Uint8Array} an answer blob of up to `32768` bytes.
|
||||
#[wasm_bindgen(skip_jsdoc)]
|
||||
pub async fn appCall(&self, target_string: String, request: String) -> APIResult<String> {
|
||||
let request: Vec<u8> = unmarshall(request)?;
|
||||
pub async fn appCall(
|
||||
&self,
|
||||
target_string: String,
|
||||
request: Box<[u8]>,
|
||||
) -> APIResult<Uint8Array> {
|
||||
let request: Vec<u8> = request.into_vec();
|
||||
let routing_context = self.getRoutingContext()?;
|
||||
|
||||
let veilid_api = get_veilid_api()?;
|
||||
let target = veilid_api.parse_as_target(target_string).await?;
|
||||
let answer = routing_context.app_call(target, request).await?;
|
||||
let answer = marshall(&answer);
|
||||
let answer = Uint8Array::from(answer.as_slice());
|
||||
APIResult::Ok(answer)
|
||||
}
|
||||
|
||||
@ -250,7 +253,7 @@ impl VeilidRoutingContext {
|
||||
/// May pull the latest value from the network, but by settings 'force_refresh' you can force a network data refresh.
|
||||
///
|
||||
/// Returns `undefined` if the value subkey has not yet been set.
|
||||
/// Returns base64Url encoded `data` if the value subkey has valid data.
|
||||
/// Returns a Uint8Array of `data` if the value subkey has valid data.
|
||||
pub async fn getDhtValue(
|
||||
&self,
|
||||
key: String,
|
||||
@ -268,15 +271,15 @@ impl VeilidRoutingContext {
|
||||
/// Pushes a changed subkey value to the network
|
||||
///
|
||||
/// Returns `undefined` if the value was successfully put.
|
||||
/// Returns base64Url encoded `data` if the value put was older than the one available on the network.
|
||||
/// Returns a Uint8Array of `data` if the value put was older than the one available on the network.
|
||||
pub async fn setDhtValue(
|
||||
&self,
|
||||
key: String,
|
||||
subKey: u32,
|
||||
data: String,
|
||||
data: Box<[u8]>,
|
||||
) -> APIResult<Option<ValueData>> {
|
||||
let key = TypedKey::from_str(&key)?;
|
||||
let data = unmarshall(data)?;
|
||||
let data = data.into_vec();
|
||||
|
||||
let routing_context = self.getRoutingContext()?;
|
||||
let res = routing_context.set_dht_value(key, subKey, data).await?;
|
||||
|
@ -63,22 +63,24 @@ impl VeilidTableDB {
|
||||
}
|
||||
|
||||
/// Read a key from a column in the TableDB immediately.
|
||||
pub async fn load(&mut self, columnId: u32, key: String) -> APIResult<Option<String>> {
|
||||
pub async fn load(&mut self, columnId: u32, key: Box<[u8]>) -> APIResult<Option<Uint8Array>> {
|
||||
self.ensureOpen().await;
|
||||
let key = unmarshall(key)?;
|
||||
let table_db = self.getTableDB()?;
|
||||
|
||||
let out = table_db.load(columnId, &key).await?;
|
||||
let out = out.map(|out| marshall(&out));
|
||||
let out = out.map(|out| Uint8Array::from(out.as_slice()));
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
/// Store a key with a value in a column in the TableDB.
|
||||
/// Performs a single transaction immediately.
|
||||
pub async fn store(&mut self, columnId: u32, key: String, value: String) -> APIResult<()> {
|
||||
pub async fn store(
|
||||
&mut self,
|
||||
columnId: u32,
|
||||
key: Box<[u8]>,
|
||||
value: Box<[u8]>,
|
||||
) -> APIResult<()> {
|
||||
self.ensureOpen().await;
|
||||
let key = unmarshall(key)?;
|
||||
let value = unmarshall(value)?;
|
||||
let table_db = self.getTableDB()?;
|
||||
|
||||
table_db.store(columnId, &key, &value).await?;
|
||||
@ -86,26 +88,29 @@ impl VeilidTableDB {
|
||||
}
|
||||
|
||||
/// Delete key with from a column in the TableDB.
|
||||
pub async fn delete(&mut self, columnId: u32, key: String) -> APIResult<Option<String>> {
|
||||
pub async fn delete(&mut self, columnId: u32, key: Box<[u8]>) -> APIResult<Option<Uint8Array>> {
|
||||
self.ensureOpen().await;
|
||||
let key = unmarshall(key)?;
|
||||
let table_db = self.getTableDB()?;
|
||||
|
||||
let out = table_db.delete(columnId, &key).await?;
|
||||
let out = out.map(|out| marshall(&out));
|
||||
let out = out.map(|out| Uint8Array::from(out.as_slice()));
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
|
||||
/// Get the list of keys in a column of the TableDB.
|
||||
///
|
||||
/// Returns an array of base64Url encoded keys.
|
||||
pub async fn getKeys(&mut self, columnId: u32) -> APIResult<StringArray> {
|
||||
/// Returns an array of Uint8Array keys.
|
||||
pub async fn getKeys(&mut self, columnId: u32) -> APIResult<Uint8ArrayArray> {
|
||||
self.ensureOpen().await;
|
||||
let table_db = self.getTableDB()?;
|
||||
|
||||
let keys = table_db.clone().get_keys(columnId).await?;
|
||||
let out: Vec<String> = keys.into_iter().map(|k| marshall(&k)).collect();
|
||||
let out = into_unchecked_string_array(out);
|
||||
let out: Vec<Uint8Array> = keys
|
||||
.into_iter()
|
||||
.map(|k| Uint8Array::from(k.as_slice()))
|
||||
.collect();
|
||||
|
||||
let out = into_unchecked_uint8array_array(out);
|
||||
|
||||
APIResult::Ok(out)
|
||||
}
|
||||
@ -164,16 +169,13 @@ impl VeilidTableDBTransaction {
|
||||
|
||||
/// Store a key with a value in a column in the TableDB.
|
||||
/// Does not modify TableDB until `.commit()` is called.
|
||||
pub fn store(&self, col: u32, key: String, value: String) -> APIResult<()> {
|
||||
let key = unmarshall(key)?;
|
||||
let value = unmarshall(value)?;
|
||||
pub fn store(&self, col: u32, key: Box<[u8]>, value: Box<[u8]>) -> APIResult<()> {
|
||||
let transaction = self.getTransaction()?;
|
||||
transaction.store(col, &key, &value)
|
||||
}
|
||||
|
||||
/// Delete key with from a column in the TableDB
|
||||
pub fn deleteKey(&self, col: u32, key: String) -> APIResult<()> {
|
||||
let key = unmarshall(key)?;
|
||||
pub fn deleteKey(&self, col: u32, key: Box<[u8]>) -> APIResult<()> {
|
||||
let transaction = self.getTransaction()?;
|
||||
transaction.delete(col, &key)
|
||||
}
|
||||
|
@ -37,6 +37,19 @@ pub(crate) fn into_unchecked_string_array(items: Vec<String>) -> StringArray {
|
||||
.unchecked_into::<StringArray>() // TODO: can I do this a better way?
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
extern "C" {
|
||||
#[wasm_bindgen(typescript_type = "Uint8Array[]")]
|
||||
pub type Uint8ArrayArray;
|
||||
}
|
||||
/// Convert a `Vec<Uint8Array>` into a `js_sys::Array` with the type of `Uint8Array[]`
|
||||
pub(crate) fn into_unchecked_uint8array_array(items: Vec<Uint8Array>) -> Uint8ArrayArray {
|
||||
items
|
||||
.iter()
|
||||
.collect::<js_sys::Array>()
|
||||
.unchecked_into::<Uint8ArrayArray>() // TODO: can I do this a better way?
|
||||
}
|
||||
|
||||
/// Convert a StringArray (`js_sys::Array` with the type of `string[]`) into `Vec<String>`
|
||||
pub(crate) fn into_unchecked_string_vec(items: StringArray) -> Vec<String> {
|
||||
items
|
||||
|
2
veilid-wasm/tests/.gitignore
vendored
2
veilid-wasm/tests/.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
node_modules
|
||||
veilid-wasm-pkg
|
||||
coverage
|
173
veilid-wasm/tests/src/VeilidRoutingContext.test.ts
Normal file
173
veilid-wasm/tests/src/VeilidRoutingContext.test.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import { expect } from '@wdio/globals';
|
||||
|
||||
import {
|
||||
veilidCoreInitConfig,
|
||||
veilidCoreStartupConfig,
|
||||
} from './utils/veilid-config';
|
||||
|
||||
import {
|
||||
DHTRecordDescriptor,
|
||||
VeilidRoutingContext,
|
||||
veilidClient,
|
||||
veilidCrypto,
|
||||
} from 'veilid-wasm';
|
||||
import { textEncoder, textDecoder } from './utils/marshalling-utils';
|
||||
import { waitForMs } from './utils/wait-utils';
|
||||
|
||||
describe('VeilidRoutingContext', () => {
|
||||
before('veilid startup', async () => {
|
||||
veilidClient.initializeCore(veilidCoreInitConfig);
|
||||
await veilidClient.startupCore((_update) => {
|
||||
// if (_update.kind === 'Log') {
|
||||
// console.log(_update.message);
|
||||
// }
|
||||
}, JSON.stringify(veilidCoreStartupConfig));
|
||||
await veilidClient.attach();
|
||||
await waitForMs(2000);
|
||||
});
|
||||
|
||||
after('veilid shutdown', async () => {
|
||||
await veilidClient.detach();
|
||||
await veilidClient.shutdownCore();
|
||||
});
|
||||
|
||||
describe('constructors', () => {
|
||||
it('should create using .create()', async () => {
|
||||
const routingContext = VeilidRoutingContext.create();
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create using new', async () => {
|
||||
const routingContext = new VeilidRoutingContext();
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create with privacy', async () => {
|
||||
const routingContext = VeilidRoutingContext.create().withPrivacy();
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create with custom privacy', async () => {
|
||||
const routingContext = VeilidRoutingContext.create().withCustomPrivacy({
|
||||
Safe: {
|
||||
hop_count: 2,
|
||||
sequencing: 'EnsureOrdered',
|
||||
stability: 'Reliable',
|
||||
},
|
||||
});
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
it('should create with sequencing', async () => {
|
||||
const routingContext =
|
||||
VeilidRoutingContext.create().withSequencing('EnsureOrdered');
|
||||
expect(routingContext instanceof VeilidRoutingContext).toBe(true);
|
||||
|
||||
routingContext.free();
|
||||
});
|
||||
});
|
||||
|
||||
describe('operations', () => {
|
||||
let routingContext: VeilidRoutingContext;
|
||||
|
||||
before('create routing context', () => {
|
||||
routingContext = VeilidRoutingContext.create()
|
||||
.withPrivacy()
|
||||
.withSequencing('EnsureOrdered');
|
||||
});
|
||||
|
||||
after('free routing context', () => {
|
||||
routingContext.free();
|
||||
});
|
||||
|
||||
describe('DHT kitchen sink', async () => {
|
||||
let dhtRecord: DHTRecordDescriptor;
|
||||
const data = '🚀 This example DHT data with unicode a Ā 𐀀 文 🚀';
|
||||
|
||||
before('create dht record', async () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
dhtRecord = await routingContext.createDhtRecord(
|
||||
{
|
||||
kind: 'DFLT',
|
||||
o_cnt: 1,
|
||||
},
|
||||
bestKind
|
||||
);
|
||||
|
||||
expect(dhtRecord.key).toBeDefined();
|
||||
expect(dhtRecord.owner).toBeDefined();
|
||||
expect(dhtRecord.owner_secret).toBeDefined();
|
||||
expect(dhtRecord.schema).toEqual({ kind: 'DFLT', o_cnt: 1 });
|
||||
});
|
||||
|
||||
after('free dht record', async () => {
|
||||
await routingContext.closeDhtRecord(dhtRecord.key);
|
||||
});
|
||||
|
||||
it('should set value', async () => {
|
||||
const setValueRes = await routingContext.setDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
textEncoder.encode(data)
|
||||
);
|
||||
expect(setValueRes).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should get value with force refresh', async () => {
|
||||
const getValueRes = await routingContext.getDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
true
|
||||
);
|
||||
expect(getValueRes?.data).toBeDefined();
|
||||
expect(textDecoder.decode(getValueRes?.data)).toBe(data);
|
||||
|
||||
expect(getValueRes?.writer).toBe(dhtRecord.owner);
|
||||
expect(getValueRes?.seq).toBe(0);
|
||||
});
|
||||
|
||||
it('should open readonly record', async () => {
|
||||
await routingContext.closeDhtRecord(dhtRecord.key);
|
||||
|
||||
const readonlyDhtRecord = await routingContext.openDhtRecord(
|
||||
dhtRecord.key
|
||||
);
|
||||
expect(readonlyDhtRecord).toBeDefined();
|
||||
|
||||
const setValueRes = routingContext.setDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
textEncoder.encode(data)
|
||||
);
|
||||
await expect(setValueRes).rejects.toEqual({
|
||||
kind: 'Generic',
|
||||
message: 'value is not writable',
|
||||
});
|
||||
});
|
||||
|
||||
it('should open writable record', async () => {
|
||||
await routingContext.closeDhtRecord(dhtRecord.key);
|
||||
|
||||
const writeableDhtRecord = await routingContext.openDhtRecord(
|
||||
dhtRecord.key,
|
||||
`${dhtRecord.owner}:${dhtRecord.owner_secret}`
|
||||
);
|
||||
expect(writeableDhtRecord).toBeDefined();
|
||||
const setValueRes = await routingContext.setDhtValue(
|
||||
dhtRecord.key,
|
||||
0,
|
||||
textEncoder.encode(`${data}👋`)
|
||||
);
|
||||
expect(setValueRes).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
@ -6,7 +6,7 @@ import {
|
||||
} from './utils/veilid-config';
|
||||
|
||||
import { VeilidTableDB, veilidClient } from 'veilid-wasm';
|
||||
import { marshall, unmarshall } from './utils/marshalling-utils';
|
||||
import { textEncoder, textDecoder } from './utils/marshalling-utils';
|
||||
|
||||
const TABLE_NAME = 'some-table';
|
||||
const TABLE_COLS = 1;
|
||||
@ -57,18 +57,22 @@ describe('VeilidTable', () => {
|
||||
const value = 'test value with unicode 🚀';
|
||||
|
||||
it('should store value', async () => {
|
||||
await table.store(0, marshall(key), marshall(value));
|
||||
await table.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(value)
|
||||
);
|
||||
});
|
||||
|
||||
it('should load value', async () => {
|
||||
const storedValue = await table.load(0, marshall(key));
|
||||
const storedValue = await table.load(0, textEncoder.encode(key));
|
||||
expect(storedValue).toBeDefined();
|
||||
expect(unmarshall(storedValue!)).toBe(value);
|
||||
expect(textDecoder.decode(storedValue!)).toBe(value);
|
||||
});
|
||||
|
||||
it('should have key in list of keys', async () => {
|
||||
const keys = await table.getKeys(0);
|
||||
const decodedKeys = keys.map(unmarshall);
|
||||
const decodedKeys = keys.map((key) => textDecoder.decode(key));
|
||||
expect(decodedKeys).toEqual([key]);
|
||||
});
|
||||
});
|
||||
@ -82,15 +86,27 @@ describe('VeilidTable', () => {
|
||||
const second = 'second✔';
|
||||
const third = 'third📢';
|
||||
|
||||
transaction.store(0, marshall(key), marshall(first));
|
||||
transaction.store(0, marshall(key), marshall(second));
|
||||
transaction.store(0, marshall(key), marshall(third));
|
||||
transaction.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(first)
|
||||
);
|
||||
transaction.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(second)
|
||||
);
|
||||
transaction.store(
|
||||
0,
|
||||
textEncoder.encode(key),
|
||||
textEncoder.encode(third)
|
||||
);
|
||||
|
||||
await transaction.commit();
|
||||
|
||||
const storedValue = await table.load(0, marshall(key));
|
||||
const storedValue = await table.load(0, textEncoder.encode(key));
|
||||
expect(storedValue).toBeDefined();
|
||||
expect(unmarshall(storedValue!)).toBe(third);
|
||||
expect(textDecoder.decode(storedValue!)).toBe(third);
|
||||
|
||||
transaction.free();
|
||||
});
|
||||
|
@ -1,13 +1,23 @@
|
||||
// TextEncoder/TextDecoder are used to solve for "The Unicode Problem" https://stackoverflow.com/a/30106551
|
||||
export const textDecoder = new TextDecoder();
|
||||
export const textEncoder = new TextEncoder();
|
||||
|
||||
export function marshall(data: string) {
|
||||
const byteString = bytesToString(new TextEncoder().encode(data));
|
||||
// TextEncoder/TextDecoder are used to solve for "The Unicode Problem" https://stackoverflow.com/a/30106551
|
||||
export function marshallString(data: string) {
|
||||
return marshallBytes(textEncoder.encode(data));
|
||||
}
|
||||
|
||||
export function unmarshallString(b64: string) {
|
||||
return textDecoder.decode(unmarshallBytes(b64));
|
||||
}
|
||||
|
||||
export function marshallBytes(data: Uint8Array) {
|
||||
const byteString = bytesToString(data);
|
||||
return base64UrlEncode(byteString);
|
||||
}
|
||||
|
||||
export function unmarshall(b64: string) {
|
||||
export function unmarshallBytes(b64: string) {
|
||||
const byteString = base64UrlDecode(b64);
|
||||
return new TextDecoder().decode(stringToBytes(byteString));
|
||||
return stringToBytes(byteString);
|
||||
}
|
||||
|
||||
function base64UrlEncode(data: string) {
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
} from './utils/veilid-config';
|
||||
|
||||
import { veilidClient, veilidCrypto } from 'veilid-wasm';
|
||||
import { textEncoder, unmarshallBytes } from './utils/marshalling-utils';
|
||||
|
||||
describe('veilidCrypto', () => {
|
||||
before('veilid startup', async () => {
|
||||
@ -29,10 +30,116 @@ describe('veilidCrypto', () => {
|
||||
expect(kinds.includes(bestKind)).toBe(true);
|
||||
});
|
||||
|
||||
it('should generate key pair', async () => {
|
||||
it('should generate key pair', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const keypair = veilidCrypto.generateKeyPair(bestKind);
|
||||
expect(typeof keypair).toBe('string');
|
||||
// TODO: fix TypeScript return type of generateKeyPair to return string instead of KeyPair
|
||||
|
||||
const [publicKey, secretKey] = keypair.split(':');
|
||||
expect(unmarshallBytes(publicKey).length).toBe(32);
|
||||
expect(unmarshallBytes(secretKey).length).toBe(32);
|
||||
|
||||
const isValid = veilidCrypto.validateKeyPair(
|
||||
bestKind,
|
||||
publicKey,
|
||||
secretKey
|
||||
);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should generate random bytes', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const bytes = veilidCrypto.randomBytes(bestKind, 64);
|
||||
expect(bytes instanceof Uint8Array).toBe(true);
|
||||
expect(bytes.length).toBe(64);
|
||||
});
|
||||
|
||||
it('should hash data and validate hash', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const data = textEncoder.encode('this is my data🚀');
|
||||
const hash = veilidCrypto.generateHash(bestKind, data);
|
||||
|
||||
expect(hash).toBeDefined();
|
||||
expect(typeof hash).toBe('string');
|
||||
|
||||
const isValid = veilidCrypto.validateHash(bestKind, data, hash);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should hash and validate password', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
|
||||
const password = textEncoder.encode('this is my data🚀');
|
||||
const saltLength = veilidCrypto.defaultSaltLength(bestKind);
|
||||
expect(saltLength).toBeGreaterThan(0);
|
||||
|
||||
const salt = veilidCrypto.randomBytes(bestKind, saltLength);
|
||||
expect(salt instanceof Uint8Array).toBe(true);
|
||||
expect(salt.length).toBe(saltLength);
|
||||
|
||||
const hash = veilidCrypto.hashPassword(bestKind, password, salt);
|
||||
expect(hash).toBeDefined();
|
||||
expect(typeof hash).toBe('string');
|
||||
|
||||
const isValid = veilidCrypto.verifyPassword(bestKind, password, hash);
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
|
||||
it('should aead encrypt and decrypt', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const body = textEncoder.encode(
|
||||
'This is an encoded body with my secret data in it🔥'
|
||||
);
|
||||
const ad = textEncoder.encode(
|
||||
'This is data associated with my secret data👋'
|
||||
);
|
||||
|
||||
const nonce = veilidCrypto.randomNonce(bestKind);
|
||||
expect(typeof nonce).toBe('string');
|
||||
|
||||
const sharedSecred = veilidCrypto.randomSharedSecret(bestKind);
|
||||
expect(typeof sharedSecred).toBe('string');
|
||||
|
||||
const encBody = veilidCrypto.encryptAead(
|
||||
bestKind,
|
||||
body,
|
||||
nonce,
|
||||
sharedSecred,
|
||||
ad
|
||||
);
|
||||
expect(encBody instanceof Uint8Array).toBe(true);
|
||||
|
||||
const overhead = veilidCrypto.aeadOverhead(bestKind);
|
||||
expect(encBody.length - body.length).toBe(overhead);
|
||||
|
||||
const decBody = veilidCrypto.decryptAead(
|
||||
bestKind,
|
||||
encBody,
|
||||
nonce,
|
||||
sharedSecred,
|
||||
ad
|
||||
);
|
||||
expect(decBody instanceof Uint8Array).toBe(true);
|
||||
expect(body).toEqual(decBody);
|
||||
});
|
||||
|
||||
it('should sign and verify', () => {
|
||||
const bestKind = veilidCrypto.bestCryptoKind();
|
||||
const keypair = veilidCrypto.generateKeyPair(bestKind);
|
||||
const data = textEncoder.encode(
|
||||
'This is some data I am signing with my key 🔑'
|
||||
);
|
||||
expect(typeof keypair).toBe('string');
|
||||
|
||||
const [publicKey, secretKey] = keypair.split(':');
|
||||
|
||||
const sig = veilidCrypto.sign(bestKind, publicKey, secretKey, data);
|
||||
expect(typeof sig).toBe('string');
|
||||
|
||||
expect(() => {
|
||||
const res = veilidCrypto.verify(bestKind, publicKey, data, sig);
|
||||
expect(res).toBeUndefined();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -5,7 +5,20 @@ export const config: Options.Testrunner = {
|
||||
// Runner Configuration
|
||||
// ====================
|
||||
// WebdriverIO supports running e2e tests as well as unit and component tests.
|
||||
runner: ['browser', { viteConfig: './vite.config.ts' }],
|
||||
runner: [
|
||||
'browser',
|
||||
{
|
||||
viteConfig: './vite.config.ts',
|
||||
coverage: {
|
||||
enabled: true,
|
||||
// needed since the ../pkg directory that has the compiled wasm npm package
|
||||
// is outside the current directory. Coverage is only collected on files
|
||||
// that are in within `cwd`.
|
||||
cwd: '..',
|
||||
include: ['pkg/**'],
|
||||
},
|
||||
},
|
||||
],
|
||||
autoCompileOpts: {
|
||||
autoCompile: true,
|
||||
tsNodeOpts: {
|
||||
|
Loading…
Reference in New Issue
Block a user