diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index 6b71130d..3a57e134 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -1562,7 +1562,7 @@ impl NetworkManager { if let Some(nr) = routing_table.lookup_node_ref(k) { let peer_stats = nr.peer_stats(); let peer = PeerTableData { - node_ids: nr.node_ids().iter().map(|x| x.to_string()).collect(), + node_ids: nr.node_ids().iter().copied().collect(), peer_address: v.last_connection.remote().to_string(), peer_stats, }; diff --git a/veilid-core/src/veilid_api/types/app_message_call.rs b/veilid-core/src/veilid_api/types/app_message_call.rs index 95fa27f8..eb3b99e0 100644 --- a/veilid-core/src/veilid_api/types/app_message_call.rs +++ b/veilid-core/src/veilid_api/types/app_message_call.rs @@ -8,18 +8,18 @@ use super::*; pub struct VeilidAppMessage { /// Some(sender) if the message was sent directly, None if received via a private/safety route #[serde(with = "opt_json_as_string")] - sender: Option, + sender: Option, xxx continue propagating this publickey->typedkey and get all the FFI done /// The content of the message to deliver to the application #[serde(with = "json_as_base64")] message: Vec, } impl VeilidAppMessage { - pub fn new(sender: Option, message: Vec) -> Self { + pub fn new(sender: Option, message: Vec) -> Self { Self { sender, message } } - pub fn sender(&self) -> Option<&PublicKey> { + pub fn sender(&self) -> Option<&TypedKey> { self.sender.as_ref() } pub fn message(&self) -> &[u8] { @@ -35,7 +35,7 @@ impl VeilidAppMessage { pub struct VeilidAppCall { /// Some(sender) if the request was sent directly, None if received via a private/safety route #[serde(with = "opt_json_as_string")] - sender: Option, + sender: Option, /// The content of the request to deliver to the application #[serde(with = "json_as_base64")] message: Vec, @@ -45,7 +45,7 @@ pub struct VeilidAppCall { } impl VeilidAppCall { - pub fn new(sender: Option, message: Vec, id: OperationId) -> Self { + pub fn new(sender: Option, message: Vec, id: OperationId) -> Self { Self { sender, message, @@ -53,7 +53,7 @@ impl VeilidAppCall { } } - pub fn sender(&self) -> Option<&PublicKey> { + pub fn sender(&self) -> Option<&TypedKey> { self.sender.as_ref() } pub fn message(&self) -> &[u8] { diff --git a/veilid-core/src/veilid_api/types/veilid_state.rs b/veilid-core/src/veilid_api/types/veilid_state.rs index 739cbee3..09f21a24 100644 --- a/veilid-core/src/veilid_api/types/veilid_state.rs +++ b/veilid-core/src/veilid_api/types/veilid_state.rs @@ -74,7 +74,7 @@ pub struct VeilidStateAttachment { )] #[archive_attr(repr(C), derive(CheckBytes))] pub struct PeerTableData { - pub node_ids: Vec, + pub node_ids: Vec, pub peer_address: String, pub peer_stats: PeerStats, } diff --git a/veilid-flutter/example/pubspec.lock b/veilid-flutter/example/pubspec.lock index 68059eef..c8f7903c 100644 --- a/veilid-flutter/example/pubspec.lock +++ b/veilid-flutter/example/pubspec.lock @@ -5,98 +5,120 @@ packages: dependency: "direct main" description: name: ansicolor - url: "https://pub.dartlang.org" + sha256: "607f8fa9786f392043f169898923e6c59b4518242b68b8862eb8a8b7d9c30b4a" + url: "https://pub.dev" source: hosted version: "2.0.1" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.10.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" change_case: dependency: transitive description: name: change_case - url: "https://pub.dartlang.org" + sha256: "2757d850a43d333fd12d6cce49e2f1248ac89a16ae10ce4ca34d4c7d73286f3c" + url: "https://pub.dev" source: hosted version: "1.0.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted version: "1.2.1" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306 + url: "https://pub.dev" + source: hosted + version: "1.3.1" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted version: "1.1.1" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" source: hosted version: "3.1.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" source: hosted version: "1.0.5" equatable: dependency: transitive description: name: equatable - url: "https://pub.dartlang.org" + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" source: hosted version: "2.0.5" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted version: "1.3.1" ffi: dependency: transitive description: name: ffi - url: "https://pub.dartlang.org" + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" source: hosted version: "2.0.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted version: "6.1.4" file_utils: dependency: transitive description: name: file_utils - url: "https://pub.dartlang.org" + sha256: d1e64389a22649095c8405c9e177272caf05139255931c9ff30d53b5c9bcaa34 + url: "https://pub.dev" source: hosted version: "1.0.1" flutter: @@ -108,14 +130,16 @@ packages: dependency: "direct main" description: name: flutter_acrylic - url: "https://pub.dartlang.org" + sha256: "646200d98e8dd2bd4ab931d4ba4f6b4cb899475d6401414017ba5d71b0fac42b" + url: "https://pub.dev" source: hosted version: "1.0.0+2" flutter_lints: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_test: @@ -132,140 +156,160 @@ packages: dependency: transitive description: name: globbing - url: "https://pub.dartlang.org" + sha256: "4f89cfaf6fa74c9c1740a96259da06bd45411ede56744e28017cc534a12b6e2d" + url: "https://pub.dev" source: hosted version: "1.0.0" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" source: hosted version: "2.0.1" loggy: dependency: "direct main" description: name: loggy - url: "https://pub.dartlang.org" + sha256: "981e03162bbd3a5a843026f75f73d26e4a0d8aa035ae060456ca7b30dfd1e339" + url: "https://pub.dev" source: hosted version: "2.0.3" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted - version: "0.12.12" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted version: "1.8.0" path: dependency: "direct main" description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted version: "1.8.2" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + sha256: "050e8e85e4b7fecdf2bb3682c1c64c4887a183720c802d323de8a5fd76d372dd" + url: "https://pub.dev" source: hosted version: "2.0.11" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + url: "https://pub.dev" source: hosted version: "2.0.22" path_provider_ios: dependency: transitive description: name: path_provider_ios - url: "https://pub.dartlang.org" + sha256: "03d639406f5343478352433f00d3c4394d52dac8df3d847869c5e2333e0bbce8" + url: "https://pub.dev" source: hosted version: "2.0.11" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 + url: "https://pub.dev" source: hosted version: "2.1.7" path_provider_macos: dependency: transitive description: name: path_provider_macos - url: "https://pub.dartlang.org" + sha256: "2a97e7fbb7ae9dcd0dfc1220a78e9ec3e71da691912e617e8715ff2a13086ae8" + url: "https://pub.dev" source: hosted version: "2.0.6" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + url: "https://pub.dev" source: hosted version: "2.0.5" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + url: "https://pub.dev" source: hosted version: "2.1.3" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" platform_info: dependency: transitive description: name: platform_info - url: "https://pub.dartlang.org" + sha256: "012e73712166cf0b56d3eb95c0d33491f56b428c169eca385f036448474147e4" + url: "https://pub.dev" source: hosted version: "3.2.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" source: hosted version: "2.1.3" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" quiver: dependency: transitive description: name: quiver - url: "https://pub.dartlang.org" + sha256: "93982981971e812c94d4a6fa3a57b89f9ec12b38b6380cd3c1370c3b01e4580e" + url: "https://pub.dev" source: hosted version: "3.1.0" sky_engine: @@ -277,65 +321,74 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.9.0" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" system_info2: dependency: transitive description: name: system_info2 - url: "https://pub.dartlang.org" + sha256: af2f948e3f31a3367a049932a8ad59faf0063ecf836a020d975b9f41566d8bc9 + url: "https://pub.dev" source: hosted version: "3.0.2" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted version: "1.2.1" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "0.4.12" + version: "0.4.16" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted version: "1.3.1" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" veilid: dependency: "direct main" description: @@ -347,21 +400,24 @@ packages: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: ca121dbbadb3e43b449053feab0cdf3f2bff93b107cacf0290e3d29f717374b6 + url: "https://pub.dev" source: hosted version: "3.1.2" xdg_directories: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: "11541eedefbcaec9de35aa82650b695297ce668662bbd6e3911a7fabdbde589f" + url: "https://pub.dev" source: hosted version: "0.2.0+2" xterm: dependency: "direct main" description: name: xterm - url: "https://pub.dartlang.org" + sha256: "990286eead883ff5ad9b8ea7674183dced2fe5421771e95ce32cbaa9117d24d8" + url: "https://pub.dev" source: hosted version: "3.4.0" sdks: diff --git a/veilid-flutter/lib/base64url_no_pad.dart b/veilid-flutter/lib/base64url_no_pad.dart deleted file mode 100644 index 81f97a2d..00000000 --- a/veilid-flutter/lib/base64url_no_pad.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; - -String base64UrlNoPadEncode(List bytes) { - var x = base64Url.encode(bytes); - while (x.endsWith('=')) { - x = x.substring(0, x.length - 1); - } - return x; -} - -Uint8List base64UrlNoPadDecode(String source) { - source = base64.normalize(source); - return base64.decode(source); -} diff --git a/veilid-flutter/lib/routing_context.dart b/veilid-flutter/lib/routing_context.dart new file mode 100644 index 00000000..83414671 --- /dev/null +++ b/veilid-flutter/lib/routing_context.dart @@ -0,0 +1,277 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:convert'; + +import 'package:change_case/change_case.dart'; + +import 'base64url_no_pad.dart'; +import 'veilid.dart'; + +////////////////////////////////////// + +////////////////////////////////////// +/// DHT Schema + +abstract class DHTSchema { + factory DHTSchema.fromJson(dynamic json) { + switch (json["kind"]) { + case "DFLT": + { + return DHTSchemaDFLT(oCnt: json["o_cnt"]); + } + case "SMPL": + { + return DHTSchemaSMPL( + oCnt: json["o_cnt"], + members: List.from( + json['members'].map((j) => DHTSchemaMember.fromJson(j)))); + } + default: + { + throw VeilidAPIExceptionInternal( + "Invalid VeilidAPIException type: ${json['kind']}"); + } + } + } + Map get json; +} + +class DHTSchemaDFLT implements DHTSchema { + final int oCnt; + // + DHTSchemaDFLT({ + required this.oCnt, + }) { + if (oCnt < 0 || oCnt > 65535) { + throw VeilidAPIExceptionInvalidArgument( + "value out of range", "oCnt", oCnt.toString()); + } + } + + @override + Map get json { + return { + 'kind': "DFLT", + 'o_cnt': oCnt, + }; + } +} + +class DHTSchemaMember { + Key mKey; + int mCnt; + + DHTSchemaMember({ + required this.mKey, + required this.mCnt, + }) { + if (mCnt < 0 || mCnt > 65535) { + throw VeilidAPIExceptionInvalidArgument( + "value out of range", "mCnt", mCnt.toString()); + } + } + + Map get json { + return { + 'm_key': mKey, + 'm_cnt': mCnt, + }; + } + + DHTSchemaMember.fromJson(dynamic json) + : mKey = json['m_key'], + mCnt = json['m_cnt']; +} + +class DHTSchemaSMPL implements DHTSchema { + final int oCnt; + final List members; + // + DHTSchemaSMPL({ + required this.oCnt, + required this.members, + }) { + if (oCnt < 0 || oCnt > 65535) { + throw VeilidAPIExceptionInvalidArgument( + "value out of range", "oCnt", oCnt.toString()); + } + } + @override + Map get json { + return { + 'kind': "SMPL", + 'o_cnt': oCnt, + 'members': members.map((p) => p.json).toList(), + }; + } +} + +////////////////////////////////////// +/// DHTRecordDescriptor + +class DHTRecordDescriptor { + TypedKey key; + Key owner; + Key? ownerSecret; + DHTSchema schema; + + DHTRecordDescriptor({ + required this.key, + required this.owner, + this.ownerSecret, + required this.schema, + }); + + Map get json { + return { + 'key': key.toString(), + 'owner': owner, + 'owner_secret': ownerSecret, + 'schema': schema.json, + }; + } + + DHTRecordDescriptor.fromJson(dynamic json) + : key = TypedKey.fromString(json['key']), + owner = json['owner'], + ownerSecret = json['owner_secret'], + schema = DHTSchema.fromJson(json['schema']); +} + +////////////////////////////////////// +/// ValueSubkeyRange + +class ValueSubkeyRange { + final int low; + final int high; + + ValueSubkeyRange({ + required this.low, + required this.high, + }) { + if (low < 0 || low > high) { + throw VeilidAPIExceptionInvalidArgument( + "invalid range", "low", low.toString()); + } + if (high < 0) { + throw VeilidAPIExceptionInvalidArgument( + "invalid range", "high", high.toString()); + } + } + + ValueSubkeyRange.fromJson(dynamic json) + : low = json[0], + high = json[1] { + if ((json as List).length != 2) { + throw VeilidAPIExceptionInvalidArgument( + "not a pair of integers", "json", json.toString()); + } + } + + List get json { + return [low, high]; + } +} + +////////////////////////////////////// +/// ValueData + +class ValueData { + final int seq; + final Uint8List data; + final Key writer; + + ValueData({ + required this.seq, + required this.data, + required this.writer, + }); + + ValueData.fromJson(dynamic json) + : seq = json['seq'], + data = base64UrlNoPadDecode(json['data']), + writer = json['writer']; + + Map get json { + return {'seq': seq, 'data': base64UrlNoPadEncode(data), 'writer': writer}; + } +} + +/// Stability + +enum Stability { + lowLatency, + reliable, +} + +extension StabilityExt on Stability { + String get json { + return name.toPascalCase(); + } +} + +Stability stabilityFromJson(String j) { + return Stability.values.byName(j.toCamelCase()); +} + +////////////////////////////////////// +/// Sequencing + +enum Sequencing { + noPreference, + preferOrdered, + ensureOrdered, +} + +extension SequencingExt on Sequencing { + String get json { + return name.toPascalCase(); + } +} + +Sequencing sequencingFromJson(String j) { + return Sequencing.values.byName(j.toCamelCase()); +} + +////////////////////////////////////// +/// RouteBlob +class RouteBlob { + final String routeId; + final Uint8List blob; + + RouteBlob(this.routeId, this.blob); + + RouteBlob.fromJson(dynamic json) + : routeId = json['route_id'], + blob = base64UrlNoPadDecode(json['blob']); + + Map get json { + return {'route_id': routeId, 'blob': base64UrlNoPadEncode(blob)}; + } +} + +////////////////////////////////////// +/// VeilidRoutingContext + +abstract class VeilidRoutingContext { + // Modifiers + VeilidRoutingContext withPrivacy(); + VeilidRoutingContext withCustomPrivacy(Stability stability); + VeilidRoutingContext withSequencing(Sequencing sequencing); + + // App call/message + Future appCall(String target, Uint8List request); + Future appMessage(String target, Uint8List message); + + // DHT Operations + Future createDHTRecord( + CryptoKind kind, DHTSchema schema); + Future openDHTRecord(TypedKey key, KeyPair? writer); + Future closeDHTRecord(TypedKey key); + Future deleteDHTRecord(TypedKey key); + Future getDHTValue(TypedKey key, int subkey, bool forceRefresh); + Future setDHTValue(TypedKey key, int subkey, Uint8List data); + Future watchDHTValues( + TypedKey key, ValueSubkeyRange subkeys, Timestamp expiration, int count); + Future cancelDHTWatch(TypedKey key, ValueSubkeyRange subkeys); +} diff --git a/veilid-flutter/lib/veilid.dart b/veilid-flutter/lib/veilid.dart index c6087efe..181153a6 100644 --- a/veilid-flutter/lib/veilid.dart +++ b/veilid-flutter/lib/veilid.dart @@ -8,213 +8,25 @@ import 'veilid_stub.dart' if (dart.library.io) 'veilid_ffi.dart' if (dart.library.js) 'veilid_js.dart'; -import 'base64url_no_pad.dart'; +import 'veilid_encoding.dart'; ////////////////////////////////////////////////////////// +import 'routing_context.dart'; +import 'veilid_config.dart'; +import 'veilid_crypto.dart'; +import 'veilid_table_db.dart'; +import 'veilid_api_exception.dart'; +import 'veilid_state.dart'; + export 'default_config.dart'; - -////////////////////////////////////////////////////////// -// FFI Platform-specific config - -class VeilidFFIConfigLoggingTerminal { - bool enabled; - VeilidConfigLogLevel level; - - VeilidFFIConfigLoggingTerminal({ - required this.enabled, - required this.level, - }); - - Map get json { - return { - 'enabled': enabled, - 'level': level.json, - }; - } - - VeilidFFIConfigLoggingTerminal.fromJson(dynamic json) - : enabled = json['enabled'], - level = veilidConfigLogLevelFromJson(json['level']); -} - -class VeilidFFIConfigLoggingOtlp { - bool enabled; - VeilidConfigLogLevel level; - String grpcEndpoint; - String serviceName; - - VeilidFFIConfigLoggingOtlp({ - required this.enabled, - required this.level, - required this.grpcEndpoint, - required this.serviceName, - }); - - Map get json { - return { - 'enabled': enabled, - 'level': level.json, - 'grpc_endpoint': grpcEndpoint, - 'service_name': serviceName, - }; - } - - VeilidFFIConfigLoggingOtlp.fromJson(dynamic json) - : enabled = json['enabled'], - level = veilidConfigLogLevelFromJson(json['level']), - grpcEndpoint = json['grpc_endpoint'], - serviceName = json['service_name']; -} - -class VeilidFFIConfigLoggingApi { - bool enabled; - VeilidConfigLogLevel level; - - VeilidFFIConfigLoggingApi({ - required this.enabled, - required this.level, - }); - - Map get json { - return { - 'enabled': enabled, - 'level': level.json, - }; - } - - VeilidFFIConfigLoggingApi.fromJson(dynamic json) - : enabled = json['enabled'], - level = veilidConfigLogLevelFromJson(json['level']); -} - -class VeilidFFIConfigLogging { - VeilidFFIConfigLoggingTerminal terminal; - VeilidFFIConfigLoggingOtlp otlp; - VeilidFFIConfigLoggingApi api; - - VeilidFFIConfigLogging( - {required this.terminal, required this.otlp, required this.api}); - - Map get json { - return { - 'terminal': terminal.json, - 'otlp': otlp.json, - 'api': api.json, - }; - } - - VeilidFFIConfigLogging.fromJson(dynamic json) - : terminal = VeilidFFIConfigLoggingTerminal.fromJson(json['terminal']), - otlp = VeilidFFIConfigLoggingOtlp.fromJson(json['otlp']), - api = VeilidFFIConfigLoggingApi.fromJson(json['api']); -} - -class VeilidFFIConfig { - VeilidFFIConfigLogging logging; - - VeilidFFIConfig({ - required this.logging, - }); - - Map get json { - return { - 'logging': logging.json, - }; - } - - VeilidFFIConfig.fromJson(Map json) - : logging = VeilidFFIConfigLogging.fromJson(json['logging']); -} - -////////////////////////////////////////////////////////// -// WASM Platform-specific config - -class VeilidWASMConfigLoggingPerformance { - bool enabled; - VeilidConfigLogLevel level; - bool logsInTimings; - bool logsInConsole; - - VeilidWASMConfigLoggingPerformance({ - required this.enabled, - required this.level, - required this.logsInTimings, - required this.logsInConsole, - }); - - Map get json { - return { - 'enabled': enabled, - 'level': level.json, - 'logs_in_timings': logsInTimings, - 'logs_in_console': logsInConsole, - }; - } - - VeilidWASMConfigLoggingPerformance.fromJson(dynamic json) - : enabled = json['enabled'], - level = veilidConfigLogLevelFromJson(json['level']), - logsInTimings = json['logs_in_timings'], - logsInConsole = json['logs_in_console']; -} - -class VeilidWASMConfigLoggingApi { - bool enabled; - VeilidConfigLogLevel level; - - VeilidWASMConfigLoggingApi({ - required this.enabled, - required this.level, - }); - - Map get json { - return { - 'enabled': enabled, - 'level': level.json, - }; - } - - VeilidWASMConfigLoggingApi.fromJson(dynamic json) - : enabled = json['enabled'], - level = veilidConfigLogLevelFromJson(json['level']); -} - -class VeilidWASMConfigLogging { - VeilidWASMConfigLoggingPerformance performance; - VeilidWASMConfigLoggingApi api; - - VeilidWASMConfigLogging({required this.performance, required this.api}); - - Map get json { - return { - 'performance': performance.json, - 'api': api.json, - }; - } - - VeilidWASMConfigLogging.fromJson(dynamic json) - : performance = - VeilidWASMConfigLoggingPerformance.fromJson(json['performance']), - api = VeilidWASMConfigLoggingApi.fromJson(json['api']); -} - -class VeilidWASMConfig { - VeilidWASMConfigLogging logging; - - VeilidWASMConfig({ - required this.logging, - }); - - Map get json { - return { - 'logging': logging.json, - }; - } - - VeilidWASMConfig.fromJson(dynamic json) - : logging = VeilidWASMConfigLogging.fromJson(json['logging']); -} +export 'routing_context.dart'; +export 'veilid_config.dart'; +export 'veilid_crypto.dart'; +export 'veilid_table_db.dart'; +export 'veilid_api_exception.dart'; +export 'veilid_state.dart'; +export 'veilid.dart'; ////////////////////////////////////// /// JSON Encode Helper @@ -234,1810 +46,6 @@ Object? veilidApiToEncodable(Object? value) { throw UnsupportedError('Cannot convert to JSON: $value'); } -////////////////////////////////////// -/// Crypto - -typedef CryptoKind = String; -const cryptoKindVLD0 = "VLD0"; -const cryptoKindNONE = "NONE"; - -////////////////////////////////////// -/// DHT Schema - -abstract class DHTSchema { - factory DHTSchema.fromJson(dynamic json) { - switch (json["kind"]) { - case "DFLT": - { - return DHTSchemaDFLT(oCnt: json["o_cnt"]); - } - case "SMPL": - { - return DHTSchemaSMPL( - oCnt: json["o_cnt"], - members: List.from( - json['members'].map((j) => DHTSchemaMember.fromJson(j)))); - } - default: - { - throw VeilidAPIExceptionInternal( - "Invalid VeilidAPIException type: ${json['kind']}"); - } - } - } - Map get json; -} - -class DHTSchemaDFLT implements DHTSchema { - final int oCnt; - // - DHTSchemaDFLT({ - required this.oCnt, - }) { - if (oCnt < 0 || oCnt > 65535) { - throw VeilidAPIExceptionInvalidArgument( - "value out of range", "oCnt", oCnt.toString()); - } - } - - @override - Map get json { - return { - 'kind': "DFLT", - 'o_cnt': oCnt, - }; - } -} - -class DHTSchemaMember { - String mKey; - int mCnt; - - DHTSchemaMember({ - required this.mKey, - required this.mCnt, - }) { - if (mCnt < 0 || mCnt > 65535) { - throw VeilidAPIExceptionInvalidArgument( - "value out of range", "mCnt", mCnt.toString()); - } - } - - Map get json { - return { - 'm_key': mKey, - 'm_cnt': mCnt, - }; - } - - DHTSchemaMember.fromJson(dynamic json) - : mKey = json['m_key'], - mCnt = json['m_cnt']; -} - -class DHTSchemaSMPL implements DHTSchema { - final int oCnt; - final List members; - // - DHTSchemaSMPL({ - required this.oCnt, - required this.members, - }) { - if (oCnt < 0 || oCnt > 65535) { - throw VeilidAPIExceptionInvalidArgument( - "value out of range", "oCnt", oCnt.toString()); - } - } - @override - Map get json { - return { - 'kind': "SMPL", - 'o_cnt': oCnt, - 'members': members.map((p) => p.json).toList(), - }; - } -} - -////////////////////////////////////// -/// DHTRecordDescriptor - -class DHTRecordDescriptor { - String key; - String owner; - String? ownerSecret; - DHTSchema schema; - - DHTRecordDescriptor({ - required this.key, - required this.owner, - this.ownerSecret, - required this.schema, - }); - - Map get json { - return { - 'key': key, - 'owner': owner, - 'owner_secret': ownerSecret, - 'schema': schema.json, - }; - } - - DHTRecordDescriptor.fromJson(dynamic json) - : key = json['key'], - owner = json['owner'], - ownerSecret = json['owner_secret'], - schema = DHTSchema.fromJson(json['schema']); -} - -////////////////////////////////////// -/// ValueSubkeyRange - -class ValueSubkeyRange { - final int low; - final int high; - - ValueSubkeyRange({ - required this.low, - required this.high, - }) { - if (low < 0 || low > high) { - throw VeilidAPIExceptionInvalidArgument( - "invalid range", "low", low.toString()); - } - if (high < 0) { - throw VeilidAPIExceptionInvalidArgument( - "invalid range", "high", high.toString()); - } - } - - ValueSubkeyRange.fromJson(dynamic json) - : low = json[0], - high = json[1] { - if ((json as List).length != 2) { - throw VeilidAPIExceptionInvalidArgument( - "not a pair of integers", "json", json.toString()); - } - } - - List get json { - return [low, high]; - } -} - -////////////////////////////////////// -/// ValueData - -class ValueData { - final int seq; - final Uint8List data; - final String writer; - - ValueData({ - required this.seq, - required this.data, - required this.writer, - }); - - ValueData.fromJson(dynamic json) - : seq = json['seq'], - data = base64UrlNoPadDecode(json['data']), - writer = json['writer']; - - Map get json { - return {'seq': seq, 'data': base64UrlNoPadEncode(data), 'writer': writer}; - } -} - -////////////////////////////////////// -/// AttachmentState - -enum AttachmentState { - detached, - attaching, - attachedWeak, - attachedGood, - attachedStrong, - fullyAttached, - overAttached, - detaching, -} - -extension AttachmentStateExt on AttachmentState { - String get json { - return name.toPascalCase(); - } -} - -AttachmentState attachmentStateFromJson(String j) { - return AttachmentState.values.byName(j.toCamelCase()); -} - -////////////////////////////////////// -/// VeilidLogLevel - -enum VeilidLogLevel { - error, - warn, - info, - debug, - trace, -} - -extension VeilidLogLevelExt on VeilidLogLevel { - String get json { - return name.toPascalCase(); - } -} - -VeilidLogLevel veilidLogLevelFromJson(String j) { - return VeilidLogLevel.values.byName(j.toCamelCase()); -} - -////////////////////////////////////// -/// VeilidConfigLogLevel - -enum VeilidConfigLogLevel { - off, - error, - warn, - info, - debug, - trace, -} - -extension VeilidConfigLogLevelExt on VeilidConfigLogLevel { - String get json { - return name.toPascalCase(); - } -} - -VeilidConfigLogLevel veilidConfigLogLevelFromJson(String j) { - return VeilidConfigLogLevel.values.byName(j.toCamelCase()); -} - -////////////////////////////////////// -/// VeilidConfig - -class VeilidConfigHTTPS { - bool enabled; - String listenAddress; - String path; - String? url; - - VeilidConfigHTTPS({ - required this.enabled, - required this.listenAddress, - required this.path, - this.url, - }); - - Map get json { - return { - 'enabled': enabled, - 'listen_address': listenAddress, - 'path': path, - 'url': url - }; - } - - VeilidConfigHTTPS.fromJson(dynamic json) - : enabled = json['enabled'], - listenAddress = json['listen_address'], - path = json['path'], - url = json['url']; -} - -//////////// - -class VeilidConfigHTTP { - bool enabled; - String listenAddress; - String path; - String? url; - - VeilidConfigHTTP({ - required this.enabled, - required this.listenAddress, - required this.path, - this.url, - }); - - Map get json { - return { - 'enabled': enabled, - 'listen_address': listenAddress, - 'path': path, - 'url': url - }; - } - - VeilidConfigHTTP.fromJson(dynamic json) - : enabled = json['enabled'], - listenAddress = json['listen_address'], - path = json['path'], - url = json['url']; -} - -//////////// - -class VeilidConfigApplication { - VeilidConfigHTTPS https; - VeilidConfigHTTP http; - - VeilidConfigApplication({ - required this.https, - required this.http, - }); - - Map get json { - return { - 'https': https.json, - 'http': http.json, - }; - } - - VeilidConfigApplication.fromJson(dynamic json) - : https = VeilidConfigHTTPS.fromJson(json['https']), - http = VeilidConfigHTTP.fromJson(json['http']); -} - -//////////// - -class VeilidConfigUDP { - bool enabled; - int socketPoolSize; - String listenAddress; - String? publicAddress; - - VeilidConfigUDP( - {required this.enabled, - required this.socketPoolSize, - required this.listenAddress, - this.publicAddress}); - - Map get json { - return { - 'enabled': enabled, - 'socket_pool_size': socketPoolSize, - 'listen_address': listenAddress, - 'public_address': publicAddress, - }; - } - - VeilidConfigUDP.fromJson(dynamic json) - : enabled = json['enabled'], - socketPoolSize = json['socket_pool_size'], - listenAddress = json['listen_address'], - publicAddress = json['publicAddress']; -} - -//////////// - -class VeilidConfigTCP { - bool connect; - bool listen; - int maxConnections; - String listenAddress; - String? publicAddress; - - VeilidConfigTCP( - {required this.connect, - required this.listen, - required this.maxConnections, - required this.listenAddress, - this.publicAddress}); - - Map get json { - return { - 'connect': connect, - 'listen': listen, - 'max_connections': maxConnections, - 'listen_address': listenAddress, - 'public_address': publicAddress, - }; - } - - VeilidConfigTCP.fromJson(dynamic json) - : connect = json['connect'], - listen = json['listen'], - maxConnections = json['max_connections'], - listenAddress = json['listen_address'], - publicAddress = json['publicAddress']; -} - -//////////// - -class VeilidConfigWS { - bool connect; - bool listen; - int maxConnections; - String listenAddress; - String path; - String? url; - - VeilidConfigWS( - {required this.connect, - required this.listen, - required this.maxConnections, - required this.listenAddress, - required this.path, - this.url}); - - Map get json { - return { - 'connect': connect, - 'listen': listen, - 'max_connections': maxConnections, - 'listen_address': listenAddress, - 'path': path, - 'url': url, - }; - } - - VeilidConfigWS.fromJson(dynamic json) - : connect = json['connect'], - listen = json['listen'], - maxConnections = json['max_connections'], - listenAddress = json['listen_address'], - path = json['path'], - url = json['url']; -} - -//////////// - -class VeilidConfigWSS { - bool connect; - bool listen; - int maxConnections; - String listenAddress; - String path; - String? url; - - VeilidConfigWSS( - {required this.connect, - required this.listen, - required this.maxConnections, - required this.listenAddress, - required this.path, - this.url}); - - Map get json { - return { - 'connect': connect, - 'listen': listen, - 'max_connections': maxConnections, - 'listen_address': listenAddress, - 'path': path, - 'url': url, - }; - } - - VeilidConfigWSS.fromJson(dynamic json) - : connect = json['connect'], - listen = json['listen'], - maxConnections = json['max_connections'], - listenAddress = json['listen_address'], - path = json['path'], - url = json['url']; -} - -//////////// - -class VeilidConfigProtocol { - VeilidConfigUDP udp; - VeilidConfigTCP tcp; - VeilidConfigWS ws; - VeilidConfigWSS wss; - - VeilidConfigProtocol({ - required this.udp, - required this.tcp, - required this.ws, - required this.wss, - }); - - Map get json { - return { - 'udp': udp.json, - 'tcp': tcp.json, - 'ws': ws.json, - 'wss': wss.json, - }; - } - - VeilidConfigProtocol.fromJson(dynamic json) - : udp = VeilidConfigUDP.fromJson(json['udp']), - tcp = VeilidConfigTCP.fromJson(json['tcp']), - ws = VeilidConfigWS.fromJson(json['ws']), - wss = VeilidConfigWSS.fromJson(json['wss']); -} - -//////////// - -class VeilidConfigTLS { - String certificatePath; - String privateKeyPath; - int connectionInitialTimeoutMs; - - VeilidConfigTLS({ - required this.certificatePath, - required this.privateKeyPath, - required this.connectionInitialTimeoutMs, - }); - - Map get json { - return { - 'certificate_path': certificatePath, - 'private_key_path': privateKeyPath, - 'connection_initial_timeout_ms': connectionInitialTimeoutMs, - }; - } - - VeilidConfigTLS.fromJson(dynamic json) - : certificatePath = json['certificate_path'], - privateKeyPath = json['private_key_path'], - connectionInitialTimeoutMs = json['connection_initial_timeout_ms']; -} - -//////////// - -class VeilidConfigDHT { - int resolveNodeTimeoutMs; - int resolveNodeCount; - int resolveNodeFanout; - int maxFindNodeCount; - int getValueTimeoutMs; - int getValueCount; - int getValueFanout; - int setValueTimeoutMs; - int setValueCount; - int setValueFanout; - int minPeerCount; - int minPeerRefreshTimeMs; - int validateDialInfoReceiptTimeMs; - int localSubkeyCacheSize; - int localMaxSubkeyCacheMemoryMb; - int remoteSubkeyCacheSize; - int remoteMaxRecords; - int remoteMaxSubkeyCacheMemoryMb; - int remoteMaxStorageSpaceMb; - - VeilidConfigDHT( - {required this.resolveNodeTimeoutMs, - required this.resolveNodeCount, - required this.resolveNodeFanout, - required this.maxFindNodeCount, - required this.getValueTimeoutMs, - required this.getValueCount, - required this.getValueFanout, - required this.setValueTimeoutMs, - required this.setValueCount, - required this.setValueFanout, - required this.minPeerCount, - required this.minPeerRefreshTimeMs, - required this.validateDialInfoReceiptTimeMs, - required this.localSubkeyCacheSize, - required this.localMaxSubkeyCacheMemoryMb, - required this.remoteSubkeyCacheSize, - required this.remoteMaxRecords, - required this.remoteMaxSubkeyCacheMemoryMb, - required this.remoteMaxStorageSpaceMb}); - - Map get json { - return { - 'max_find_node_count': maxFindNodeCount, - 'resolve_node_timeout_ms': resolveNodeTimeoutMs, - 'resolve_node_count': resolveNodeCount, - 'resolve_node_fanout': resolveNodeFanout, - 'get_value_timeout_ms': getValueTimeoutMs, - 'get_value_count': getValueCount, - 'get_value_fanout': getValueFanout, - 'set_value_timeout_ms': setValueTimeoutMs, - 'set_value_count': setValueCount, - 'set_value_fanout': setValueFanout, - 'min_peer_count': minPeerCount, - 'min_peer_refresh_time_ms': minPeerRefreshTimeMs, - 'validate_dial_info_receipt_time_ms': validateDialInfoReceiptTimeMs, - 'local_subkey_cache_size: 128': localSubkeyCacheSize, - 'local_max_subkey_cache_memory_mb': localMaxSubkeyCacheMemoryMb, - 'remote_subkey_cache_size': remoteSubkeyCacheSize, - 'remote_max_records': remoteMaxRecords, - 'remote_max_subkey_cache_memory_mb': remoteMaxSubkeyCacheMemoryMb, - 'remote_max_storage_space_mb': remoteMaxStorageSpaceMb, - }; - } - - VeilidConfigDHT.fromJson(dynamic json) - : resolveNodeTimeoutMs = json['resolve_node_timeout_ms'], - resolveNodeCount = json['resolve_node_count'], - resolveNodeFanout = json['resolve_node_fanout'], - maxFindNodeCount = json['max_find_node_count'], - getValueTimeoutMs = json['get_value_timeout_ms'], - getValueCount = json['get_value_count'], - getValueFanout = json['get_value_fanout'], - setValueTimeoutMs = json['set_value_timeout_ms'], - setValueCount = json['set_value_count'], - setValueFanout = json['set_value_fanout'], - minPeerCount = json['min_peer_count'], - minPeerRefreshTimeMs = json['min_peer_refresh_time_ms'], - validateDialInfoReceiptTimeMs = - json['validate_dial_info_receipt_time_ms'], - localSubkeyCacheSize = json['local_subkey_cache_size'], - localMaxSubkeyCacheMemoryMb = json['local_max_subkey_cache_memory_mb'], - remoteSubkeyCacheSize = json['remote_subkey_cache_size'], - remoteMaxRecords = json['remote_max_records'], - remoteMaxSubkeyCacheMemoryMb = - json['remote_max_subkey_cache_memory_mb'], - remoteMaxStorageSpaceMb = json['remote_max_storage_space_mb']; -} - -//////////// - -class VeilidConfigRPC { - int concurrency; - int queueSize; - int? maxTimestampBehindMs; - int? maxTimestampAheadMs; - int timeoutMs; - int maxRouteHopCount; - int defaultRouteHopCount; - - VeilidConfigRPC( - {required this.concurrency, - required this.queueSize, - this.maxTimestampBehindMs, - this.maxTimestampAheadMs, - required this.timeoutMs, - required this.maxRouteHopCount, - required this.defaultRouteHopCount}); - - Map get json { - return { - 'concurrency': concurrency, - 'queue_size': queueSize, - 'max_timestamp_behind_ms': maxTimestampBehindMs, - 'max_timestamp_ahead_ms': maxTimestampAheadMs, - 'timeout_ms': timeoutMs, - 'max_route_hop_count': maxRouteHopCount, - 'default_route_hop_count': defaultRouteHopCount, - }; - } - - VeilidConfigRPC.fromJson(dynamic json) - : concurrency = json['concurrency'], - queueSize = json['queue_size'], - maxTimestampBehindMs = json['max_timestamp_behind_ms'], - maxTimestampAheadMs = json['max_timestamp_ahead_ms'], - timeoutMs = json['timeout_ms'], - maxRouteHopCount = json['max_route_hop_count'], - defaultRouteHopCount = json['default_route_hop_count']; -} - -//////////// - -class VeilidConfigRoutingTable { - List nodeId; - List nodeIdSecret; - List bootstrap; - int limitOverAttached; - int limitFullyAttached; - int limitAttachedStrong; - int limitAttachedGood; - int limitAttachedWeak; - - VeilidConfigRoutingTable({ - required this.nodeId, - required this.nodeIdSecret, - required this.bootstrap, - required this.limitOverAttached, - required this.limitFullyAttached, - required this.limitAttachedStrong, - required this.limitAttachedGood, - required this.limitAttachedWeak, - }); - - Map get json { - return { - 'node_id': nodeId.map((p) => p).toList(), - 'node_id_secret': nodeIdSecret.map((p) => p).toList(), - 'bootstrap': bootstrap.map((p) => p).toList(), - 'limit_over_attached': limitOverAttached, - 'limit_fully_attached': limitFullyAttached, - 'limit_attached_strong': limitAttachedStrong, - 'limit_attached_good': limitAttachedGood, - 'limit_attached_weak': limitAttachedWeak, - }; - } - - VeilidConfigRoutingTable.fromJson(dynamic json) - : nodeId = List.from(json['node_id'].map((j) => j)), - nodeIdSecret = List.from(json['node_id_secret'].map((j) => j)), - bootstrap = List.from(json['bootstrap'].map((j) => j)), - limitOverAttached = json['limit_over_attached'], - limitFullyAttached = json['limit_fully_attached'], - limitAttachedStrong = json['limit_attached_strong'], - limitAttachedGood = json['limit_attached_good'], - limitAttachedWeak = json['limit_attached_weak']; -} - -//////////// - -class VeilidConfigNetwork { - int connectionInitialTimeoutMs; - int connectionInactivityTimeoutMs; - int maxConnectionsPerIp4; - int maxConnectionsPerIp6Prefix; - int maxConnectionsPerIp6PrefixSize; - int maxConnectionFrequencyPerMin; - int clientWhitelistTimeoutMs; - int reverseConnectionReceiptTimeMs; - int holePunchReceiptTimeMs; - VeilidConfigRoutingTable routingTable; - VeilidConfigRPC rpc; - VeilidConfigDHT dht; - bool upnp; - bool detectAddressChanges; - int restrictedNatRetries; - VeilidConfigTLS tls; - VeilidConfigApplication application; - VeilidConfigProtocol protocol; - - VeilidConfigNetwork({ - required this.connectionInitialTimeoutMs, - required this.connectionInactivityTimeoutMs, - required this.maxConnectionsPerIp4, - required this.maxConnectionsPerIp6Prefix, - required this.maxConnectionsPerIp6PrefixSize, - required this.maxConnectionFrequencyPerMin, - required this.clientWhitelistTimeoutMs, - required this.reverseConnectionReceiptTimeMs, - required this.holePunchReceiptTimeMs, - required this.routingTable, - required this.rpc, - required this.dht, - required this.upnp, - required this.detectAddressChanges, - required this.restrictedNatRetries, - required this.tls, - required this.application, - required this.protocol, - }); - - Map get json { - return { - 'connection_initial_timeout_ms': connectionInitialTimeoutMs, - 'connection_inactivity_timeout_ms': connectionInactivityTimeoutMs, - 'max_connections_per_ip4': maxConnectionsPerIp4, - 'max_connections_per_ip6_prefix': maxConnectionsPerIp6Prefix, - 'max_connections_per_ip6_prefix_size': maxConnectionsPerIp6PrefixSize, - 'max_connection_frequency_per_min': maxConnectionFrequencyPerMin, - 'client_whitelist_timeout_ms': clientWhitelistTimeoutMs, - 'reverse_connection_receipt_time_ms': reverseConnectionReceiptTimeMs, - 'hole_punch_receipt_time_ms': holePunchReceiptTimeMs, - 'routing_table': routingTable.json, - 'rpc': rpc.json, - 'dht': dht.json, - 'upnp': upnp, - 'detect_address_changes': detectAddressChanges, - 'restricted_nat_retries': restrictedNatRetries, - 'tls': tls.json, - 'application': application.json, - 'protocol': protocol.json, - }; - } - - VeilidConfigNetwork.fromJson(dynamic json) - : connectionInitialTimeoutMs = json['connection_initial_timeout_ms'], - connectionInactivityTimeoutMs = - json['connection_inactivity_timeout_ms'], - maxConnectionsPerIp4 = json['max_connections_per_ip4'], - maxConnectionsPerIp6Prefix = json['max_connections_per_ip6_prefix'], - maxConnectionsPerIp6PrefixSize = - json['max_connections_per_ip6_prefix_size'], - maxConnectionFrequencyPerMin = json['max_connection_frequency_per_min'], - clientWhitelistTimeoutMs = json['client_whitelist_timeout_ms'], - reverseConnectionReceiptTimeMs = - json['reverse_connection_receipt_time_ms'], - holePunchReceiptTimeMs = json['hole_punch_receipt_time_ms'], - routingTable = VeilidConfigRoutingTable.fromJson(json['routing_table']), - rpc = VeilidConfigRPC.fromJson(json['rpc']), - dht = VeilidConfigDHT.fromJson(json['dht']), - upnp = json['upnp'], - detectAddressChanges = json['detect_address_changes'], - restrictedNatRetries = json['restricted_nat_retries'], - tls = VeilidConfigTLS.fromJson(json['tls']), - application = VeilidConfigApplication.fromJson(json['application']), - protocol = VeilidConfigProtocol.fromJson(json['protocol']); -} - -//////////// - -class VeilidConfigTableStore { - String directory; - bool delete; - - VeilidConfigTableStore({ - required this.directory, - required this.delete, - }); - - Map get json { - return {'directory': directory, 'delete': delete}; - } - - VeilidConfigTableStore.fromJson(dynamic json) - : directory = json['directory'], - delete = json['delete']; -} - -//////////// - -class VeilidConfigBlockStore { - String directory; - bool delete; - - VeilidConfigBlockStore({ - required this.directory, - required this.delete, - }); - - Map get json { - return {'directory': directory, 'delete': delete}; - } - - VeilidConfigBlockStore.fromJson(dynamic json) - : directory = json['directory'], - delete = json['delete']; -} - -//////////// - -class VeilidConfigProtectedStore { - bool allowInsecureFallback; - bool alwaysUseInsecureStorage; - String insecureFallbackDirectory; - bool delete; - - VeilidConfigProtectedStore({ - required this.allowInsecureFallback, - required this.alwaysUseInsecureStorage, - required this.insecureFallbackDirectory, - required this.delete, - }); - - Map get json { - return { - 'allow_insecure_fallback': allowInsecureFallback, - 'always_use_insecure_storage': alwaysUseInsecureStorage, - 'insecure_fallback_directory': insecureFallbackDirectory, - 'delete': delete, - }; - } - - VeilidConfigProtectedStore.fromJson(dynamic json) - : allowInsecureFallback = json['allow_insecure_fallback'], - alwaysUseInsecureStorage = json['always_use_insecure_storage'], - insecureFallbackDirectory = json['insecure_fallback_directory'], - delete = json['delete']; -} - -//////////// - -class VeilidConfigCapabilities { - bool protocolUDP; - bool protocolConnectTCP; - bool protocolAcceptTCP; - bool protocolConnectWS; - bool protocolAcceptWS; - bool protocolConnectWSS; - bool protocolAcceptWSS; - - VeilidConfigCapabilities({ - required this.protocolUDP, - required this.protocolConnectTCP, - required this.protocolAcceptTCP, - required this.protocolConnectWS, - required this.protocolAcceptWS, - required this.protocolConnectWSS, - required this.protocolAcceptWSS, - }); - - Map get json { - return { - 'protocol_udp': protocolUDP, - 'protocol_connect_tcp': protocolConnectTCP, - 'protocol_accept_tcp': protocolAcceptTCP, - 'protocol_connect_ws': protocolConnectWS, - 'protocol_accept_ws': protocolAcceptWS, - 'protocol_connect_wss': protocolConnectWSS, - 'protocol_accept_wss': protocolAcceptWSS, - }; - } - - VeilidConfigCapabilities.fromJson(dynamic json) - : protocolUDP = json['protocol_udp'], - protocolConnectTCP = json['protocol_connect_tcp'], - protocolAcceptTCP = json['protocol_accept_tcp'], - protocolConnectWS = json['protocol_connect_ws'], - protocolAcceptWS = json['protocol_accept_ws'], - protocolConnectWSS = json['protocol_connect_wss'], - protocolAcceptWSS = json['protocol_accept_wss']; -} - -//////////// - -class VeilidConfig { - String programName; - String namespace; - VeilidConfigCapabilities capabilities; - VeilidConfigProtectedStore protectedStore; - VeilidConfigTableStore tableStore; - VeilidConfigBlockStore blockStore; - VeilidConfigNetwork network; - - VeilidConfig({ - required this.programName, - required this.namespace, - required this.capabilities, - required this.protectedStore, - required this.tableStore, - required this.blockStore, - required this.network, - }); - - Map get json { - return { - 'program_name': programName, - 'namespace': namespace, - 'capabilities': capabilities.json, - 'protected_store': protectedStore.json, - 'table_store': tableStore.json, - 'block_store': blockStore.json, - 'network': network.json - }; - } - - VeilidConfig.fromJson(dynamic json) - : programName = json['program_name'], - namespace = json['namespace'], - capabilities = VeilidConfigCapabilities.fromJson(json['capabilities']), - protectedStore = - VeilidConfigProtectedStore.fromJson(json['protected_store']), - tableStore = VeilidConfigTableStore.fromJson(json['table_store']), - blockStore = VeilidConfigBlockStore.fromJson(json['block_store']), - network = VeilidConfigNetwork.fromJson(json['network']); -} - -//////////// - -class LatencyStats { - BigInt fastest; - BigInt average; - BigInt slowest; - - LatencyStats({ - required this.fastest, - required this.average, - required this.slowest, - }); - - Map get json { - return { - 'fastest': fastest.toString(), - 'average': average.toString(), - 'slowest': slowest.toString(), - }; - } - - LatencyStats.fromJson(dynamic json) - : fastest = BigInt.parse(json['fastest']), - average = BigInt.parse(json['average']), - slowest = BigInt.parse(json['slowest']); -} - -//////////// - -class TransferStats { - BigInt total; - BigInt maximum; - BigInt average; - BigInt minimum; - - TransferStats({ - required this.total, - required this.maximum, - required this.average, - required this.minimum, - }); - - Map get json { - return { - 'total': total.toString(), - 'maximum': maximum.toString(), - 'average': average.toString(), - 'minimum': minimum.toString(), - }; - } - - TransferStats.fromJson(dynamic json) - : total = BigInt.parse(json['total']), - maximum = BigInt.parse(json['maximum']), - average = BigInt.parse(json['average']), - minimum = BigInt.parse(json['minimum']); -} - -//////////// - -class TransferStatsDownUp { - TransferStats down; - TransferStats up; - - TransferStatsDownUp({ - required this.down, - required this.up, - }); - - Map get json { - return { - 'down': down.json, - 'up': up.json, - }; - } - - TransferStatsDownUp.fromJson(dynamic json) - : down = TransferStats.fromJson(json['down']), - up = TransferStats.fromJson(json['up']); -} - -//////////// - -class RPCStats { - int messagesSent; - int messagesRcvd; - int questionsInFlight; - BigInt? lastQuestion; - BigInt? lastSeenTs; - BigInt? firstConsecutiveSeenTs; - int recentLostAnswers; - int failedToSend; - - RPCStats({ - required this.messagesSent, - required this.messagesRcvd, - required this.questionsInFlight, - required this.lastQuestion, - required this.lastSeenTs, - required this.firstConsecutiveSeenTs, - required this.recentLostAnswers, - required this.failedToSend, - }); - - Map get json { - return { - 'messages_sent': messagesSent, - 'messages_rcvd': messagesRcvd, - 'questions_in_flight': questionsInFlight, - 'last_question': lastQuestion?.toString(), - 'last_seen_ts': lastSeenTs?.toString(), - 'first_consecutive_seen_ts': firstConsecutiveSeenTs?.toString(), - 'recent_lost_answers': recentLostAnswers, - 'failed_to_send': failedToSend, - }; - } - - RPCStats.fromJson(dynamic json) - : messagesSent = json['messages_sent'], - messagesRcvd = json['messages_rcvd'], - questionsInFlight = json['questions_in_flight'], - lastQuestion = json['last_question'] != null - ? BigInt.parse(json['last_question']) - : null, - lastSeenTs = json['last_seen_ts'] != null - ? BigInt.parse(json['last_seen_ts']) - : null, - firstConsecutiveSeenTs = json['first_consecutive_seen_ts'] != null - ? BigInt.parse(json['first_consecutive_seen_ts']) - : null, - recentLostAnswers = json['recent_lost_answers'], - failedToSend = json['failed_to_send']; -} - -//////////// - -class PeerStats { - BigInt timeAdded; - RPCStats rpcStats; - LatencyStats? latency; - TransferStatsDownUp transfer; - - PeerStats({ - required this.timeAdded, - required this.rpcStats, - required this.latency, - required this.transfer, - }); - - Map get json { - return { - 'time_added': timeAdded.toString(), - 'rpc_stats': rpcStats.json, - 'latency': latency?.json, - 'transfer': transfer.json, - }; - } - - PeerStats.fromJson(dynamic json) - : timeAdded = BigInt.parse(json['time_added']), - rpcStats = RPCStats.fromJson(json['rpc_stats']), - latency = json['latency'] != null - ? LatencyStats.fromJson(json['latency']) - : null, - transfer = TransferStatsDownUp.fromJson(json['transfer']); -} - -//////////// - -class PeerTableData { - List nodeIds; - PeerAddress peerAddress; - PeerStats peerStats; - - PeerTableData({ - required this.nodeIds, - required this.peerAddress, - required this.peerStats, - }); - - Map get json { - return { - 'node_ids': nodeIds.map((p) => p).toList(), - 'peer_address': peerAddress.json, - 'peer_stats': peerStats.json, - }; - } - - PeerTableData.fromJson(dynamic json) - : nodeIds = List.from(json['node_ids'].map((j) => j)), - peerAddress = PeerAddress.fromJson(json['peer_address']), - peerStats = PeerStats.fromJson(json['peer_stats']); -} - -////////////////////////////////////// -/// AttachmentState - -enum ProtocolType { - udp, - tcp, - ws, - wss, -} - -extension ProtocolTypeExt on ProtocolType { - String get json { - return name.toUpperCase(); - } -} - -ProtocolType protocolTypeFromJson(String j) { - return ProtocolType.values.byName(j.toLowerCase()); -} - -//////////// - -class PeerAddress { - ProtocolType protocolType; - String socketAddress; - - PeerAddress({ - required this.protocolType, - required this.socketAddress, - }); - - Map get json { - return { - 'protocol_type': protocolType.json, - 'socket_address': socketAddress, - }; - } - - PeerAddress.fromJson(dynamic json) - : protocolType = protocolTypeFromJson(json['protocol_type']), - socketAddress = json['socket_address']; -} - -////////////////////////////////////// -/// VeilidUpdate - -abstract class VeilidUpdate { - factory VeilidUpdate.fromJson(dynamic json) { - switch (json["kind"]) { - case "Log": - { - return VeilidLog( - logLevel: veilidLogLevelFromJson(json["log_level"]), - message: json["message"], - backtrace: json["backtrace"]); - } - case "AppMessage": - { - return VeilidAppMessage( - sender: json["sender"], message: json["message"]); - } - case "AppCall": - { - return VeilidAppCall( - sender: json["sender"], message: json["message"], id: json["id"]); - } - case "Attachment": - { - return VeilidUpdateAttachment( - state: VeilidStateAttachment.fromJson(json)); - } - case "Network": - { - return VeilidUpdateNetwork(state: VeilidStateNetwork.fromJson(json)); - } - case "Config": - { - return VeilidUpdateConfig(state: VeilidStateConfig.fromJson(json)); - } - case "RouteChange": - { - return VeilidUpdateRouteChange( - deadRoutes: List.from(json['dead_routes'].map((j) => j)), - deadRemoteRoutes: - List.from(json['dead_remote_routes'].map((j) => j))); - } - case "ValueChange": - { - return VeilidUpdateValueChange( - key: json['key'], - subkeys: List.from( - json['subkeys'].map((j) => ValueSubkeyRange.fromJson(j))), - count: json['count'], - valueData: ValueData.fromJson(json['value_data'])); - } - default: - { - throw VeilidAPIExceptionInternal( - "Invalid VeilidAPIException type: ${json['kind']}"); - } - } - } - Map get json; -} - -class VeilidLog implements VeilidUpdate { - final VeilidLogLevel logLevel; - final String message; - final String? backtrace; - // - VeilidLog({ - required this.logLevel, - required this.message, - required this.backtrace, - }); - - @override - Map get json { - return { - 'kind': "Log", - 'log_level': logLevel.json, - 'message': message, - 'backtrace': backtrace - }; - } -} - -class VeilidAppMessage implements VeilidUpdate { - final String? sender; - final Uint8List message; - - // - VeilidAppMessage({ - required this.sender, - required this.message, - }); - - @override - Map get json { - return { - 'kind': "AppMessage", - 'sender': sender, - 'message': base64UrlNoPadEncode(message) - }; - } -} - -class VeilidAppCall implements VeilidUpdate { - final String? sender; - final Uint8List message; - final String id; - - // - VeilidAppCall({ - required this.sender, - required this.message, - required this.id, - }); - - @override - Map get json { - return { - 'kind': "AppMessage", - 'sender': sender, - 'message': base64UrlNoPadEncode(message), - 'id': id, - }; - } -} - -class VeilidUpdateAttachment implements VeilidUpdate { - final VeilidStateAttachment state; - // - VeilidUpdateAttachment({required this.state}); - - @override - Map get json { - var jsonRep = state.json; - jsonRep['kind'] = "Attachment"; - return jsonRep; - } -} - -class VeilidUpdateNetwork implements VeilidUpdate { - final VeilidStateNetwork state; - // - VeilidUpdateNetwork({required this.state}); - - @override - Map get json { - var jsonRep = state.json; - jsonRep['kind'] = "Network"; - return jsonRep; - } -} - -class VeilidUpdateConfig implements VeilidUpdate { - final VeilidStateConfig state; - // - VeilidUpdateConfig({required this.state}); - - @override - Map get json { - var jsonRep = state.json; - jsonRep['kind'] = "Config"; - return jsonRep; - } -} - -class VeilidUpdateRouteChange implements VeilidUpdate { - final List deadRoutes; - final List deadRemoteRoutes; - // - VeilidUpdateRouteChange({ - required this.deadRoutes, - required this.deadRemoteRoutes, - }); - - @override - Map get json { - return { - 'dead_routes': deadRoutes.map((p) => p).toList(), - 'dead_remote_routes': deadRemoteRoutes.map((p) => p).toList() - }; - } -} - -class VeilidUpdateValueChange implements VeilidUpdate { - final String key; - final List subkeys; - final int count; - final ValueData valueData; - - // - VeilidUpdateValueChange({ - required this.key, - required this.subkeys, - required this.count, - required this.valueData, - }); - - @override - Map get json { - return { - 'key': key, - 'subkeys': subkeys.map((p) => p.json).toList(), - 'count': count, - 'value_data': valueData.json, - }; - } -} - -////////////////////////////////////// -/// VeilidStateAttachment - -class VeilidStateAttachment { - final AttachmentState state; - final bool publicInternetReady; - final bool localNetworkReady; - - VeilidStateAttachment( - this.state, this.publicInternetReady, this.localNetworkReady); - - VeilidStateAttachment.fromJson(dynamic json) - : state = attachmentStateFromJson(json['state']), - publicInternetReady = json['public_internet_ready'], - localNetworkReady = json['local_network_ready']; - - Map get json { - return { - 'state': state.json, - 'public_internet_ready': publicInternetReady, - 'local_network_ready': localNetworkReady, - }; - } -} - -////////////////////////////////////// -/// VeilidStateNetwork - -class VeilidStateNetwork { - final bool started; - final BigInt bpsDown; - final BigInt bpsUp; - final List peers; - - VeilidStateNetwork( - {required this.started, - required this.bpsDown, - required this.bpsUp, - required this.peers}); - - VeilidStateNetwork.fromJson(dynamic json) - : started = json['started'], - bpsDown = BigInt.parse(json['bps_down']), - bpsUp = BigInt.parse(json['bps_up']), - peers = List.from( - json['peers'].map((j) => PeerTableData.fromJson(j))); - - Map get json { - return { - 'started': started, - 'bps_down': bpsDown.toString(), - 'bps_up': bpsUp.toString(), - 'peers': peers.map((p) => p.json).toList(), - }; - } -} - -////////////////////////////////////// -/// VeilidStateConfig - -class VeilidStateConfig { - final Map config; - - VeilidStateConfig({ - required this.config, - }); - - VeilidStateConfig.fromJson(dynamic json) : config = json['config']; - - Map get json { - return {'config': config}; - } -} - -////////////////////////////////////// -/// VeilidState - -class VeilidState { - final VeilidStateAttachment attachment; - final VeilidStateNetwork network; - final VeilidStateConfig config; - - VeilidState.fromJson(dynamic json) - : attachment = VeilidStateAttachment.fromJson(json['attachment']), - network = VeilidStateNetwork.fromJson(json['network']), - config = VeilidStateConfig.fromJson(json['config']); - - Map get json { - return { - 'attachment': attachment.json, - 'network': network.json, - 'config': config.json - }; - } -} - -////////////////////////////////////// -/// VeilidAPIException - -abstract class VeilidAPIException implements Exception { - factory VeilidAPIException.fromJson(dynamic json) { - switch (json["kind"]) { - case "NotInitialized": - { - return VeilidAPIExceptionNotInitialized(); - } - case "AlreadyInitialized": - { - return VeilidAPIExceptionAlreadyInitialized(); - } - case "Timeout": - { - return VeilidAPIExceptionTimeout(); - } - case "TryAgain": - { - return VeilidAPIExceptionTryAgain(); - } - case "Shutdown": - { - return VeilidAPIExceptionShutdown(); - } - case "InvalidTarget": - { - return VeilidAPIExceptionInvalidTarget(); - } - case "NoConnection": - { - return VeilidAPIExceptionNoConnection(json["message"]); - } - case "KeyNotFound": - { - return VeilidAPIExceptionKeyNotFound(json["key"]); - } - case "Internal": - { - return VeilidAPIExceptionInternal(json["message"]); - } - case "Unimplemented": - { - return VeilidAPIExceptionUnimplemented(json["unimplemented"]); - } - case "ParseError": - { - return VeilidAPIExceptionParseError(json["message"], json["value"]); - } - case "InvalidArgument": - { - return VeilidAPIExceptionInvalidArgument( - json["context"], json["argument"], json["value"]); - } - case "MissingArgument": - { - return VeilidAPIExceptionMissingArgument( - json["context"], json["argument"]); - } - case "Generic": - { - return VeilidAPIExceptionGeneric(json["message"]); - } - default: - { - throw VeilidAPIExceptionInternal( - "Invalid VeilidAPIException type: ${json['kind']}"); - } - } - } - - String toDisplayError(); -} - -class VeilidAPIExceptionNotInitialized implements VeilidAPIException { - @override - String toString() { - return "VeilidAPIException: NotInitialized"; - } - - @override - String toDisplayError() { - return "Not initialized"; - } -} - -class VeilidAPIExceptionAlreadyInitialized implements VeilidAPIException { - @override - String toString() { - return "VeilidAPIException: AlreadyInitialized"; - } - - @override - String toDisplayError() { - return "Already initialized"; - } -} - -class VeilidAPIExceptionTimeout implements VeilidAPIException { - @override - String toString() { - return "VeilidAPIException: Timeout"; - } - - @override - String toDisplayError() { - return "Timeout"; - } -} - -class VeilidAPIExceptionTryAgain implements VeilidAPIException { - @override - String toString() { - return "VeilidAPIException: TryAgain"; - } - - @override - String toDisplayError() { - return "Try again"; - } -} - -class VeilidAPIExceptionShutdown implements VeilidAPIException { - @override - String toString() { - return "VeilidAPIException: Shutdown"; - } - - @override - String toDisplayError() { - return "Currently shut down"; - } -} - -class VeilidAPIExceptionInvalidTarget implements VeilidAPIException { - @override - String toString() { - return "VeilidAPIException: InvalidTarget"; - } - - @override - String toDisplayError() { - return "Invalid target"; - } -} - -class VeilidAPIExceptionNoConnection implements VeilidAPIException { - final String message; - - @override - String toString() { - return "VeilidAPIException: NoConnection (message: $message)"; - } - - @override - String toDisplayError() { - return "No connection: $message"; - } - - // - VeilidAPIExceptionNoConnection(this.message); -} - -class VeilidAPIExceptionKeyNotFound implements VeilidAPIException { - final String key; - - @override - String toString() { - return "VeilidAPIException: KeyNotFound (key: $key)"; - } - - @override - String toDisplayError() { - return "Key not found: $key"; - } - - // - VeilidAPIExceptionKeyNotFound(this.key); -} - -class VeilidAPIExceptionInternal implements VeilidAPIException { - final String message; - - @override - String toString() { - return "VeilidAPIException: Internal ($message)"; - } - - @override - String toDisplayError() { - return "Internal error: $message"; - } - - // - VeilidAPIExceptionInternal(this.message); -} - -class VeilidAPIExceptionUnimplemented implements VeilidAPIException { - final String message; - - @override - String toString() { - return "VeilidAPIException: Unimplemented ($message)"; - } - - @override - String toDisplayError() { - return "Unimplemented: $message"; - } - - // - VeilidAPIExceptionUnimplemented(this.message); -} - -class VeilidAPIExceptionParseError implements VeilidAPIException { - final String message; - final String value; - - @override - String toString() { - return "VeilidAPIException: ParseError ($message)\n value: $value"; - } - - @override - String toDisplayError() { - return "Parse error: $message"; - } - - // - VeilidAPIExceptionParseError(this.message, this.value); -} - -class VeilidAPIExceptionInvalidArgument implements VeilidAPIException { - final String context; - final String argument; - final String value; - - @override - String toString() { - return "VeilidAPIException: InvalidArgument ($context:$argument)\n value: $value"; - } - - @override - String toDisplayError() { - return "Invalid argument for $context: $argument"; - } - - // - VeilidAPIExceptionInvalidArgument(this.context, this.argument, this.value); -} - -class VeilidAPIExceptionMissingArgument implements VeilidAPIException { - final String context; - final String argument; - - @override - String toString() { - return "VeilidAPIException: MissingArgument ($context:$argument)"; - } - - @override - String toDisplayError() { - return "Missing argument for $context: $argument"; - } - - // - VeilidAPIExceptionMissingArgument(this.context, this.argument); -} - -class VeilidAPIExceptionGeneric implements VeilidAPIException { - final String message; - - @override - String toString() { - return "VeilidAPIException: Generic (message: $message)"; - } - - @override - String toDisplayError() { - return message; - } - - // - VeilidAPIExceptionGeneric(this.message); -} - ////////////////////////////////////// /// VeilidVersion @@ -2050,189 +58,56 @@ class VeilidVersion { } ////////////////////////////////////// -/// Stability +/// Timestamp +class Timestamp { + final BigInt value; + Timestamp({required this.value}); -enum Stability { - lowLatency, - reliable, -} + @override + String toString() { + return value.toString(); + } + + Timestamp.fromString(String s) : value = BigInt.parse(s); + + Timestamp.fromJson(dynamic json) : this.fromString(json as String); -extension StabilityExt on Stability { String get json { - return name.toPascalCase(); + return toString(); + } + + TimestampDuration diff(Timestamp other) { + return TimestampDuration(value: value - other.value); + } + + Timestamp offset(TimestampDuration dur) { + return Timestamp(value: value + dur.value); } } -Stability stabilityFromJson(String j) { - return Stability.values.byName(j.toCamelCase()); -} +class TimestampDuration { + final BigInt value; + TimestampDuration({required this.value}); -////////////////////////////////////// -/// Sequencing + @override + String toString() { + return value.toString(); + } -enum Sequencing { - noPreference, - preferOrdered, - ensureOrdered, -} + TimestampDuration.fromString(String s) : value = BigInt.parse(s); + + TimestampDuration.fromJson(dynamic json) : this.fromString(json as String); -extension SequencingExt on Sequencing { String get json { - return name.toPascalCase(); - } -} - -Sequencing sequencingFromJson(String j) { - return Sequencing.values.byName(j.toCamelCase()); -} - -////////////////////////////////////// -/// RouteBlob -class RouteBlob { - final String routeId; - final Uint8List blob; - - RouteBlob(this.routeId, this.blob); - - RouteBlob.fromJson(dynamic json) - : routeId = json['route_id'], - blob = base64UrlNoPadDecode(json['blob']); - - Map get json { - return {'route_id': routeId, 'blob': base64UrlNoPadEncode(blob)}; - } -} - -////////////////////////////////////// -/// VeilidRoutingContext - -abstract class VeilidRoutingContext { - VeilidRoutingContext withPrivacy(); - VeilidRoutingContext withCustomPrivacy(Stability stability); - VeilidRoutingContext withSequencing(Sequencing sequencing); - Future appCall(String target, Uint8List request); - Future appMessage(String target, Uint8List message); - - Future createDHTRecord( - CryptoKind kind, DHTSchema schema); -xxx continue here - // pub async fn open_dht_record( - // &self, - // key: TypedKey, - // writer: Option, - // ) -> Result { - // let storage_manager = self.api.storage_manager()?; - // storage_manager - // .open_record(key, writer, self.unlocked_inner.safety_selection) - // .await - // } - - // pub async fn close_dht_record(&self, key: TypedKey) -> Result<(), VeilidAPIError> { - // let storage_manager = self.api.storage_manager()?; - // storage_manager.close_record(key).await - // } - - // pub async fn delete_dht_record(&self, key: TypedKey) -> Result<(), VeilidAPIError> { - // let storage_manager = self.api.storage_manager()?; - // storage_manager.delete_record(key).await - // } - - // pub async fn get_dht_value( - // &self, - // key: TypedKey, - // subkey: ValueSubkey, - // force_refresh: bool, - // ) -> Result, VeilidAPIError> { - // let storage_manager = self.api.storage_manager()?; - // storage_manager.get_value(key, subkey, force_refresh).await - // } - - // pub async fn set_dht_value( - // &self, - // key: TypedKey, - // subkey: ValueSubkey, - // data: Vec, - // ) -> Result, VeilidAPIError> { - // let storage_manager = self.api.storage_manager()?; - // storage_manager.set_value(key, subkey, data).await - // } - - // pub async fn watch_dht_values( - // &self, - // key: TypedKey, - // subkeys: &[ValueSubkeyRange], - // expiration: Timestamp, - // count: u32, - // ) -> Result { - // let storage_manager = self.api.storage_manager()?; - // storage_manager - // .watch_values(key, subkeys, expiration, count) - // .await - // } - - // pub async fn cancel_dht_watch( - // &self, - // key: TypedKey, - // subkeys: &[ValueSubkeyRange], - // ) -> Result { - // let storage_manager = self.api.storage_manager()?; - // storage_manager.cancel_watch_values(key, subkeys).await - // } -} - -///////////////////////////////////// -/// VeilidTableDB -abstract class VeilidTableDBTransaction { - Future commit(); - Future rollback(); - Future store(int col, Uint8List key, Uint8List value); - Future delete(int col, Uint8List key); - - Future storeJson(int col, Uint8List key, Object? object, - {Object? Function(Object? nonEncodable)? toEncodable}) async { - return store(col, key, - utf8.encoder.convert(jsonEncode(object, toEncodable: toEncodable))); + return toString(); } - Future storeStringJson(int col, String key, Object? object, - {Object? Function(Object? nonEncodable)? toEncodable}) { - return storeJson(col, utf8.encoder.convert(key), object, - toEncodable: toEncodable); - } -} - -abstract class VeilidTableDB { - int getColumnCount(); - List getKeys(int col); - VeilidTableDBTransaction transact(); - Future store(int col, Uint8List key, Uint8List value); - Future load(int col, Uint8List key); - Future delete(int col, Uint8List key); - - Future storeJson(int col, Uint8List key, Object? object, - {Object? Function(Object? nonEncodable)? toEncodable}) { - return store(col, key, - utf8.encoder.convert(jsonEncode(object, toEncodable: toEncodable))); + int toMillis() { + return (value ~/ BigInt.from(1000)).toInt(); } - Future storeStringJson(int col, String key, Object? object, - {Object? Function(Object? nonEncodable)? toEncodable}) { - return storeJson(col, utf8.encoder.convert(key), object, - toEncodable: toEncodable); - } - - Future loadJson(int col, Uint8List key, - {Object? Function(Object? key, Object? value)? reviver}) async { - var s = await load(col, key); - if (s == null) { - return null; - } - return jsonDecode(utf8.decode(s, allowMalformed: false), reviver: reviver); - } - - Future loadStringJson(int col, String key, - {Object? Function(Object? key, Object? value)? reviver}) { - return loadJson(col, utf8.encoder.convert(key), reviver: reviver); + BigInt toMicros(Timestamp other) { + return value; } } @@ -2250,6 +125,16 @@ abstract class Veilid { Future detach(); Future shutdownVeilidCore(); + // Crypto + List validCryptoKinds(); + VeilidCryptoSystem getCryptoSystem(CryptoKind kind); + VeilidCryptoSystem bestCryptoSystem(); + List verifySignatures( + List nodeIds, Uint8List data, List signatures); + List generateSignatures( + Uint8List data, List keyPairs); + TypedKeyPair generateKeyPair(CryptoKind kind); + // Routing context Future routingContext(); @@ -2268,6 +153,7 @@ abstract class Veilid { Future deleteTableDB(String name); // Misc + Timestamp now(); String veilidVersionString(); VeilidVersion veilidVersion(); Future debug(String command); diff --git a/veilid-flutter/lib/veilid_api_exception.dart b/veilid-flutter/lib/veilid_api_exception.dart new file mode 100644 index 00000000..40a9db66 --- /dev/null +++ b/veilid-flutter/lib/veilid_api_exception.dart @@ -0,0 +1,286 @@ +////////////////////////////////////// +/// VeilidAPIException + +abstract class VeilidAPIException implements Exception { + factory VeilidAPIException.fromJson(dynamic json) { + switch (json["kind"]) { + case "NotInitialized": + { + return VeilidAPIExceptionNotInitialized(); + } + case "AlreadyInitialized": + { + return VeilidAPIExceptionAlreadyInitialized(); + } + case "Timeout": + { + return VeilidAPIExceptionTimeout(); + } + case "TryAgain": + { + return VeilidAPIExceptionTryAgain(); + } + case "Shutdown": + { + return VeilidAPIExceptionShutdown(); + } + case "InvalidTarget": + { + return VeilidAPIExceptionInvalidTarget(); + } + case "NoConnection": + { + return VeilidAPIExceptionNoConnection(json["message"]); + } + case "KeyNotFound": + { + return VeilidAPIExceptionKeyNotFound(json["key"]); + } + case "Internal": + { + return VeilidAPIExceptionInternal(json["message"]); + } + case "Unimplemented": + { + return VeilidAPIExceptionUnimplemented(json["unimplemented"]); + } + case "ParseError": + { + return VeilidAPIExceptionParseError(json["message"], json["value"]); + } + case "InvalidArgument": + { + return VeilidAPIExceptionInvalidArgument( + json["context"], json["argument"], json["value"]); + } + case "MissingArgument": + { + return VeilidAPIExceptionMissingArgument( + json["context"], json["argument"]); + } + case "Generic": + { + return VeilidAPIExceptionGeneric(json["message"]); + } + default: + { + throw VeilidAPIExceptionInternal( + "Invalid VeilidAPIException type: ${json['kind']}"); + } + } + } + + String toDisplayError(); +} + +class VeilidAPIExceptionNotInitialized implements VeilidAPIException { + @override + String toString() { + return "VeilidAPIException: NotInitialized"; + } + + @override + String toDisplayError() { + return "Not initialized"; + } +} + +class VeilidAPIExceptionAlreadyInitialized implements VeilidAPIException { + @override + String toString() { + return "VeilidAPIException: AlreadyInitialized"; + } + + @override + String toDisplayError() { + return "Already initialized"; + } +} + +class VeilidAPIExceptionTimeout implements VeilidAPIException { + @override + String toString() { + return "VeilidAPIException: Timeout"; + } + + @override + String toDisplayError() { + return "Timeout"; + } +} + +class VeilidAPIExceptionTryAgain implements VeilidAPIException { + @override + String toString() { + return "VeilidAPIException: TryAgain"; + } + + @override + String toDisplayError() { + return "Try again"; + } +} + +class VeilidAPIExceptionShutdown implements VeilidAPIException { + @override + String toString() { + return "VeilidAPIException: Shutdown"; + } + + @override + String toDisplayError() { + return "Currently shut down"; + } +} + +class VeilidAPIExceptionInvalidTarget implements VeilidAPIException { + @override + String toString() { + return "VeilidAPIException: InvalidTarget"; + } + + @override + String toDisplayError() { + return "Invalid target"; + } +} + +class VeilidAPIExceptionNoConnection implements VeilidAPIException { + final String message; + + @override + String toString() { + return "VeilidAPIException: NoConnection (message: $message)"; + } + + @override + String toDisplayError() { + return "No connection: $message"; + } + + // + VeilidAPIExceptionNoConnection(this.message); +} + +class VeilidAPIExceptionKeyNotFound implements VeilidAPIException { + final String key; + + @override + String toString() { + return "VeilidAPIException: KeyNotFound (key: $key)"; + } + + @override + String toDisplayError() { + return "Key not found: $key"; + } + + // + VeilidAPIExceptionKeyNotFound(this.key); +} + +class VeilidAPIExceptionInternal implements VeilidAPIException { + final String message; + + @override + String toString() { + return "VeilidAPIException: Internal ($message)"; + } + + @override + String toDisplayError() { + return "Internal error: $message"; + } + + // + VeilidAPIExceptionInternal(this.message); +} + +class VeilidAPIExceptionUnimplemented implements VeilidAPIException { + final String message; + + @override + String toString() { + return "VeilidAPIException: Unimplemented ($message)"; + } + + @override + String toDisplayError() { + return "Unimplemented: $message"; + } + + // + VeilidAPIExceptionUnimplemented(this.message); +} + +class VeilidAPIExceptionParseError implements VeilidAPIException { + final String message; + final String value; + + @override + String toString() { + return "VeilidAPIException: ParseError ($message)\n value: $value"; + } + + @override + String toDisplayError() { + return "Parse error: $message"; + } + + // + VeilidAPIExceptionParseError(this.message, this.value); +} + +class VeilidAPIExceptionInvalidArgument implements VeilidAPIException { + final String context; + final String argument; + final String value; + + @override + String toString() { + return "VeilidAPIException: InvalidArgument ($context:$argument)\n value: $value"; + } + + @override + String toDisplayError() { + return "Invalid argument for $context: $argument"; + } + + // + VeilidAPIExceptionInvalidArgument(this.context, this.argument, this.value); +} + +class VeilidAPIExceptionMissingArgument implements VeilidAPIException { + final String context; + final String argument; + + @override + String toString() { + return "VeilidAPIException: MissingArgument ($context:$argument)"; + } + + @override + String toDisplayError() { + return "Missing argument for $context: $argument"; + } + + // + VeilidAPIExceptionMissingArgument(this.context, this.argument); +} + +class VeilidAPIExceptionGeneric implements VeilidAPIException { + final String message; + + @override + String toString() { + return "VeilidAPIException: Generic (message: $message)"; + } + + @override + String toDisplayError() { + return message; + } + + // + VeilidAPIExceptionGeneric(this.message); +} diff --git a/veilid-flutter/lib/veilid_config.dart b/veilid-flutter/lib/veilid_config.dart new file mode 100644 index 00000000..ce9e0380 --- /dev/null +++ b/veilid-flutter/lib/veilid_config.dart @@ -0,0 +1,744 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:convert'; + +import 'package:change_case/change_case.dart'; + +import 'veilid_encoding.dart'; +import 'veilid.dart'; + +////////////////////////////////////// +/// VeilidConfigLogLevel + +enum VeilidConfigLogLevel { + off, + error, + warn, + info, + debug, + trace, +} + +extension VeilidConfigLogLevelExt on VeilidConfigLogLevel { + String get json { + return name.toPascalCase(); + } +} + +VeilidConfigLogLevel veilidConfigLogLevelFromJson(String j) { + return VeilidConfigLogLevel.values.byName(j.toCamelCase()); +} + +////////////////////////////////////// +/// VeilidConfig + +class VeilidConfigHTTPS { + bool enabled; + String listenAddress; + String path; + String? url; + + VeilidConfigHTTPS({ + required this.enabled, + required this.listenAddress, + required this.path, + this.url, + }); + + Map get json { + return { + 'enabled': enabled, + 'listen_address': listenAddress, + 'path': path, + 'url': url + }; + } + + VeilidConfigHTTPS.fromJson(dynamic json) + : enabled = json['enabled'], + listenAddress = json['listen_address'], + path = json['path'], + url = json['url']; +} + +//////////// + +class VeilidConfigHTTP { + bool enabled; + String listenAddress; + String path; + String? url; + + VeilidConfigHTTP({ + required this.enabled, + required this.listenAddress, + required this.path, + this.url, + }); + + Map get json { + return { + 'enabled': enabled, + 'listen_address': listenAddress, + 'path': path, + 'url': url + }; + } + + VeilidConfigHTTP.fromJson(dynamic json) + : enabled = json['enabled'], + listenAddress = json['listen_address'], + path = json['path'], + url = json['url']; +} + +//////////// + +class VeilidConfigApplication { + VeilidConfigHTTPS https; + VeilidConfigHTTP http; + + VeilidConfigApplication({ + required this.https, + required this.http, + }); + + Map get json { + return { + 'https': https.json, + 'http': http.json, + }; + } + + VeilidConfigApplication.fromJson(dynamic json) + : https = VeilidConfigHTTPS.fromJson(json['https']), + http = VeilidConfigHTTP.fromJson(json['http']); +} + +//////////// + +class VeilidConfigUDP { + bool enabled; + int socketPoolSize; + String listenAddress; + String? publicAddress; + + VeilidConfigUDP( + {required this.enabled, + required this.socketPoolSize, + required this.listenAddress, + this.publicAddress}); + + Map get json { + return { + 'enabled': enabled, + 'socket_pool_size': socketPoolSize, + 'listen_address': listenAddress, + 'public_address': publicAddress, + }; + } + + VeilidConfigUDP.fromJson(dynamic json) + : enabled = json['enabled'], + socketPoolSize = json['socket_pool_size'], + listenAddress = json['listen_address'], + publicAddress = json['publicAddress']; +} + +//////////// + +class VeilidConfigTCP { + bool connect; + bool listen; + int maxConnections; + String listenAddress; + String? publicAddress; + + VeilidConfigTCP( + {required this.connect, + required this.listen, + required this.maxConnections, + required this.listenAddress, + this.publicAddress}); + + Map get json { + return { + 'connect': connect, + 'listen': listen, + 'max_connections': maxConnections, + 'listen_address': listenAddress, + 'public_address': publicAddress, + }; + } + + VeilidConfigTCP.fromJson(dynamic json) + : connect = json['connect'], + listen = json['listen'], + maxConnections = json['max_connections'], + listenAddress = json['listen_address'], + publicAddress = json['publicAddress']; +} + +//////////// + +class VeilidConfigWS { + bool connect; + bool listen; + int maxConnections; + String listenAddress; + String path; + String? url; + + VeilidConfigWS( + {required this.connect, + required this.listen, + required this.maxConnections, + required this.listenAddress, + required this.path, + this.url}); + + Map get json { + return { + 'connect': connect, + 'listen': listen, + 'max_connections': maxConnections, + 'listen_address': listenAddress, + 'path': path, + 'url': url, + }; + } + + VeilidConfigWS.fromJson(dynamic json) + : connect = json['connect'], + listen = json['listen'], + maxConnections = json['max_connections'], + listenAddress = json['listen_address'], + path = json['path'], + url = json['url']; +} + +//////////// + +class VeilidConfigWSS { + bool connect; + bool listen; + int maxConnections; + String listenAddress; + String path; + String? url; + + VeilidConfigWSS( + {required this.connect, + required this.listen, + required this.maxConnections, + required this.listenAddress, + required this.path, + this.url}); + + Map get json { + return { + 'connect': connect, + 'listen': listen, + 'max_connections': maxConnections, + 'listen_address': listenAddress, + 'path': path, + 'url': url, + }; + } + + VeilidConfigWSS.fromJson(dynamic json) + : connect = json['connect'], + listen = json['listen'], + maxConnections = json['max_connections'], + listenAddress = json['listen_address'], + path = json['path'], + url = json['url']; +} + +//////////// + +class VeilidConfigProtocol { + VeilidConfigUDP udp; + VeilidConfigTCP tcp; + VeilidConfigWS ws; + VeilidConfigWSS wss; + + VeilidConfigProtocol({ + required this.udp, + required this.tcp, + required this.ws, + required this.wss, + }); + + Map get json { + return { + 'udp': udp.json, + 'tcp': tcp.json, + 'ws': ws.json, + 'wss': wss.json, + }; + } + + VeilidConfigProtocol.fromJson(dynamic json) + : udp = VeilidConfigUDP.fromJson(json['udp']), + tcp = VeilidConfigTCP.fromJson(json['tcp']), + ws = VeilidConfigWS.fromJson(json['ws']), + wss = VeilidConfigWSS.fromJson(json['wss']); +} + +//////////// + +class VeilidConfigTLS { + String certificatePath; + String privateKeyPath; + int connectionInitialTimeoutMs; + + VeilidConfigTLS({ + required this.certificatePath, + required this.privateKeyPath, + required this.connectionInitialTimeoutMs, + }); + + Map get json { + return { + 'certificate_path': certificatePath, + 'private_key_path': privateKeyPath, + 'connection_initial_timeout_ms': connectionInitialTimeoutMs, + }; + } + + VeilidConfigTLS.fromJson(dynamic json) + : certificatePath = json['certificate_path'], + privateKeyPath = json['private_key_path'], + connectionInitialTimeoutMs = json['connection_initial_timeout_ms']; +} + +//////////// + +class VeilidConfigDHT { + int resolveNodeTimeoutMs; + int resolveNodeCount; + int resolveNodeFanout; + int maxFindNodeCount; + int getValueTimeoutMs; + int getValueCount; + int getValueFanout; + int setValueTimeoutMs; + int setValueCount; + int setValueFanout; + int minPeerCount; + int minPeerRefreshTimeMs; + int validateDialInfoReceiptTimeMs; + int localSubkeyCacheSize; + int localMaxSubkeyCacheMemoryMb; + int remoteSubkeyCacheSize; + int remoteMaxRecords; + int remoteMaxSubkeyCacheMemoryMb; + int remoteMaxStorageSpaceMb; + + VeilidConfigDHT( + {required this.resolveNodeTimeoutMs, + required this.resolveNodeCount, + required this.resolveNodeFanout, + required this.maxFindNodeCount, + required this.getValueTimeoutMs, + required this.getValueCount, + required this.getValueFanout, + required this.setValueTimeoutMs, + required this.setValueCount, + required this.setValueFanout, + required this.minPeerCount, + required this.minPeerRefreshTimeMs, + required this.validateDialInfoReceiptTimeMs, + required this.localSubkeyCacheSize, + required this.localMaxSubkeyCacheMemoryMb, + required this.remoteSubkeyCacheSize, + required this.remoteMaxRecords, + required this.remoteMaxSubkeyCacheMemoryMb, + required this.remoteMaxStorageSpaceMb}); + + Map get json { + return { + 'max_find_node_count': maxFindNodeCount, + 'resolve_node_timeout_ms': resolveNodeTimeoutMs, + 'resolve_node_count': resolveNodeCount, + 'resolve_node_fanout': resolveNodeFanout, + 'get_value_timeout_ms': getValueTimeoutMs, + 'get_value_count': getValueCount, + 'get_value_fanout': getValueFanout, + 'set_value_timeout_ms': setValueTimeoutMs, + 'set_value_count': setValueCount, + 'set_value_fanout': setValueFanout, + 'min_peer_count': minPeerCount, + 'min_peer_refresh_time_ms': minPeerRefreshTimeMs, + 'validate_dial_info_receipt_time_ms': validateDialInfoReceiptTimeMs, + 'local_subkey_cache_size: 128': localSubkeyCacheSize, + 'local_max_subkey_cache_memory_mb': localMaxSubkeyCacheMemoryMb, + 'remote_subkey_cache_size': remoteSubkeyCacheSize, + 'remote_max_records': remoteMaxRecords, + 'remote_max_subkey_cache_memory_mb': remoteMaxSubkeyCacheMemoryMb, + 'remote_max_storage_space_mb': remoteMaxStorageSpaceMb, + }; + } + + VeilidConfigDHT.fromJson(dynamic json) + : resolveNodeTimeoutMs = json['resolve_node_timeout_ms'], + resolveNodeCount = json['resolve_node_count'], + resolveNodeFanout = json['resolve_node_fanout'], + maxFindNodeCount = json['max_find_node_count'], + getValueTimeoutMs = json['get_value_timeout_ms'], + getValueCount = json['get_value_count'], + getValueFanout = json['get_value_fanout'], + setValueTimeoutMs = json['set_value_timeout_ms'], + setValueCount = json['set_value_count'], + setValueFanout = json['set_value_fanout'], + minPeerCount = json['min_peer_count'], + minPeerRefreshTimeMs = json['min_peer_refresh_time_ms'], + validateDialInfoReceiptTimeMs = + json['validate_dial_info_receipt_time_ms'], + localSubkeyCacheSize = json['local_subkey_cache_size'], + localMaxSubkeyCacheMemoryMb = json['local_max_subkey_cache_memory_mb'], + remoteSubkeyCacheSize = json['remote_subkey_cache_size'], + remoteMaxRecords = json['remote_max_records'], + remoteMaxSubkeyCacheMemoryMb = + json['remote_max_subkey_cache_memory_mb'], + remoteMaxStorageSpaceMb = json['remote_max_storage_space_mb']; +} + +//////////// + +class VeilidConfigRPC { + int concurrency; + int queueSize; + int? maxTimestampBehindMs; + int? maxTimestampAheadMs; + int timeoutMs; + int maxRouteHopCount; + int defaultRouteHopCount; + + VeilidConfigRPC( + {required this.concurrency, + required this.queueSize, + this.maxTimestampBehindMs, + this.maxTimestampAheadMs, + required this.timeoutMs, + required this.maxRouteHopCount, + required this.defaultRouteHopCount}); + + Map get json { + return { + 'concurrency': concurrency, + 'queue_size': queueSize, + 'max_timestamp_behind_ms': maxTimestampBehindMs, + 'max_timestamp_ahead_ms': maxTimestampAheadMs, + 'timeout_ms': timeoutMs, + 'max_route_hop_count': maxRouteHopCount, + 'default_route_hop_count': defaultRouteHopCount, + }; + } + + VeilidConfigRPC.fromJson(dynamic json) + : concurrency = json['concurrency'], + queueSize = json['queue_size'], + maxTimestampBehindMs = json['max_timestamp_behind_ms'], + maxTimestampAheadMs = json['max_timestamp_ahead_ms'], + timeoutMs = json['timeout_ms'], + maxRouteHopCount = json['max_route_hop_count'], + defaultRouteHopCount = json['default_route_hop_count']; +} + +//////////// + +class VeilidConfigRoutingTable { + List nodeId; + List nodeIdSecret; + List bootstrap; + int limitOverAttached; + int limitFullyAttached; + int limitAttachedStrong; + int limitAttachedGood; + int limitAttachedWeak; + + VeilidConfigRoutingTable({ + required this.nodeId, + required this.nodeIdSecret, + required this.bootstrap, + required this.limitOverAttached, + required this.limitFullyAttached, + required this.limitAttachedStrong, + required this.limitAttachedGood, + required this.limitAttachedWeak, + }); + + Map get json { + return { + 'node_id': nodeId.map((p) => p.json).toList(), + 'node_id_secret': nodeIdSecret.map((p) => p.json).toList(), + 'bootstrap': bootstrap.map((p) => p).toList(), + 'limit_over_attached': limitOverAttached, + 'limit_fully_attached': limitFullyAttached, + 'limit_attached_strong': limitAttachedStrong, + 'limit_attached_good': limitAttachedGood, + 'limit_attached_weak': limitAttachedWeak, + }; + } + + VeilidConfigRoutingTable.fromJson(dynamic json) + : nodeId = List.from(json['node_id'].map((j) => Key.fromJson(j))), + nodeIdSecret = + List.from(json['node_id_secret'].map((j) => Key.fromJson(j))), + bootstrap = List.from(json['bootstrap'].map((j) => j)), + limitOverAttached = json['limit_over_attached'], + limitFullyAttached = json['limit_fully_attached'], + limitAttachedStrong = json['limit_attached_strong'], + limitAttachedGood = json['limit_attached_good'], + limitAttachedWeak = json['limit_attached_weak']; +} + +//////////// + +class VeilidConfigNetwork { + int connectionInitialTimeoutMs; + int connectionInactivityTimeoutMs; + int maxConnectionsPerIp4; + int maxConnectionsPerIp6Prefix; + int maxConnectionsPerIp6PrefixSize; + int maxConnectionFrequencyPerMin; + int clientWhitelistTimeoutMs; + int reverseConnectionReceiptTimeMs; + int holePunchReceiptTimeMs; + VeilidConfigRoutingTable routingTable; + VeilidConfigRPC rpc; + VeilidConfigDHT dht; + bool upnp; + bool detectAddressChanges; + int restrictedNatRetries; + VeilidConfigTLS tls; + VeilidConfigApplication application; + VeilidConfigProtocol protocol; + + VeilidConfigNetwork({ + required this.connectionInitialTimeoutMs, + required this.connectionInactivityTimeoutMs, + required this.maxConnectionsPerIp4, + required this.maxConnectionsPerIp6Prefix, + required this.maxConnectionsPerIp6PrefixSize, + required this.maxConnectionFrequencyPerMin, + required this.clientWhitelistTimeoutMs, + required this.reverseConnectionReceiptTimeMs, + required this.holePunchReceiptTimeMs, + required this.routingTable, + required this.rpc, + required this.dht, + required this.upnp, + required this.detectAddressChanges, + required this.restrictedNatRetries, + required this.tls, + required this.application, + required this.protocol, + }); + + Map get json { + return { + 'connection_initial_timeout_ms': connectionInitialTimeoutMs, + 'connection_inactivity_timeout_ms': connectionInactivityTimeoutMs, + 'max_connections_per_ip4': maxConnectionsPerIp4, + 'max_connections_per_ip6_prefix': maxConnectionsPerIp6Prefix, + 'max_connections_per_ip6_prefix_size': maxConnectionsPerIp6PrefixSize, + 'max_connection_frequency_per_min': maxConnectionFrequencyPerMin, + 'client_whitelist_timeout_ms': clientWhitelistTimeoutMs, + 'reverse_connection_receipt_time_ms': reverseConnectionReceiptTimeMs, + 'hole_punch_receipt_time_ms': holePunchReceiptTimeMs, + 'routing_table': routingTable.json, + 'rpc': rpc.json, + 'dht': dht.json, + 'upnp': upnp, + 'detect_address_changes': detectAddressChanges, + 'restricted_nat_retries': restrictedNatRetries, + 'tls': tls.json, + 'application': application.json, + 'protocol': protocol.json, + }; + } + + VeilidConfigNetwork.fromJson(dynamic json) + : connectionInitialTimeoutMs = json['connection_initial_timeout_ms'], + connectionInactivityTimeoutMs = + json['connection_inactivity_timeout_ms'], + maxConnectionsPerIp4 = json['max_connections_per_ip4'], + maxConnectionsPerIp6Prefix = json['max_connections_per_ip6_prefix'], + maxConnectionsPerIp6PrefixSize = + json['max_connections_per_ip6_prefix_size'], + maxConnectionFrequencyPerMin = json['max_connection_frequency_per_min'], + clientWhitelistTimeoutMs = json['client_whitelist_timeout_ms'], + reverseConnectionReceiptTimeMs = + json['reverse_connection_receipt_time_ms'], + holePunchReceiptTimeMs = json['hole_punch_receipt_time_ms'], + routingTable = VeilidConfigRoutingTable.fromJson(json['routing_table']), + rpc = VeilidConfigRPC.fromJson(json['rpc']), + dht = VeilidConfigDHT.fromJson(json['dht']), + upnp = json['upnp'], + detectAddressChanges = json['detect_address_changes'], + restrictedNatRetries = json['restricted_nat_retries'], + tls = VeilidConfigTLS.fromJson(json['tls']), + application = VeilidConfigApplication.fromJson(json['application']), + protocol = VeilidConfigProtocol.fromJson(json['protocol']); +} + +//////////// + +class VeilidConfigTableStore { + String directory; + bool delete; + + VeilidConfigTableStore({ + required this.directory, + required this.delete, + }); + + Map get json { + return {'directory': directory, 'delete': delete}; + } + + VeilidConfigTableStore.fromJson(dynamic json) + : directory = json['directory'], + delete = json['delete']; +} + +//////////// + +class VeilidConfigBlockStore { + String directory; + bool delete; + + VeilidConfigBlockStore({ + required this.directory, + required this.delete, + }); + + Map get json { + return {'directory': directory, 'delete': delete}; + } + + VeilidConfigBlockStore.fromJson(dynamic json) + : directory = json['directory'], + delete = json['delete']; +} + +//////////// + +class VeilidConfigProtectedStore { + bool allowInsecureFallback; + bool alwaysUseInsecureStorage; + String insecureFallbackDirectory; + bool delete; + + VeilidConfigProtectedStore({ + required this.allowInsecureFallback, + required this.alwaysUseInsecureStorage, + required this.insecureFallbackDirectory, + required this.delete, + }); + + Map get json { + return { + 'allow_insecure_fallback': allowInsecureFallback, + 'always_use_insecure_storage': alwaysUseInsecureStorage, + 'insecure_fallback_directory': insecureFallbackDirectory, + 'delete': delete, + }; + } + + VeilidConfigProtectedStore.fromJson(dynamic json) + : allowInsecureFallback = json['allow_insecure_fallback'], + alwaysUseInsecureStorage = json['always_use_insecure_storage'], + insecureFallbackDirectory = json['insecure_fallback_directory'], + delete = json['delete']; +} + +//////////// + +class VeilidConfigCapabilities { + bool protocolUDP; + bool protocolConnectTCP; + bool protocolAcceptTCP; + bool protocolConnectWS; + bool protocolAcceptWS; + bool protocolConnectWSS; + bool protocolAcceptWSS; + + VeilidConfigCapabilities({ + required this.protocolUDP, + required this.protocolConnectTCP, + required this.protocolAcceptTCP, + required this.protocolConnectWS, + required this.protocolAcceptWS, + required this.protocolConnectWSS, + required this.protocolAcceptWSS, + }); + + Map get json { + return { + 'protocol_udp': protocolUDP, + 'protocol_connect_tcp': protocolConnectTCP, + 'protocol_accept_tcp': protocolAcceptTCP, + 'protocol_connect_ws': protocolConnectWS, + 'protocol_accept_ws': protocolAcceptWS, + 'protocol_connect_wss': protocolConnectWSS, + 'protocol_accept_wss': protocolAcceptWSS, + }; + } + + VeilidConfigCapabilities.fromJson(dynamic json) + : protocolUDP = json['protocol_udp'], + protocolConnectTCP = json['protocol_connect_tcp'], + protocolAcceptTCP = json['protocol_accept_tcp'], + protocolConnectWS = json['protocol_connect_ws'], + protocolAcceptWS = json['protocol_accept_ws'], + protocolConnectWSS = json['protocol_connect_wss'], + protocolAcceptWSS = json['protocol_accept_wss']; +} + +//////////// + +class VeilidConfig { + String programName; + String namespace; + VeilidConfigCapabilities capabilities; + VeilidConfigProtectedStore protectedStore; + VeilidConfigTableStore tableStore; + VeilidConfigBlockStore blockStore; + VeilidConfigNetwork network; + + VeilidConfig({ + required this.programName, + required this.namespace, + required this.capabilities, + required this.protectedStore, + required this.tableStore, + required this.blockStore, + required this.network, + }); + + Map get json { + return { + 'program_name': programName, + 'namespace': namespace, + 'capabilities': capabilities.json, + 'protected_store': protectedStore.json, + 'table_store': tableStore.json, + 'block_store': blockStore.json, + 'network': network.json + }; + } + + VeilidConfig.fromJson(dynamic json) + : programName = json['program_name'], + namespace = json['namespace'], + capabilities = VeilidConfigCapabilities.fromJson(json['capabilities']), + protectedStore = + VeilidConfigProtectedStore.fromJson(json['protected_store']), + tableStore = VeilidConfigTableStore.fromJson(json['table_store']), + blockStore = VeilidConfigBlockStore.fromJson(json['block_store']), + network = VeilidConfigNetwork.fromJson(json['network']); +} diff --git a/veilid-flutter/lib/veilid_crypto.dart b/veilid-flutter/lib/veilid_crypto.dart new file mode 100644 index 00000000..e18adee7 --- /dev/null +++ b/veilid-flutter/lib/veilid_crypto.dart @@ -0,0 +1,155 @@ +import 'dart:async'; +import 'dart:typed_data'; + +import 'package:charcode/charcode.dart'; + +import 'veilid_encoding.dart'; +import 'veilid.dart'; + +////////////////////////////////////// +/// CryptoKind + +typedef CryptoKind = int; +const CryptoKind cryptoKindVLD0 = + $V << 0 | $L << 8 | $D << 16 | $0 << 24; // "VLD0" +const CryptoKind cryptoKindNONE = + $N << 0 | $O << 8 | $N << 16 | $E << 24; // "NONE" + +String cryptoKindToString(CryptoKind kind) { + return "${String.fromCharCode(kind & 0xFF)}${String.fromCharCode((kind >> 8) & 0xFF)}${String.fromCharCode((kind >> 16) & 0xFF)}${String.fromCharCode((kind >> 24) & 0xFF)}"; +} + +CryptoKind cryptoKindFromString(String s) { + if (s.codeUnits.length != 4) { + throw const FormatException("malformed string"); + } + CryptoKind kind = s.codeUnits[0] | + s.codeUnits[1] << 8 | + s.codeUnits[2] << 16 | + s.codeUnits[3] << 24; + return kind; +} + +////////////////////////////////////// +/// Types + +class Typed { + late CryptoKind kind; + late V value; + Typed({required this.kind, required this.value}); + + @override + String toString() { + return "${cryptoKindToString(kind)}:$value"; + } + + Typed.fromString(String s) { + var parts = s.split(":"); + if (parts.length < 2 || parts[0].codeUnits.length != 4) { + throw const FormatException("malformed string"); + } + kind = parts[0].codeUnits[0] | + parts[0].codeUnits[1] << 8 | + parts[0].codeUnits[2] << 16 | + parts[0].codeUnits[3] << 24; + value = EncodedString.fromString(parts.sublist(1).join(":")); + } + + String get json { + return toString(); + } + + Typed.fromJson(dynamic json) : this.fromString(json as String); +} + +class KeyPair { + late Key key; + late Key secret; + KeyPair({required this.key, required this.secret}); + + @override + String toString() { + return "${key.toString()}:${secret.toString()}"; + } + + KeyPair.fromString(String s) { + var parts = s.split(":"); + if (parts.length != 2 || + parts[0].codeUnits.length != 43 || + parts[1].codeUnits.length != 43) { + throw const FormatException("malformed string"); + } + key = Key(parts[0]); + secret = Key(parts[1]); + } + + String get json { + return toString(); + } + + KeyPair.fromJson(dynamic json) : this.fromString(json as String); +} + +class TypedKeyPair { + late CryptoKind kind; + late Key key; + late Key secret; + TypedKeyPair({required this.kind, required this.key, required this.secret}); + + @override + String toString() { + return "${cryptoKindToString(kind)}:${key.toString()}:${secret.toString()}"; + } + + TypedKeyPair.fromString(String s) { + var parts = s.split(":"); + if (parts.length != 3 || + parts[0].codeUnits.length != 4 || + parts[1].codeUnits.length != 43 || + parts[2].codeUnits.length != 43) { + throw VeilidAPIExceptionInvalidArgument("malformed string", "s", s); + } + kind = cryptoKindFromString(parts[0]); + key = Key(parts[1]); + secret = Key(parts[2]); + } + + String get json { + return toString(); + } + + TypedKeyPair.fromJson(dynamic json) : this.fromString(json as String); +} + +typedef Key = FixedEncodedString43; +typedef Signature = FixedEncodedString86; +typedef Nonce = FixedEncodedString32; + +typedef TypedKey = Typed; +typedef TypedSignature = Typed; + +////////////////////////////////////// +/// VeilidCryptoSystem + +abstract class VeilidCryptoSystem { + CryptoKind kind(); + Key cachedDH(Key key, Key secret); + Key computeDH(Key key, Key secret); + Nonce randomNonce(); + Key randomSharedSecret(); + KeyPair generateKeyPair(); + Key generateHash(Uint8List data); + Key generateHashReader(Stream> reader); + bool validateKeyPair(Key key, Key secret); + bool validateHash(Uint8List data, Key hash); + bool validateHashReader(Stream> reader, Key hash); + Key distance(Key key1, Key key2); + Signature sign(Key key, Key secret, Uint8List data); + void verify(Key key, Uint8List data, Signature signature); + BigInt aeadOverhead(); + Uint8List decryptAead( + Uint8List body, Nonce nonce, Key sharedSecret, Uint8List? associatedData); + Uint8List encryptAead( + Uint8List body, Nonce nonce, Key sharedSecret, Uint8List? associatedData); + Uint8List cryptNoAuth(Uint8List body, Nonce nonce, Key sharedSecret); +} diff --git a/veilid-flutter/lib/veilid_encoding.dart b/veilid-flutter/lib/veilid_encoding.dart new file mode 100644 index 00000000..f2746da9 --- /dev/null +++ b/veilid-flutter/lib/veilid_encoding.dart @@ -0,0 +1,116 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +String base64UrlNoPadEncode(List bytes) { + var x = base64Url.encode(bytes); + while (x.endsWith('=')) { + x = x.substring(0, x.length - 1); + } + return x; +} + +Uint8List base64UrlNoPadDecode(String source) { + source = base64.normalize(source); + return base64.decode(source); +} + +abstract class EncodedString { + late String contents; + EncodedString(String s) { + validate(s); + contents = s; + } + EncodedString.encode(List b) { + var s = base64UrlNoPadEncode(b); + validate(s); + contents = s; + } + + int encodedLength(); + int decodedLength(); + void validate(String s) { + var d = base64UrlNoPadDecode(s); + if (d.length != decodedLength()) { + throw Exception("length ${s.length} should be ${encodedLength()}"); + } + } + + Uint8List decode() { + return base64UrlNoPadDecode(contents); + } + + @override + String toString() { + return contents; + } + + static T fromString(String s) { + switch (T) { + case FixedEncodedString32: + return FixedEncodedString32(s) as T; + case FixedEncodedString43: + return FixedEncodedString43(s) as T; + case FixedEncodedString86: + return FixedEncodedString86(s) as T; + default: + throw UnimplementedError(); + } + } +} + +class FixedEncodedString32 extends EncodedString { + FixedEncodedString32(String s) : super(s); + @override + int encodedLength() { + return 32; + } + + @override + int decodedLength() { + return 24; + } + + String get json { + return toString(); + } + + FixedEncodedString32.fromJson(dynamic json) : this(json as String); +} + +class FixedEncodedString43 extends EncodedString { + FixedEncodedString43(String s) : super(s); + @override + int encodedLength() { + return 43; + } + + @override + int decodedLength() { + return 32; + } + + String get json { + return toString(); + } + + FixedEncodedString43.fromJson(dynamic json) : this(json as String); +} + +class FixedEncodedString86 extends EncodedString { + FixedEncodedString86(String s) : super(s); + @override + int encodedLength() { + return 86; + } + + @override + int decodedLength() { + return 64; + } + + String get json { + return toString(); + } + + FixedEncodedString86.fromJson(dynamic json) : this(json as String); +} diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index dd0b13a4..2bb17d96 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -10,6 +10,119 @@ import 'package:ffi/ffi.dart'; import 'veilid.dart'; import 'base64url_no_pad.dart'; +////////////////////////////////////////////////////////// +// FFI Platform-specific config + +class VeilidFFIConfigLoggingTerminal { + bool enabled; + VeilidConfigLogLevel level; + + VeilidFFIConfigLoggingTerminal({ + required this.enabled, + required this.level, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + }; + } + + VeilidFFIConfigLoggingTerminal.fromJson(dynamic json) + : enabled = json['enabled'], + level = veilidConfigLogLevelFromJson(json['level']); +} + +class VeilidFFIConfigLoggingOtlp { + bool enabled; + VeilidConfigLogLevel level; + String grpcEndpoint; + String serviceName; + + VeilidFFIConfigLoggingOtlp({ + required this.enabled, + required this.level, + required this.grpcEndpoint, + required this.serviceName, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + 'grpc_endpoint': grpcEndpoint, + 'service_name': serviceName, + }; + } + + VeilidFFIConfigLoggingOtlp.fromJson(dynamic json) + : enabled = json['enabled'], + level = veilidConfigLogLevelFromJson(json['level']), + grpcEndpoint = json['grpc_endpoint'], + serviceName = json['service_name']; +} + +class VeilidFFIConfigLoggingApi { + bool enabled; + VeilidConfigLogLevel level; + + VeilidFFIConfigLoggingApi({ + required this.enabled, + required this.level, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + }; + } + + VeilidFFIConfigLoggingApi.fromJson(dynamic json) + : enabled = json['enabled'], + level = veilidConfigLogLevelFromJson(json['level']); +} + +class VeilidFFIConfigLogging { + VeilidFFIConfigLoggingTerminal terminal; + VeilidFFIConfigLoggingOtlp otlp; + VeilidFFIConfigLoggingApi api; + + VeilidFFIConfigLogging( + {required this.terminal, required this.otlp, required this.api}); + + Map get json { + return { + 'terminal': terminal.json, + 'otlp': otlp.json, + 'api': api.json, + }; + } + + VeilidFFIConfigLogging.fromJson(dynamic json) + : terminal = VeilidFFIConfigLoggingTerminal.fromJson(json['terminal']), + otlp = VeilidFFIConfigLoggingOtlp.fromJson(json['otlp']), + api = VeilidFFIConfigLoggingApi.fromJson(json['api']); +} + +class VeilidFFIConfig { + VeilidFFIConfigLogging logging; + + VeilidFFIConfig({ + required this.logging, + }); + + Map get json { + return { + 'logging': logging.json, + }; + } + + VeilidFFIConfig.fromJson(Map json) + : logging = VeilidFFIConfigLogging.fromJson(json['logging']); +} + ////////////////////////////////////////////////////////// // Load the veilid_flutter library once @@ -603,7 +716,7 @@ class VeilidTableDBFFI extends VeilidTableDB { final recvPort = ReceivePort("veilid_table_db_delete"); final sendPort = recvPort.sendPort; - _tdb.ffi._tableDbLoad( + _tdb.ffi._tableDbDelete( sendPort.nativePort, _tdb.id, col, @@ -635,6 +748,14 @@ class VeilidFFI implements Veilid { final _RoutingContextWithSequencingDart _routingContextWithSequencing; final _RoutingContextAppCallDart _routingContextAppCall; final _RoutingContextAppMessageDart _routingContextAppMessage; + final _RoutingContextCreateDHTRecordDart _RoutingContextCreateDHTRecord; + final _RoutingContextOpenDHTRecordDart _RoutingContextOpenDHTRecord; + final _RoutingContextCloseDHTRecordDart _RoutingContextCloseDHTRecord; + final _RoutingContextDeleteDHTRecordDart _RoutingContextDeleteDHTRecord; + final _RoutingContextGetDHTValueDart _RoutingContextGetDHTValue; + final _RoutingContextSetDHTValueDart _RoutingContextSetDHTValue; + final _RoutingContextWatchDHTValuesDart _RoutingContextWatchDHTValues; + final _RoutingContextCancelDHTWatchDart _RoutingContextCancelDHTWatch; final _NewPrivateRouteDart _newPrivateRoute; final _NewCustomPrivateRouteDart _newCustomPrivateRoute; @@ -658,6 +779,32 @@ class VeilidFFI implements Veilid { final _TableDbTransactionStoreDart _tableDbTransactionStore; final _TableDbTransactionDeleteDart _tableDbTransactionDelete; + final _ValidCryptoKindsDart _validCryptoKinds; + final _GetCryptoSystemDart _getCryptoSystem; + final _BestCryptoSystemDart _bestCryptoSystem; + final _VerifySignaturesDart _verifySignatures; + final _GenerateSignaturesDart _generateSignatures; + final _GenerateKeyPairDart _generateKeyPair; + + final _CryptoCachedDHDart _cryptoCachedDH; + final _CryptoComputeDHDart _cryptoComputeDH; + final _CryptoRandomNonceDart _cryptoRandomNonce; + final _CryptoRandomSharedSecretDart _cryptoRandomSharedSecret; + final _CryptoGenerateKeyPairDart _cryptoGenerateKeyPair; + final _CryptoGenerateHashDart _cryptoGenerateHash; + final _CryptoGenerateHashReaderDart _cryptoGenerateHashReader; + final _CryptoValidateKeyPairDart _cryptoValidateKeyPair; + final _CryptoValidateHashDart _cryptoValidateHash; + final _CryptoValidateHashReaderDart _cryptoValidateHashReader; + final _CryptoDistanceDart _cryptoDistance; + final _CryptoSignDart _cryptoSign; + final _CryptoVerifyDart _cryptoVerify; + final _CryptoAaedOverheadDart _cryptoAeadOverhead; + final _CryptoDecryptAeadDart _cryptoDecryptAead; + final _CryptoEncryptAeadDart _cryptoEncryptAead; + final _CryptoCryptNoAuthDart _cryptoCryptNoAuth; + + final _NowDart _now; final _DebugDart _debug; final _VeilidVersionStringDart _veilidVersionString; final _VeilidVersionDart _veilidVersion; diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index 963a5fca..ae6d6ac0 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -9,6 +9,95 @@ import 'dart:typed_data'; import 'base64url_no_pad.dart'; +////////////////////////////////////////////////////////// +// WASM Platform-specific config + +class VeilidWASMConfigLoggingPerformance { + bool enabled; + VeilidConfigLogLevel level; + bool logsInTimings; + bool logsInConsole; + + VeilidWASMConfigLoggingPerformance({ + required this.enabled, + required this.level, + required this.logsInTimings, + required this.logsInConsole, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + 'logs_in_timings': logsInTimings, + 'logs_in_console': logsInConsole, + }; + } + + VeilidWASMConfigLoggingPerformance.fromJson(dynamic json) + : enabled = json['enabled'], + level = veilidConfigLogLevelFromJson(json['level']), + logsInTimings = json['logs_in_timings'], + logsInConsole = json['logs_in_console']; +} + +class VeilidWASMConfigLoggingApi { + bool enabled; + VeilidConfigLogLevel level; + + VeilidWASMConfigLoggingApi({ + required this.enabled, + required this.level, + }); + + Map get json { + return { + 'enabled': enabled, + 'level': level.json, + }; + } + + VeilidWASMConfigLoggingApi.fromJson(dynamic json) + : enabled = json['enabled'], + level = veilidConfigLogLevelFromJson(json['level']); +} + +class VeilidWASMConfigLogging { + VeilidWASMConfigLoggingPerformance performance; + VeilidWASMConfigLoggingApi api; + + VeilidWASMConfigLogging({required this.performance, required this.api}); + + Map get json { + return { + 'performance': performance.json, + 'api': api.json, + }; + } + + VeilidWASMConfigLogging.fromJson(dynamic json) + : performance = + VeilidWASMConfigLoggingPerformance.fromJson(json['performance']), + api = VeilidWASMConfigLoggingApi.fromJson(json['api']); +} + +class VeilidWASMConfig { + VeilidWASMConfigLogging logging; + + VeilidWASMConfig({ + required this.logging, + }); + + Map get json { + return { + 'logging': logging.json, + }; + } + + VeilidWASMConfig.fromJson(dynamic json) + : logging = VeilidWASMConfigLogging.fromJson(json['logging']); +} + ////////////////////////////////////////////////////////// Veilid getVeilid() => VeilidJS(); diff --git a/veilid-flutter/lib/veilid_state.dart b/veilid-flutter/lib/veilid_state.dart new file mode 100644 index 00000000..344f3f47 --- /dev/null +++ b/veilid-flutter/lib/veilid_state.dart @@ -0,0 +1,594 @@ +import 'dart:typed_data'; + +import 'package:change_case/change_case.dart'; + +import 'veilid_encoding.dart'; +import 'veilid.dart'; + +////////////////////////////////////// +/// AttachmentState + +enum AttachmentState { + detached, + attaching, + attachedWeak, + attachedGood, + attachedStrong, + fullyAttached, + overAttached, + detaching, +} + +extension AttachmentStateExt on AttachmentState { + String get json { + return name.toPascalCase(); + } +} + +AttachmentState attachmentStateFromJson(String j) { + return AttachmentState.values.byName(j.toCamelCase()); +} + +////////////////////////////////////// +/// VeilidLogLevel + +enum VeilidLogLevel { + error, + warn, + info, + debug, + trace, +} + +extension VeilidLogLevelExt on VeilidLogLevel { + String get json { + return name.toPascalCase(); + } +} + +VeilidLogLevel veilidLogLevelFromJson(String j) { + return VeilidLogLevel.values.byName(j.toCamelCase()); +} + +//////////// + +class LatencyStats { + TimestampDuration fastest; + TimestampDuration average; + TimestampDuration slowest; + + LatencyStats({ + required this.fastest, + required this.average, + required this.slowest, + }); + + Map get json { + return { + 'fastest': fastest.json, + 'average': average.json, + 'slowest': slowest.json, + }; + } + + LatencyStats.fromJson(dynamic json) + : fastest = TimestampDuration.fromJson(json['fastest']), + average = TimestampDuration.fromJson(json['average']), + slowest = TimestampDuration.fromJson(json['slowest']); +} + +//////////// + +class TransferStats { + BigInt total; + BigInt maximum; + BigInt average; + BigInt minimum; + + TransferStats({ + required this.total, + required this.maximum, + required this.average, + required this.minimum, + }); + + Map get json { + return { + 'total': total.toString(), + 'maximum': maximum.toString(), + 'average': average.toString(), + 'minimum': minimum.toString(), + }; + } + + TransferStats.fromJson(dynamic json) + : total = BigInt.parse(json['total']), + maximum = BigInt.parse(json['maximum']), + average = BigInt.parse(json['average']), + minimum = BigInt.parse(json['minimum']); +} + +//////////// + +class TransferStatsDownUp { + TransferStats down; + TransferStats up; + + TransferStatsDownUp({ + required this.down, + required this.up, + }); + + Map get json { + return { + 'down': down.json, + 'up': up.json, + }; + } + + TransferStatsDownUp.fromJson(dynamic json) + : down = TransferStats.fromJson(json['down']), + up = TransferStats.fromJson(json['up']); +} + +//////////// + +class RPCStats { + int messagesSent; + int messagesRcvd; + int questionsInFlight; + Timestamp? lastQuestion; + Timestamp? lastSeenTs; + Timestamp? firstConsecutiveSeenTs; + int recentLostAnswers; + int failedToSend; + + RPCStats({ + required this.messagesSent, + required this.messagesRcvd, + required this.questionsInFlight, + required this.lastQuestion, + required this.lastSeenTs, + required this.firstConsecutiveSeenTs, + required this.recentLostAnswers, + required this.failedToSend, + }); + + Map get json { + return { + 'messages_sent': messagesSent, + 'messages_rcvd': messagesRcvd, + 'questions_in_flight': questionsInFlight, + 'last_question': lastQuestion?.json, + 'last_seen_ts': lastSeenTs?.json, + 'first_consecutive_seen_ts': firstConsecutiveSeenTs?.json, + 'recent_lost_answers': recentLostAnswers, + 'failed_to_send': failedToSend, + }; + } + + RPCStats.fromJson(dynamic json) + : messagesSent = json['messages_sent'], + messagesRcvd = json['messages_rcvd'], + questionsInFlight = json['questions_in_flight'], + lastQuestion = json['last_question'] != null + ? Timestamp.fromJson(json['last_question']) + : null, + lastSeenTs = json['last_seen_ts'] != null + ? Timestamp.fromJson(json['last_seen_ts']) + : null, + firstConsecutiveSeenTs = json['first_consecutive_seen_ts'] != null + ? Timestamp.fromJson(json['first_consecutive_seen_ts']) + : null, + recentLostAnswers = json['recent_lost_answers'], + failedToSend = json['failed_to_send']; +} + +//////////// + +class PeerStats { + Timestamp timeAdded; + RPCStats rpcStats; + LatencyStats? latency; + TransferStatsDownUp transfer; + + PeerStats({ + required this.timeAdded, + required this.rpcStats, + required this.latency, + required this.transfer, + }); + + Map get json { + return { + 'time_added': timeAdded.json, + 'rpc_stats': rpcStats.json, + 'latency': latency?.json, + 'transfer': transfer.json, + }; + } + + PeerStats.fromJson(dynamic json) + : timeAdded = Timestamp.fromJson(json['time_added']), + rpcStats = RPCStats.fromJson(json['rpc_stats']), + latency = json['latency'] != null + ? LatencyStats.fromJson(json['latency']) + : null, + transfer = TransferStatsDownUp.fromJson(json['transfer']); +} + +//////////// + +class PeerTableData { + List nodeIds; + PeerAddress peerAddress; + PeerStats peerStats; + + PeerTableData({ + required this.nodeIds, + required this.peerAddress, + required this.peerStats, + }); + + Map get json { + return { + 'node_ids': nodeIds.map((p) => p.json).toList(), + 'peer_address': peerAddress.json, + 'peer_stats': peerStats.json, + }; + } + + PeerTableData.fromJson(dynamic json) + : nodeIds = List.from( + json['node_ids'].map((j) => TypedKey.fromJson(j))), + peerAddress = PeerAddress.fromJson(json['peer_address']), + peerStats = PeerStats.fromJson(json['peer_stats']); +} + +////////////////////////////////////// +/// AttachmentState + +enum ProtocolType { + udp, + tcp, + ws, + wss, +} + +extension ProtocolTypeExt on ProtocolType { + String get json { + return name.toUpperCase(); + } +} + +ProtocolType protocolTypeFromJson(String j) { + return ProtocolType.values.byName(j.toLowerCase()); +} + +//////////// + +class PeerAddress { + ProtocolType protocolType; + String socketAddress; + + PeerAddress({ + required this.protocolType, + required this.socketAddress, + }); + + Map get json { + return { + 'protocol_type': protocolType.json, + 'socket_address': socketAddress, + }; + } + + PeerAddress.fromJson(dynamic json) + : protocolType = protocolTypeFromJson(json['protocol_type']), + socketAddress = json['socket_address']; +} + +////////////////////////////////////// +/// VeilidUpdate + +abstract class VeilidUpdate { + factory VeilidUpdate.fromJson(dynamic json) { + switch (json["kind"]) { + case "Log": + { + return VeilidLog( + logLevel: veilidLogLevelFromJson(json["log_level"]), + message: json["message"], + backtrace: json["backtrace"]); + } + case "AppMessage": + { + return VeilidAppMessage( + sender: json["sender"], message: json["message"]); + } + case "AppCall": + { + return VeilidAppCall( + sender: json["sender"], message: json["message"], id: json["id"]); + } + case "Attachment": + { + return VeilidUpdateAttachment( + state: VeilidStateAttachment.fromJson(json)); + } + case "Network": + { + return VeilidUpdateNetwork(state: VeilidStateNetwork.fromJson(json)); + } + case "Config": + { + return VeilidUpdateConfig(state: VeilidStateConfig.fromJson(json)); + } + case "RouteChange": + { + return VeilidUpdateRouteChange( + deadRoutes: List.from(json['dead_routes'].map((j) => j)), + deadRemoteRoutes: + List.from(json['dead_remote_routes'].map((j) => j))); + } + case "ValueChange": + { + return VeilidUpdateValueChange( + key: TypedKey.fromJson(json['key']), + subkeys: List.from( + json['subkeys'].map((j) => ValueSubkeyRange.fromJson(j))), + count: json['count'], + valueData: ValueData.fromJson(json['value_data'])); + } + default: + { + throw VeilidAPIExceptionInternal( + "Invalid VeilidAPIException type: ${json['kind']}"); + } + } + } + Map get json; +} + +class VeilidLog implements VeilidUpdate { + final VeilidLogLevel logLevel; + final String message; + final String? backtrace; + // + VeilidLog({ + required this.logLevel, + required this.message, + required this.backtrace, + }); + + @override + Map get json { + return { + 'kind': "Log", + 'log_level': logLevel.json, + 'message': message, + 'backtrace': backtrace + }; + } +} + +class VeilidAppMessage implements VeilidUpdate { + final String? sender; + final Uint8List message; + + // + VeilidAppMessage({ + required this.sender, + required this.message, + }); + + @override + Map get json { + return { + 'kind': "AppMessage", + 'sender': sender, + 'message': base64UrlNoPadEncode(message) + }; + } +} + +class VeilidAppCall implements VeilidUpdate { + final String? sender; + final Uint8List message; + final String id; + + // + VeilidAppCall({ + required this.sender, + required this.message, + required this.id, + }); + + @override + Map get json { + return { + 'kind': "AppMessage", + 'sender': sender, + 'message': base64UrlNoPadEncode(message), + 'id': id, + }; + } +} + +class VeilidUpdateAttachment implements VeilidUpdate { + final VeilidStateAttachment state; + // + VeilidUpdateAttachment({required this.state}); + + @override + Map get json { + var jsonRep = state.json; + jsonRep['kind'] = "Attachment"; + return jsonRep; + } +} + +class VeilidUpdateNetwork implements VeilidUpdate { + final VeilidStateNetwork state; + // + VeilidUpdateNetwork({required this.state}); + + @override + Map get json { + var jsonRep = state.json; + jsonRep['kind'] = "Network"; + return jsonRep; + } +} + +class VeilidUpdateConfig implements VeilidUpdate { + final VeilidStateConfig state; + // + VeilidUpdateConfig({required this.state}); + + @override + Map get json { + var jsonRep = state.json; + jsonRep['kind'] = "Config"; + return jsonRep; + } +} + +class VeilidUpdateRouteChange implements VeilidUpdate { + final List deadRoutes; + final List deadRemoteRoutes; + // + VeilidUpdateRouteChange({ + required this.deadRoutes, + required this.deadRemoteRoutes, + }); + + @override + Map get json { + return { + 'dead_routes': deadRoutes.map((p) => p).toList(), + 'dead_remote_routes': deadRemoteRoutes.map((p) => p).toList() + }; + } +} + +class VeilidUpdateValueChange implements VeilidUpdate { + final TypedKey key; + final List subkeys; + final int count; + final ValueData valueData; + + // + VeilidUpdateValueChange({ + required this.key, + required this.subkeys, + required this.count, + required this.valueData, + }); + + @override + Map get json { + return { + 'key': key.json, + 'subkeys': subkeys.map((p) => p.json).toList(), + 'count': count, + 'value_data': valueData.json, + }; + } +} + +////////////////////////////////////// +/// VeilidStateAttachment + +class VeilidStateAttachment { + final AttachmentState state; + final bool publicInternetReady; + final bool localNetworkReady; + + VeilidStateAttachment( + this.state, this.publicInternetReady, this.localNetworkReady); + + VeilidStateAttachment.fromJson(dynamic json) + : state = attachmentStateFromJson(json['state']), + publicInternetReady = json['public_internet_ready'], + localNetworkReady = json['local_network_ready']; + + Map get json { + return { + 'state': state.json, + 'public_internet_ready': publicInternetReady, + 'local_network_ready': localNetworkReady, + }; + } +} + +////////////////////////////////////// +/// VeilidStateNetwork + +class VeilidStateNetwork { + final bool started; + final BigInt bpsDown; + final BigInt bpsUp; + final List peers; + + VeilidStateNetwork( + {required this.started, + required this.bpsDown, + required this.bpsUp, + required this.peers}); + + VeilidStateNetwork.fromJson(dynamic json) + : started = json['started'], + bpsDown = BigInt.parse(json['bps_down']), + bpsUp = BigInt.parse(json['bps_up']), + peers = List.from( + json['peers'].map((j) => PeerTableData.fromJson(j))); + + Map get json { + return { + 'started': started, + 'bps_down': bpsDown.toString(), + 'bps_up': bpsUp.toString(), + 'peers': peers.map((p) => p.json).toList(), + }; + } +} + +////////////////////////////////////// +/// VeilidStateConfig + +class VeilidStateConfig { + final Map config; + + VeilidStateConfig({ + required this.config, + }); + + VeilidStateConfig.fromJson(dynamic json) : config = json['config']; + + Map get json { + return {'config': config}; + } +} + +////////////////////////////////////// +/// VeilidState + +class VeilidState { + final VeilidStateAttachment attachment; + final VeilidStateNetwork network; + final VeilidStateConfig config; + + VeilidState.fromJson(dynamic json) + : attachment = VeilidStateAttachment.fromJson(json['attachment']), + network = VeilidStateNetwork.fromJson(json['network']), + config = VeilidStateConfig.fromJson(json['config']); + + Map get json { + return { + 'attachment': attachment.json, + 'network': network.json, + 'config': config.json + }; + } +} diff --git a/veilid-flutter/lib/veilid_table_db.dart b/veilid-flutter/lib/veilid_table_db.dart new file mode 100644 index 00000000..4790988d --- /dev/null +++ b/veilid-flutter/lib/veilid_table_db.dart @@ -0,0 +1,59 @@ +import 'dart:async'; +import 'dart:typed_data'; +import 'dart:convert'; + +///////////////////////////////////// +/// VeilidTableDB +abstract class VeilidTableDBTransaction { + Future commit(); + Future rollback(); + Future store(int col, Uint8List key, Uint8List value); + Future delete(int col, Uint8List key); + + Future storeJson(int col, Uint8List key, Object? object, + {Object? Function(Object? nonEncodable)? toEncodable}) async { + return store(col, key, + utf8.encoder.convert(jsonEncode(object, toEncodable: toEncodable))); + } + + Future storeStringJson(int col, String key, Object? object, + {Object? Function(Object? nonEncodable)? toEncodable}) { + return storeJson(col, utf8.encoder.convert(key), object, + toEncodable: toEncodable); + } +} + +abstract class VeilidTableDB { + int getColumnCount(); + List getKeys(int col); + VeilidTableDBTransaction transact(); + Future store(int col, Uint8List key, Uint8List value); + Future load(int col, Uint8List key); + Future delete(int col, Uint8List key); + + Future storeJson(int col, Uint8List key, Object? object, + {Object? Function(Object? nonEncodable)? toEncodable}) { + return store(col, key, + utf8.encoder.convert(jsonEncode(object, toEncodable: toEncodable))); + } + + Future storeStringJson(int col, String key, Object? object, + {Object? Function(Object? nonEncodable)? toEncodable}) { + return storeJson(col, utf8.encoder.convert(key), object, + toEncodable: toEncodable); + } + + Future loadJson(int col, Uint8List key, + {Object? Function(Object? key, Object? value)? reviver}) async { + var s = await load(col, key); + if (s == null) { + return null; + } + return jsonDecode(utf8.decode(s, allowMalformed: false), reviver: reviver); + } + + Future loadStringJson(int col, String key, + {Object? Function(Object? key, Object? value)? reviver}) { + return loadJson(col, utf8.encoder.convert(key), reviver: reviver); + } +} diff --git a/veilid-flutter/pubspec.yaml b/veilid-flutter/pubspec.yaml index ad515fc1..5e5f7b02 100644 --- a/veilid-flutter/pubspec.yaml +++ b/veilid-flutter/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: path_provider: ^2.0.9 path: ^1.8.0 system_info2: ^3.0.2 + charcode: ^1.3.1 dev_dependencies: flutter_test: diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index a3c96ff1..841c030f 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -481,7 +481,7 @@ pub extern "C" fn routing_context_app_message(port: i64, id: u32, target: FfiStr let routing_context = { let rc = ROUTING_CONTEXTS.lock(); let Some(routing_context) = rc.get(&id) else { - return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_message", "id", id)); }; routing_context.clone() }; @@ -492,6 +492,65 @@ pub extern "C" fn routing_context_app_message(port: i64, id: u32, target: FfiStr }); } +#[no_mangle] +pub extern "C" fn routing_context_create_dht_record(port: i64, id: u32, kind: u32, schema: FfiStr) { + let crypto_kind: veilid_core::CryptoKind = veilid_core::FourCC::from(kind.to_be_bytes()); + let schema: veilid_core::DHTSchema = veilid_core::deserialize_opt_json(schema.into_opt_string()).unwrap(); + + DartIsolateWrapper::new(port).spawn_result(async move { + let routing_context = { + let rc = ROUTING_CONTEXTS.lock(); + let Some(routing_context) = rc.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_create_dht_record", "id", id)); + }; + routing_context.clone() + }; + + let dht_record_descriptor = routing_context.create_dht_record(crypto_kind, schema).await?; + let out = veilid_core::serialize_json(dht_record_descriptor); + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn routing_context_open_dht_record(port: i64, id: u32, key: FfiStr, writer: FfiStr) { + let key: veilid_core::TypedKey = veilid_core::deserialize_opt_json(key.into_opt_string()).unwrap(); + let writer: Option = writer.into_opt_string().map(|s| veilid_core::deserialize_json(&s).unwrap()); + DartIsolateWrapper::new(port).spawn_result(async move { + let routing_context = { + let rc = ROUTING_CONTEXTS.lock(); + let Some(routing_context) = rc.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_open_dht_record", "id", id)); + }; + routing_context.clone() + }; + let writer = match writer { + Some(w) => w.to_key_pair(key.kind)?, + None => None + }; + let dht_record_descriptor = routing_context.open_dht_record(key, writer).await?; + let out = veilid_core::serialize_json(dht_record_descriptor); + APIResult::Ok(out) + }); +} + + + +// final _RoutingContextCloseDHTRecordDart _RoutingContextCloseDHTRecord; +// final _RoutingContextDeleteDHTRecordDart _RoutingContextDeleteDHTRecord; +// final _RoutingContextGetDHTValueDart _RoutingContextGetDHTValue; +// final _RoutingContextSetDHTValueDart _RoutingContextSetDHTValue; +// final _RoutingContextWatchDHTValuesDart _RoutingContextWatchDHTValues; +// final _RoutingContextCancelDHTWatchDart _RoutingContextCancelDHTWatch; + + + + + + + + + #[no_mangle] pub extern "C" fn new_private_route(port: i64) { DartIsolateWrapper::new(port).spawn_result_json(async move { @@ -834,6 +893,36 @@ pub extern "C" fn table_db_delete(port: i64, id: u32, col: u32, key: FfiStr) { }); } + + +final _ValidCryptoKindsDart _validCryptoKinds; +final _GetCryptoSystemDart _getCryptoSystem; +final _BestCryptoSystemDart _bestCryptoSystem; +final _VerifySignaturesDart _verifySignatures; +final _GenerateSignaturesDart _generateSignatures; +final _GenerateKeyPairDart _generateKeyPair; + +final _CryptoCachedDHDart _cryptoCachedDH; +final _CryptoComputeDHDart _cryptoComputeDH; +final _CryptoRandomNonceDart _cryptoRandomNonce; +final _CryptoRandomSharedSecretDart _cryptoRandomSharedSecret; +final _CryptoGenerateKeyPairDart _cryptoGenerateKeyPair; +final _CryptoGenerateHashDart _cryptoGenerateHash; +final _CryptoGenerateHashReaderDart _cryptoGenerateHashReader; +final _CryptoValidateKeyPairDart _cryptoValidateKeyPair; +final _CryptoValidateHashDart _cryptoValidateHash; +final _CryptoValidateHashReaderDart _cryptoValidateHashReader; +final _CryptoDistanceDart _cryptoDistance; +final _CryptoSignDart _cryptoSign; +final _CryptoVerifyDart _cryptoVerify; +final _CryptoAaedOverheadDart _cryptoAeadOverhead; +final _CryptoDecryptAeadDart _cryptoDecryptAead; +final _CryptoEncryptAeadDart _cryptoEncryptAead; +final _CryptoCryptNoAuthDart _cryptoCryptNoAuth; + +final _NowDart _now; + + #[no_mangle] pub extern "C" fn debug(port: i64, command: FfiStr) { let command = command.into_opt_string().unwrap_or_default();