From 10af290e2f03c776e1cb567bc004b84509679948 Mon Sep 17 00:00:00 2001 From: John Smith Date: Tue, 16 May 2023 13:28:09 -0400 Subject: [PATCH] wasm work --- veilid-flutter/lib/veilid_ffi.dart | 40 ++- veilid-flutter/lib/veilid_js.dart | 269 +++++++++++++++++- veilid-flutter/rust/src/dart_ffi.rs | 10 +- .../rust/src/dart_isolate_wrapper.rs | 21 ++ veilid-wasm/src/lib.rs | 24 +- 5 files changed, 346 insertions(+), 18 deletions(-) diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index 4bce79b2..7597d7c0 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -380,6 +380,42 @@ Future processFutureJson( }); } +Future processFutureOptJson( + T Function(dynamic) jsonConstructor, Future future) { + return future.then((value) { + final list = value as List; + switch (list[0] as int) { + case messageErr: + { + throw VeilidAPIExceptionInternal("Internal API Error: ${list[1]}"); + } + case messageOkJson: + { + if (list[1] == null) { + return null; + } + var ret = jsonDecode(list[1] as String); + return jsonConstructor(ret); + } + case messageErrJson: + { + throw VeilidAPIException.fromJson(jsonDecode(list[1])); + } + default: + { + throw VeilidAPIExceptionInternal( + "Unexpected async return message type: ${list[0]}"); + } + } + }).catchError((e) { + // Wrap all other errors in VeilidAPIExceptionInternal + throw VeilidAPIExceptionInternal(e.toString()); + }, test: (e) { + // Pass errors that are already VeilidAPIException through without wrapping + return e is! VeilidAPIException; + }); +} + Future processFutureVoid(Future future) { return future.then((value) { final list = value as List; @@ -967,9 +1003,9 @@ class VeilidCryptoSystemFFI implements VeilidCryptoSystem { final nativeEncodedData = base64UrlNoPadEncode(data).toNativeUtf8(); final nativeSignature = jsonEncode(signature).toNativeUtf8(); - final recvPort = ReceivePort("crypto_sign"); + final recvPort = ReceivePort("crypto_verify"); final sendPort = recvPort.sendPort; - _ffi._cryptoSign(sendPort.nativePort, _kind, nativeKey, nativeEncodedData, + _ffi._cryptoVerify(sendPort.nativePort, _kind, nativeKey, nativeEncodedData, nativeSignature); return processFutureVoid(recvPort.first); } diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index a46eefa5..dad121af 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -77,6 +77,216 @@ class VeilidRoutingContextJS implements VeilidRoutingContext { return _wrapApiPromise(js_util.callMethod(wasm, "routing_context_app_message", [_ctx.id, target, encodedMessage])); } + + @override + Future createDHTRecord( + CryptoKind kind, DHTSchema schema) async { + return DHTRecordDescriptor.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "routing_context_create_dht_record", + [_ctx.id, kind, jsonEncode(schema)])))); + } + + @override + Future openDHTRecord( + TypedKey key, KeyPair? writer) async { + return DHTRecordDescriptor.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "routing_context_open_dht_record", [ + _ctx.id, + jsonEncode(key), + writer != null ? jsonEncode(writer) : null + ])))); + } + + @override + Future closeDHTRecord(TypedKey key) { + return _wrapApiPromise(js_util.callMethod( + wasm, "routing_context_close_dht_record", [_ctx.id, jsonEncode(key)])); + } + + @override + Future deleteDHTRecord(TypedKey key) { + return _wrapApiPromise(js_util.callMethod( + wasm, "routing_context_delete_dht_record", [_ctx.id, jsonEncode(key)])); + } + + @override + Future getDHTValue( + TypedKey key, int subkey, bool forceRefresh) async { + final opt = await _wrapApiPromise(js_util.callMethod( + wasm, + "routing_context_get_dht_value", + [_ctx.id, jsonEncode(key), subkey, forceRefresh])); + return opt == null ? null : ValueData.fromJson(jsonDecode(opt)); + } + + @override + Future setDHTValue( + TypedKey key, int subkey, Uint8List data) async { + final opt = await _wrapApiPromise(js_util.callMethod( + wasm, + "routing_context_set_dht_value", + [_ctx.id, jsonEncode(key), subkey, base64UrlNoPadEncode(data)])); + return opt == null ? null : ValueData.fromJson(jsonDecode(opt)); + } + + @override + Future watchDHTValues(TypedKey key, ValueSubkeyRange subkeys, + Timestamp expiration, int count) async { + final ts = await _wrapApiPromise(js_util.callMethod( + wasm, "routing_context_watch_dht_values", [ + _ctx.id, + jsonEncode(key), + jsonEncode(subkeys), + expiration.toString(), + count + ])); + return Timestamp.fromString(ts); + } + + @override + Future cancelDHTWatch(TypedKey key, ValueSubkeyRange subkeys) { + return _wrapApiPromise(js_util.callMethod( + wasm, + "routing_context_cancel_dht_watch", + [_ctx.id, jsonEncode(key), jsonEncode(subkeys)])); + } +} + +// JS implementation of VeilidCryptoSystem +class VeilidCryptoSystemJS implements VeilidCryptoSystem { + final CryptoKind _kind; + final VeilidJS _js; + + VeilidCryptoSystemJS._(this._js, this._kind); + + @override + CryptoKind kind() { + return _kind; + } + + @override + Future cachedDH(PublicKey key, SecretKey secret) async { + return SharedSecret.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "crypto_cached_dh", + [_kind, jsonEncode(key), jsonEncode(secret)])))); + } + + @override + Future computeDH(PublicKey key, SecretKey secret) async { + return SharedSecret.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "crypto_compute_dh", + [_kind, jsonEncode(key), jsonEncode(secret)])))); + } + + @override + Future randomNonce() async { + return Nonce.fromJson(jsonDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "crypto_random_nonce", [_kind])))); + } + + @override + Future randomSharedSecret() async { + return SharedSecret.fromJson(jsonDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "crypto_random_shared_secret", [_kind])))); + } + + @override + Future generateKeyPair() async { + return KeyPair.fromJson(jsonDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "crypto_generate_key_pair", [_kind])))); + } + + @override + Future generateHash(Uint8List data) async { + return HashDigest.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "crypto_generate_hash", + [_kind, base64UrlNoPadEncode(data)])))); + } + + @override + Future validateKeyPair(PublicKey key, SecretKey secret) { + return _wrapApiPromise(js_util.callMethod(wasm, "crypto_validate_key_pair", + [_kind, jsonEncode(key), jsonEncode(secret)])); + } + + @override + Future validateHash(Uint8List data, HashDigest hash) { + return _wrapApiPromise(js_util.callMethod(wasm, "crypto_validate_hash", + [_kind, base64UrlNoPadEncode(data), jsonEncode(hash)])); + } + + @override + Future distance(CryptoKey key1, CryptoKey key2) async { + return CryptoKeyDistance.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "crypto_distance", + [_kind, jsonEncode(key1), jsonEncode(key2)])))); + } + + @override + Future sign( + PublicKey key, SecretKey secret, Uint8List data) async { + return Signature.fromJson(jsonDecode(await _wrapApiPromise(js_util + .callMethod(wasm, "crypto_sign", [ + _kind, + jsonEncode(key), + jsonEncode(secret), + base64UrlNoPadEncode(data) + ])))); + } + + @override + Future verify(PublicKey key, Uint8List data, Signature signature) { + return _wrapApiPromise(js_util.callMethod(wasm, "crypto_verify", [ + _kind, + jsonEncode(key), + base64UrlNoPadEncode(data), + jsonEncode(signature), + ])); + } + + @override + Future aeadOverhead() { + return _wrapApiPromise( + js_util.callMethod(wasm, "crypto_aead_overhead", [_kind])); + } + + @override + Future decryptAead(Uint8List body, Nonce nonce, + SharedSecret sharedSecret, Uint8List? associatedData) async { + return base64UrlNoPadDecode( + await _wrapApiPromise(js_util.callMethod(wasm, "crypto_decrypt_aead", [ + _kind, + base64UrlNoPadEncode(body), + jsonEncode(nonce), + jsonEncode(sharedSecret), + associatedData != null ? base64UrlNoPadEncode(associatedData) : null + ]))); + } + + @override + Future encryptAead(Uint8List body, Nonce nonce, + SharedSecret sharedSecret, Uint8List? associatedData) async { + return base64UrlNoPadDecode( + await _wrapApiPromise(js_util.callMethod(wasm, "crypto_encrypt_aead", [ + _kind, + base64UrlNoPadEncode(body), + jsonEncode(nonce), + jsonEncode(sharedSecret), + associatedData != null ? base64UrlNoPadEncode(associatedData) : null + ]))); + } + + @override + Future cryptNoAuth( + Uint8List body, Nonce nonce, SharedSecret sharedSecret) async { + return base64UrlNoPadDecode(await _wrapApiPromise(js_util.callMethod( + wasm, "crypto_crypt_no_auth", [ + _kind, + base64UrlNoPadEncode(body), + jsonEncode(nonce), + jsonEncode(sharedSecret) + ]))); + } } class _TDBT { @@ -257,6 +467,50 @@ class VeilidJS implements Veilid { js_util.callMethod(wasm, "shutdown_veilid_core", [])); } + @override + List validCryptoKinds() { + return jsonDecode(js_util.callMethod(wasm, "valid_crypto_kinds", [])); + } + + @override + Future getCryptoSystem(CryptoKind kind) async { + if (!validCryptoKinds().contains(kind)) { + throw VeilidAPIExceptionGeneric("unsupported cryptosystem"); + } + return VeilidCryptoSystemJS._(this, kind); + } + + @override + Future bestCryptoSystem() async { + return VeilidCryptoSystemJS._( + this, js_util.callMethod(wasm, "best_crypto_kind", [])); + } + + @override + Future> verifySignatures(List nodeIds, + Uint8List data, List signatures) async { + return jsonListConstructor(TypedKey.fromJson)(jsonDecode( + await _wrapApiPromise(js_util.callMethod(wasm, "verify_signatures", [ + jsonEncode(nodeIds), + base64UrlNoPadEncode(data), + jsonEncode(signatures) + ])))); + } + + @override + Future> generateSignatures( + Uint8List data, List keyPairs) async { + return jsonListConstructor(TypedSignature.fromJson)(jsonDecode( + await _wrapApiPromise(js_util.callMethod(wasm, "generate_signatures", + [base64UrlNoPadEncode(data), jsonEncode(keyPairs)])))); + } + + @override + Future generateKeyPair(CryptoKind kind) async { + return TypedKeyPair.fromJson(jsonDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "generate_key_pair", [kind])))); + } + @override Future routingContext() async { int id = @@ -266,9 +520,8 @@ class VeilidJS implements Veilid { @override Future newPrivateRoute() async { - Map blobJson = jsonDecode(await _wrapApiPromise( - js_util.callMethod(wasm, "new_private_route", []))); - return RouteBlob.fromJson(blobJson); + return RouteBlob.fromJson(jsonDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "new_private_route", [])))); } @override @@ -277,10 +530,9 @@ class VeilidJS implements Veilid { var stabilityString = jsonEncode(stability); var sequencingString = jsonEncode(sequencing); - Map blobJson = jsonDecode(await _wrapApiPromise(js_util + return RouteBlob.fromJson(jsonDecode(await _wrapApiPromise(js_util .callMethod( - wasm, "new_private_route", [stabilityString, sequencingString]))); - return RouteBlob.fromJson(blobJson); + wasm, "new_private_route", [stabilityString, sequencingString])))); } @override @@ -315,6 +567,11 @@ class VeilidJS implements Veilid { return _wrapApiPromise(js_util.callMethod(wasm, "delete_table_db", [name])); } + @override + Timestamp now() { + return Timestamp.fromString(js_util.callMethod(wasm, "now", [])); + } + @override Future debug(String command) async { return await _wrapApiPromise(js_util.callMethod(wasm, "debug", [command])); diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index 8294ea19..8e521ecf 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -566,7 +566,7 @@ pub extern "C" fn routing_context_delete_dht_record(port: i64, id: u32, key: Ffi #[no_mangle] pub extern "C" fn routing_context_get_dht_value(port: i64, id: u32, key: FfiStr, subkey: u32, force_refresh: bool) { let key: veilid_core::TypedKey = veilid_core::deserialize_opt_json(key.into_opt_string()).unwrap(); - DartIsolateWrapper::new(port).spawn_result_json(async move { + DartIsolateWrapper::new(port).spawn_result_opt_json(async move { let routing_context = { let rc = ROUTING_CONTEXTS.lock(); let Some(routing_context) = rc.get(&id) else { @@ -591,7 +591,7 @@ pub extern "C" fn routing_context_set_dht_value(port: i64, id: u32, key: FfiStr, ) .unwrap(); - DartIsolateWrapper::new(port).spawn_result_json(async move { + DartIsolateWrapper::new(port).spawn_result_opt_json(async move { let routing_context = { let rc = ROUTING_CONTEXTS.lock(); let Some(routing_context) = rc.get(&id) else { @@ -1252,12 +1252,12 @@ pub extern "C" fn crypto_verify(port: i64, kind: u32, key: FfiStr, data: FfiStr, let signature: veilid_core::Signature = veilid_core::deserialize_opt_json(signature.into_opt_string()).unwrap(); - DartIsolateWrapper::new(port).spawn_result_json(async move { + DartIsolateWrapper::new(port).spawn_result(async move { let veilid_api = get_veilid_api().await?; let crypto = veilid_api.crypto()?; let csv = crypto.get(kind).ok_or_else(|| veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()))?; - let out = csv.verify(&key, &data, &signature)?; - APIResult::Ok(out) + csv.verify(&key, &data, &signature)?; + APIRESULT_VOID }); } diff --git a/veilid-flutter/rust/src/dart_isolate_wrapper.rs b/veilid-flutter/rust/src/dart_isolate_wrapper.rs index 2c0bb371..3adc7d50 100644 --- a/veilid-flutter/rust/src/dart_isolate_wrapper.rs +++ b/veilid-flutter/rust/src/dart_isolate_wrapper.rs @@ -52,6 +52,17 @@ impl DartIsolateWrapper { }); } + pub fn spawn_result_opt_json(self, future: F) + where + F: Future, E>> + Send + 'static, + T: Serialize + Debug, + E: Serialize + Debug, + { + spawn(async move { + self.result_opt_json(future.await); + }); + } + pub fn result(self, result: Result) -> bool { match result { Ok(v) => self.ok(v), @@ -67,6 +78,16 @@ impl DartIsolateWrapper { Err(e) => self.err_json(e), } } + pub fn result_opt_json( + self, + result: Result, E>, + ) -> bool { + match result { + Ok(Some(v)) => self.ok_json(v), + Ok(None) => self.ok(()), + Err(e) => self.err_json(e), + } + } pub fn ok(self, value: T) -> bool { self.isolate .post(vec![MESSAGE_OK.into_dart(), value.into_dart()]) diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index aa09a491..86a4126e 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -59,6 +59,12 @@ fn take_veilid_api() -> Result(val: T) -> JsValue { JsValue::from_str(&serialize_json(val)) } +pub fn to_opt_json(val: Option) -> JsValue { + match val { + Some(v) => JsValue::from_str(&serialize_json(v)), + None => JsValue::UNDEFINED, + } +} pub fn to_jsvalue(val: T) -> JsValue where @@ -113,6 +119,14 @@ where future_to_promise(future.map(|res| res.map(|v| to_json(v)).map_err(|e| to_json(e)))) } +pub fn wrap_api_future_opt_json(future: F) -> Promise +where + F: Future>> + 'static, + T: Serialize + Debug + 'static, +{ + future_to_promise(future.map(|res| res.map(|v| to_opt_json(v)).map_err(|e| to_json(e)))) +} + pub fn wrap_api_future_plain(future: F) -> Promise where F: Future> + 'static, @@ -489,7 +503,7 @@ pub fn routing_context_get_dht_value( force_refresh: bool, ) -> Promise { let key: veilid_core::TypedKey = veilid_core::deserialize_json(&key).unwrap(); - wrap_api_future_json(async move { + wrap_api_future_opt_json(async move { let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { @@ -511,7 +525,7 @@ pub fn routing_context_set_dht_value(id: u32, key: String, subkey: u32, data: St .decode(&data.as_bytes()) .unwrap(); - wrap_api_future_json(async move { + wrap_api_future_opt_json(async move { let routing_context = { let rc = (*ROUTING_CONTEXTS).borrow(); let Some(routing_context) = rc.get(&id) else { @@ -1181,14 +1195,14 @@ pub fn crypto_verify(kind: u32, key: String, data: String, signature: String) -> .unwrap(); let signature: veilid_core::Signature = veilid_core::deserialize_json(&signature).unwrap(); - wrap_api_future_json(async move { + wrap_api_future_void(async move { let veilid_api = get_veilid_api()?; let crypto = veilid_api.crypto()?; let csv = crypto.get(kind).ok_or_else(|| { veilid_core::VeilidAPIError::invalid_argument("crypto_verify", "kind", kind.to_string()) })?; - let out = csv.verify(&key, &data, &signature)?; - APIResult::Ok(out) + csv.verify(&key, &data, &signature)?; + APIRESULT_UNDEFINED }) }