diff --git a/.cargo/config.toml b/.cargo/config.toml index 91fa3b11..bff29e6e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,14 +1,2 @@ [build] rustflags = ["--cfg", "tokio_unstable"] - -[target.aarch64-unknown-linux-gnu] -linker = "aarch64-linux-gnu-gcc" - -[target.aarch64-linux-android] -linker = "/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang" -[target.armv7-linux-androideabi] -linker = "/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi30-clang" -[target.x86_64-linux-android] -linker = "/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android30-clang" -[target.i686-linux-android] -linker = "/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android30-clang" \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index a5c2ae1b..3cdd9159 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,9 +16,6 @@ [submodule "external/netlink"] path = external/netlink url = ../netlink.git -[submodule "external/no-std-net"] - path = external/no-std-net - url = ../no-std-net.git [submodule "external/libmdns"] path = external/libmdns url = ../libmdns.git diff --git a/Cargo.lock b/Cargo.lock index df0254c1..8e5e591a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ "gimli", ] @@ -23,7 +23,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -46,7 +46,7 @@ checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ "getrandom 0.2.8", "once_cell", - "version_check", + "version_check 0.9.4", ] [[package]] @@ -58,14 +58,14 @@ dependencies = [ "cfg-if 1.0.0", "getrandom 0.2.8", "once_cell", - "version_check", + "version_check 0.9.4", ] [[package]] name = "aho-corasick" -version = "0.7.19" +version = "0.7.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" dependencies = [ "memchr", ] @@ -100,6 +100,23 @@ dependencies = [ "syn", ] +[[package]] +name = "android-logd-logger" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53eff4527d2f64c8374a3bbe1d280ce660203e8c83e4a893231037a488639a7b" +dependencies = [ + "bytes 1.3.0", + "env_logger 0.8.4", + "lazy_static", + "libc", + "log", + "redox_syscall", + "thiserror", + "time 0.2.27", + "winapi 0.3.9", +] + [[package]] name = "android_log-sys" version = "0.2.0" @@ -108,12 +125,12 @@ checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" [[package]] name = "android_logger" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e9dd62f37dea550caf48c77591dc50bd1a378ce08855be1a0c42a97b7550fb" +checksum = "8619b80c242aa7bd638b5c7ddd952addeecb71f69c75e33f1d47b2804f8f883a" dependencies = [ "android_log-sys", - "env_logger", + "env_logger 0.10.0", "log", "once_cell", ] @@ -133,6 +150,16 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "ansi-parser" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcb2392079bf27198570d6af79ecbd9ec7d8f16d3ec6b60933922fdb66287127" +dependencies = [ + "heapless", + "nom 4.2.3", +] + [[package]] name = "ansi_term" version = "0.12.1" @@ -144,9 +171,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.66" +version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" [[package]] name = "arraydeque" @@ -166,6 +193,18 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "as-slice" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45403b49e3954a4b8428a0ac21a4b7afadccf92bfd96273f1a58cd4812496ae0" +dependencies = [ + "generic-array 0.12.4", + "generic-array 0.13.3", + "generic-array 0.14.6", + "stable_deref_trait", +] + [[package]] name = "async-attributes" version = "1.1.2" @@ -178,11 +217,11 @@ dependencies = [ [[package]] name = "async-channel" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14485364214912d3b19cc3435dde4df66065127f05fa0d75c712f36f12c2f28" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" dependencies = [ - "concurrent-queue 1.2.4", + "concurrent-queue", "event-listener", "futures-core", ] @@ -195,7 +234,7 @@ checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" dependencies = [ "async-lock", "async-task", - "concurrent-queue 2.0.0", + "concurrent-queue", "fastrand", "futures-lite", "slab", @@ -218,13 +257,13 @@ dependencies = [ [[package]] name = "async-io" -version = "1.10.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8121296a9f05be7f34aa4196b1747243b3b62e048bb7906f644f3fbfc490cf7" +checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" dependencies = [ "async-lock", "autocfg", - "concurrent-queue 1.2.4", + "concurrent-queue", "futures-lite", "libc", "log", @@ -233,7 +272,7 @@ dependencies = [ "slab", "socket2", "waker-fn", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -248,20 +287,20 @@ dependencies = [ [[package]] name = "async-process" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02111fd8655a613c25069ea89fc8d9bb89331fa77486eb3bc059ee757cfa481c" +checksum = "6381ead98388605d0d9ff86371043b5aa922a3905824244de40dc263a14fcba4" dependencies = [ "async-io", + "async-lock", "autocfg", "blocking", "cfg-if 1.0.0", "event-listener", "futures-lite", "libc", - "once_cell", "signal-hook", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -349,9 +388,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.58" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" +checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" dependencies = [ "proc-macro2", "quote", @@ -373,16 +412,16 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b750efd83b7e716a015eed5ebb583cda83c52d9b24a8f0125e5c48c3313c9f8" +checksum = "8e6acf7e4a267eecbb127ed696bb2d50572c22ba7f586a646321e1798d8336a1" dependencies = [ "async-tls", "futures-io", "futures-util", "log", "pin-project-lite 0.2.9", - "tungstenite 0.17.3", + "tungstenite 0.18.0", ] [[package]] @@ -398,8 +437,8 @@ dependencies = [ "futures-timer", "futures-util", "pin-project 1.0.12", - "rustc_version", - "tokio 1.21.2", + "rustc_version 0.4.0", + "tokio 1.23.0", "wasm-bindgen-futures", ] @@ -411,7 +450,7 @@ checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c" dependencies = [ "futures", "pharos", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -447,7 +486,7 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" dependencies = [ - "hermit-abi", + "hermit-abi 0.1.19", "libc", "winapi 0.3.9", ] @@ -460,14 +499,14 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.17" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" +checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" dependencies = [ "async-trait", "axum-core", "bitflags", - "bytes 1.2.1", + "bytes 1.3.0", "futures-util", "http", "http-body", @@ -478,9 +517,9 @@ dependencies = [ "mime", "percent-encoding", "pin-project-lite 0.2.9", + "rustversion", "serde", "sync_wrapper", - "tokio 1.21.2", "tower", "tower-http", "tower-layer", @@ -489,25 +528,26 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.2.9" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" +checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" dependencies = [ "async-trait", - "bytes 1.2.1", + "bytes 1.3.0", "futures-util", "http", "http-body", "mime", + "rustversion", "tower-layer", "tower-service", ] [[package]] name = "backtrace" -version = "0.3.66" +version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ "addr2line", "cc", @@ -518,6 +558,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base64" version = "0.12.3" @@ -537,7 +583,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" dependencies = [ "bitflags", - "cexpr", + "cexpr 0.4.0", "clang-sys", "lazy_static", "lazycell", @@ -546,7 +592,30 @@ dependencies = [ "quote", "regex", "rustc-hash", - "shlex", + "shlex 0.1.1", +] + +[[package]] +name = "bindgen" +version = "0.59.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +dependencies = [ + "bitflags", + "cexpr 0.6.0", + "clang-sys", + "clap 2.34.0", + "env_logger 0.9.3", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.1.0", + "which", ] [[package]] @@ -569,9 +638,9 @@ dependencies = [ [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "42ae2468a89544a466886840aa467a25b766499f4f04bf7d9fcd10ecee9fccef" dependencies = [ "arrayref", "arrayvec", @@ -597,7 +666,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -606,7 +675,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -627,16 +696,16 @@ checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] name = "blocking" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" dependencies = [ "async-channel", + "async-lock", "async-task", "atomic-waker", "fastrand", "futures-lite", - "once_cell", ] [[package]] @@ -707,27 +776,21 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" - -[[package]] -name = "cache-padded" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "capnp" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e850735c543306805e2ba8ee0a9632b0f62bb05872a8be2e2674e9903a1c048" +checksum = "afaa14ddcf4553e700608c1c0ee3ca1f4cf673470462b99ff6dd6bedcdb6c6ce" [[package]] name = "capnp-futures" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4660c91469f0222724da5b6f5fed9605b466c5dd55e1fbbc3aabd1034637ccd0" +checksum = "addd5d5f64da51c84060b760cc1a39b5de0de8b67f254c38e3e4889d9dcf9137" dependencies = [ "capnp", "futures", @@ -746,9 +809,9 @@ dependencies = [ [[package]] name = "capnpc" -version = "0.15.0" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed9e0059e1c97ed65eb246d90fb843a16f8da5eb31ec4a560acb31a825923f9" +checksum = "476b328e8298e5454f9d72b53a15da8d8725e572bc3d43e4e4cdb77a49093ee9" dependencies = [ "capnp", ] @@ -761,9 +824,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a284da2e6fe2092f2353e51713435363112dfd60030e22add80be333fb928f" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" [[package]] name = "cesu8" @@ -780,6 +843,15 @@ dependencies = [ "nom 5.1.2", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.2", +] + [[package]] name = "cfg-if" version = "0.1.10" @@ -830,15 +902,15 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", "num-integer", "num-traits", - "time 0.1.44", + "time 0.1.45", "wasm-bindgen", "winapi 0.3.9", ] @@ -876,7 +948,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -900,6 +972,21 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + [[package]] name = "clap" version = "3.2.23" @@ -910,9 +997,9 @@ dependencies = [ "bitflags", "clap_lex", "indexmap", - "strsim", + "strsim 0.10.0", "termcolor", - "textwrap", + "textwrap 0.16.0", ] [[package]] @@ -962,19 +1049,10 @@ version = "4.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "memchr", ] -[[package]] -name = "concurrent-queue" -version = "1.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c" -dependencies = [ - "cache-padded", -] - [[package]] name = "concurrent-queue" version = "2.0.0" @@ -986,14 +1064,14 @@ dependencies = [ [[package]] name = "config" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11f1667b8320afa80d69d8bbe40830df2c8a06003d86f73d8e003b2c48df416d" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" dependencies = [ "async-trait", "json5", "lazy_static", - "nom 7.1.1", + "nom 7.1.2", "pathdiff", "ron", "rust-ini", @@ -1031,7 +1109,7 @@ dependencies = [ "serde", "serde_json", "thread_local", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-stream", "tonic", "tracing", @@ -1060,10 +1138,16 @@ dependencies = [ ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "const_fn" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" + +[[package]] +name = "constant_time_eq" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" [[package]] name = "core-foundation" @@ -1125,7 +1209,7 @@ dependencies = [ "atty", "cast", "ciborium", - "clap", + "clap 3.2.23", "criterion-plot", "itertools", "lazy_static", @@ -1174,22 +1258,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.11" +version = "0.9.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f916dfc5d356b0ed9dae65f1db9fc9770aa2851d2662b988ccf4fe3516e86348" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" dependencies = [ "autocfg", "cfg-if 1.0.0", "crossbeam-utils", - "memoffset", + "memoffset 0.7.1", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" dependencies = [ "cfg-if 1.0.0", ] @@ -1231,7 +1315,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.6", "typenum", ] @@ -1241,7 +1325,7 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] @@ -1257,12 +1341,12 @@ dependencies = [ [[package]] name = "ctrlc" -version = "3.2.3" +version = "3.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d91974fbbe88ec1df0c24a4f00f99583667a7e2e6272b2b92d294d81e462173" +checksum = "1631ca6e3c59112501a9d87fd86f21591ff77acd31331e8a73f8d80a65bbdd71" dependencies = [ - "nix 0.25.0", - "winapi 0.3.9", + "nix 0.26.1", + "windows-sys 0.42.0", ] [[package]] @@ -1285,7 +1369,7 @@ dependencies = [ "libc", "log", "signal-hook", - "tokio 1.21.2", + "tokio 1.23.0", "unicode-segmentation", "unicode-width", ] @@ -1321,6 +1405,7 @@ name = "cursive_core" version = "0.3.5" dependencies = [ "ahash 0.8.2", + "ansi-parser", "async-std", "crossbeam-channel", "enum-map", @@ -1330,7 +1415,7 @@ dependencies = [ "num", "owning_ref", "time 0.3.17", - "tokio 1.21.2", + "tokio 1.23.0", "toml", "unicode-segmentation", "unicode-width", @@ -1374,9 +1459,9 @@ dependencies = [ [[package]] name = "cxx" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" +checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" dependencies = [ "cc", "cxxbridge-flags", @@ -1386,9 +1471,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32cc5fea1d894b77d269ddb9f192110069a8a9c1f1d441195fba90553dea3" +checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" dependencies = [ "cc", "codespan-reporting", @@ -1401,15 +1486,15 @@ dependencies = [ [[package]] name = "cxxbridge-flags" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" +checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" [[package]] name = "cxxbridge-macro" -version = "1.0.81" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" +checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ "proc-macro2", "quote", @@ -1456,7 +1541,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn", ] @@ -1505,14 +1590,14 @@ dependencies = [ "hashbrown", "lock_api", "once_cell", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.5", ] [[package]] name = "data-encoding" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" [[package]] name = "derivative" @@ -1531,14 +1616,14 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] name = "digest" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer 0.10.3", "crypto-common", @@ -1564,6 +1649,12 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "discard" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" + [[package]] name = "dlv-list" version = "0.3.0" @@ -1612,18 +1703,18 @@ dependencies = [ [[package]] name = "enum-map" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a56d54c8dd9b3ad34752ed197a4eb2a6601bc010808eb097a04a58ae4c43e1" +checksum = "50c25992259941eb7e57b936157961b217a4fc8597829ddef0596d6c3cd86e1a" dependencies = [ "enum-map-derive", ] [[package]] name = "enum-map-derive" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9045e2676cd5af83c3b167d917b0a5c90a4d8e266e2683d6631b235c457fc27" +checksum = "2a4da76b3b6116d758c7ba93f7ec6a35d2e2cf24feda76c6e38a375f4d5c59f2" dependencies = [ "proc-macro2", "quote", @@ -1673,11 +1764,34 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ "log", "regex", @@ -1712,9 +1826,9 @@ dependencies = [ [[package]] name = "ethereum-types" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81224dc661606574f5a0f28c9947d0ee1d93ff11c5f1c4e7272f52e8c0b5483c" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" dependencies = [ "ethbloom", "fixed-hash", @@ -1813,9 +1927,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", "miniz_oxide", @@ -2013,6 +2127,24 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +dependencies = [ + "typenum", +] + +[[package]] +name = "generic-array" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f797e67af32588215eaaab8327027ee8e71b9dd0b2b26996aedf20c030fce309" +dependencies = [ + "typenum", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -2020,7 +2152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ "typenum", - "version_check", + "version_check 0.9.4", ] [[package]] @@ -2049,9 +2181,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" [[package]] name = "glob" @@ -2061,9 +2193,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "gloo-timers" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +checksum = "98c4a8d6391675c6b2ee1a6c8d06e8e2d03605c44cec1270675985a4c2a5500b" dependencies = [ "futures-channel", "futures-core", @@ -2073,9 +2205,9 @@ dependencies = [ [[package]] name = "gloo-utils" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40913a05c8297adca04392f707b1e73b12ba7b8eab7244a4961580b1fd34063c" +checksum = "a8e8fc851e9c7b9852508bc6e3f690f452f474417e8545ec9857b7f7377036b5" dependencies = [ "js-sys", "serde", @@ -2104,7 +2236,7 @@ version = "0.9.1+1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9447d1a926beeef466606cc45717f80897998b548e7dc622873d453e1ecb4be4" dependencies = [ - "bindgen", + "bindgen 0.57.0", "boringssl-src", "cc", "cmake", @@ -2120,7 +2252,7 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "fnv", "futures-core", "futures-sink", @@ -2128,7 +2260,7 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-util", "tracing", ] @@ -2139,6 +2271,15 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hash32" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4041af86e63ac4298ce40e5cca669066e75b6f1aa3390fe2561ffa5e1d9f4cc" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -2174,10 +2315,22 @@ dependencies = [ "base64 0.13.1", "byteorder", "flate2", - "nom 7.1.1", + "nom 7.1.2", "num-traits", ] +[[package]] +name = "heapless" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74911a68a1658cfcfb61bc0ccfbd536e3b6e906f8c2f7883ee50157e3e2184f1" +dependencies = [ + "as-slice", + "generic-array 0.13.3", + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.0" @@ -2193,6 +2346,15 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + [[package]] name = "hex" version = "0.4.3" @@ -2236,7 +2398,7 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "fnv", "itoa", ] @@ -2247,7 +2409,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "http", "pin-project-lite 0.2.9", ] @@ -2282,7 +2444,7 @@ version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "futures-channel", "futures-core", "futures-util", @@ -2294,7 +2456,7 @@ dependencies = [ "itoa", "pin-project-lite 0.2.9", "socket2", - "tokio 1.21.2", + "tokio 1.23.0", "tower-service", "tracing", "want", @@ -2308,7 +2470,7 @@ checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ "hyper", "pin-project-lite 0.2.9", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-io-timeout", ] @@ -2378,7 +2540,7 @@ name = "igd" version = "0.12.0" dependencies = [ "attohttpc", - "bytes 1.2.1", + "bytes 1.3.0", "futures", "http", "hyper", @@ -2387,7 +2549,7 @@ dependencies = [ "simplelog 0.9.0", "tokio 0.2.25", "tokio 0.3.7", - "tokio 1.21.2", + "tokio 1.23.0", "url", "xmltree", ] @@ -2438,9 +2600,9 @@ checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -2452,7 +2614,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "generic-array", + "generic-array 0.14.6", ] [[package]] @@ -2487,9 +2649,9 @@ dependencies = [ [[package]] name = "ipconfig" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "723519edce41262b05d4143ceb95050e4c614f483e78e9fd9e39a8275a84ad98" +checksum = "bd302af1b90f2463a98fa5ad469fc212c8e3175a41c3068601bfa2727591c5be" dependencies = [ "socket2", "widestring 0.5.1", @@ -2499,9 +2661,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.5.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" +checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" [[package]] name = "itertools" @@ -2514,9 +2676,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] name = "jni" @@ -2599,11 +2761,8 @@ dependencies = [ name = "keyring-manager" version = "0.5.0" dependencies = [ - "android_logger", - "backtrace", "byteorder", "cfg-if 1.0.0", - "clap", "core-foundation 0.9.3", "core-foundation-sys 0.8.3", "directories", @@ -2612,18 +2771,14 @@ dependencies = [ "keychain-services", "lazy_static", "log", - "ndk 0.6.0", + "ndk", "ndk-glue", - "rpassword 5.0.1", "secret-service", "security-framework", "security-framework-sys", "serde", "serde_cbor", - "serial_test", - "simplelog 0.12.0", "snailquote", - "tempfile", "winapi 0.3.9", ] @@ -2641,6 +2796,9 @@ dependencies = [ "keyvaluedb", "keyvaluedb-shared-tests", "parking_lot 0.12.1", + "tokio 1.23.0", + "wasm-bindgen-futures", + "wasm-bindgen-test", ] [[package]] @@ -2669,6 +2827,7 @@ dependencies = [ "rusqlite", "sysinfo", "tempfile", + "tokio 1.23.0", ] [[package]] @@ -2676,6 +2835,7 @@ name = "keyvaluedb-web" version = "0.1.0" dependencies = [ "console_log", + "flume", "futures", "js-sys", "keyvaluedb", @@ -2713,9 +2873,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] name = "libloading" @@ -2752,9 +2912,9 @@ dependencies = [ [[package]] name = "link-cplusplus" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" dependencies = [ "cc", ] @@ -2823,9 +2983,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "matchit" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73cbba799671b762df5a175adf59ce145165747bb891505c43d09aefbbf38beb" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" [[package]] name = "memchr" @@ -2842,6 +3002,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + [[package]] name = "memory_units" version = "0.4.0" @@ -2862,9 +3031,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.5.4" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] @@ -2970,19 +3139,6 @@ dependencies = [ "socket2", ] -[[package]] -name = "ndk" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" -dependencies = [ - "bitflags", - "jni-sys", - "ndk-sys 0.3.0", - "num_enum", - "thiserror", -] - [[package]] name = "ndk" version = "0.7.0" @@ -2991,7 +3147,7 @@ checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" dependencies = [ "bitflags", "jni-sys", - "ndk-sys 0.4.0", + "ndk-sys 0.4.1+23.1.7779620", "num_enum", "raw-window-handle", "thiserror", @@ -3012,10 +3168,10 @@ dependencies = [ "android_logger", "libc", "log", - "ndk 0.7.0", + "ndk", "ndk-context", "ndk-macro", - "ndk-sys 0.4.0", + "ndk-sys 0.4.1+23.1.7779620", "once_cell", "parking_lot 0.12.1", ] @@ -3044,9 +3200,9 @@ dependencies = [ [[package]] name = "ndk-sys" -version = "0.4.0" +version = "0.4.1+23.1.7779620" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21d83ec9c63ec5bf950200a8e508bdad6659972187b625469f58ef8c08e29046" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" dependencies = [ "jni-sys", ] @@ -3098,12 +3254,12 @@ dependencies = [ name = "netlink-proto" version = "0.9.1" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "futures", "log", "netlink-packet-core", "netlink-sys", - "tokio 1.21.2", + "tokio 1.23.0", ] [[package]] @@ -3111,11 +3267,11 @@ name = "netlink-sys" version = "0.8.1" dependencies = [ "async-io", - "bytes 1.2.1", + "bytes 1.3.0", "futures", "libc", "log", - "tokio 1.21.2", + "tokio 1.23.0", ] [[package]] @@ -3128,29 +3284,31 @@ dependencies = [ "cc", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.6.5", ] [[package]] name = "nix" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e322c04a9e3440c327fca7b6c8a63e6890a32fa2ad689db972425f07e0d22abb" +checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" dependencies = [ - "autocfg", "bitflags", "cfg-if 1.0.0", "libc", - "memoffset", + "memoffset 0.7.1", "pin-utils", + "static_assertions", ] [[package]] -name = "no-std-net" -version = "0.6.0" +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" dependencies = [ - "serde", - "serde_test", + "memchr", + "version_check 0.1.5", ] [[package]] @@ -3160,14 +3318,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" dependencies = [ "memchr", - "version_check", + "version_check 0.9.4", ] [[package]] name = "nom" -version = "7.1.1" +version = "7.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36" +checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" dependencies = [ "memchr", "minimal-lexical", @@ -3279,11 +3437,11 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.14.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] @@ -3319,18 +3477,18 @@ dependencies = [ [[package]] name = "object" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] name = "oorandom" @@ -3370,7 +3528,7 @@ dependencies = [ "prost", "protobuf", "thiserror", - "tokio 1.21.2", + "tokio 1.23.0", "tonic", ] @@ -3434,7 +3592,7 @@ dependencies = [ "percent-encoding", "rand 0.8.5", "thiserror", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-stream", ] @@ -3450,9 +3608,20 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.4.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5bf27447411e9ee3ff51186bf7a08e16c341efdde93f4d823e8844429bed7e" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] [[package]] name = "overload" @@ -3475,6 +3644,20 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "paranoid-android" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e736c9fbaf42b43459cd1fded3dd272968daadfcbc5660ee231a12899f092289" +dependencies = [ + "lazy_static", + "ndk-sys 0.3.0", + "sharded-slab", + "smallvec", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "parity-scale-codec" version = "3.2.1" @@ -3515,7 +3698,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core 0.8.5", + "parking_lot_core 0.8.6", ] [[package]] @@ -3525,14 +3708,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", + "parking_lot_core 0.9.5", ] [[package]] name = "parking_lot_core" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if 1.0.0", "instant", @@ -3544,9 +3727,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" +checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" dependencies = [ "cfg-if 1.0.0", "libc", @@ -3557,9 +3740,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de2e551fb905ac83f73f7aedf2f0cb4a0da7e35efa24a202a936269f1f18e1" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" [[package]] name = "pathdiff" @@ -3581,9 +3764,9 @@ checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] name = "pest" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8" +checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" dependencies = [ "thiserror", "ucd-trie", @@ -3591,9 +3774,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fd9bc6500181952d34bd0b2b0163a54d794227b498be0b7afa7698d0a7b18f" +checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" dependencies = [ "pest", "pest_generator", @@ -3601,9 +3784,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2610d5ac5156217b4ff8e46ddcef7cdf44b273da2ac5bca2ecbfa86a330e7c4" +checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" dependencies = [ "pest", "pest_meta", @@ -3614,13 +3797,13 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.4.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824749bf7e21dd66b36fbe26b3f45c713879cccd4a009a917ab8e045ca8246fe" +checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" dependencies = [ "once_cell", "pest", - "sha1", + "sha1 0.10.5", ] [[package]] @@ -3640,7 +3823,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414" dependencies = [ "futures", - "rustc_version", + "rustc_version 0.4.0", ] [[package]] @@ -3737,16 +3920,16 @@ dependencies = [ [[package]] name = "polling" -version = "2.4.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4609a838d88b73d8238967b60dd115cc08d38e2bbaf51ee1e4b695f89122e2" +checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" dependencies = [ "autocfg", "cfg-if 1.0.0", "libc", "log", "wepoll-ffi", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -3768,9 +3951,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c142c0e46b57171fe0c528bee8c5b7569e80f0c17e377cd0e30ea57dbc11bb51" +checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773" dependencies = [ "proc-macro2", "syn", @@ -3819,7 +4002,7 @@ dependencies = [ "proc-macro2", "quote", "syn", - "version_check", + "version_check 0.9.4", ] [[package]] @@ -3830,35 +4013,41 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check", + "version_check 0.9.4", ] [[package]] -name = "proc-macro2" -version = "1.0.47" +name = "proc-macro-hack" +version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" +checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "prost-derive", ] [[package]] name = "prost-build" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8b442418ea0822409d9e7d047cbf1e7e9e1760b172bf9982cf29d517c93511" +checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "heck", "itertools", "lazy_static", @@ -3876,9 +4065,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" +checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" dependencies = [ "anyhow", "itertools", @@ -3889,11 +4078,11 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.11.2" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747761bc3dc48f9a34553bf65605cf6cb6288ba219f3450b4275dbd81539551a" +checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "prost", ] @@ -3931,9 +4120,9 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" dependencies = [ "proc-macro2", ] @@ -4026,21 +4215,19 @@ dependencies = [ [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "autocfg", - "crossbeam-deque", "either", "rayon-core", ] [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -4140,7 +4327,7 @@ dependencies = [ [[package]] name = "rkyv" version = "0.7.39" -source = "git+https://github.com/crioux/rkyv.git?branch=issue_326#2f19cfac9f31a15e2fe74ad362eec7b011dc35b9" +source = "git+https://github.com/rkyv/rkyv.git?rev=57e2a8d#57e2a8daff3e6381e170e723ed1beea5c113b232" dependencies = [ "bytecheck", "hashbrown", @@ -4153,7 +4340,7 @@ dependencies = [ [[package]] name = "rkyv_derive" version = "0.7.39" -source = "git+https://github.com/crioux/rkyv.git?branch=issue_326#2f19cfac9f31a15e2fe74ad362eec7b011dc35b9" +source = "git+https://github.com/rkyv/rkyv.git?rev=57e2a8d#57e2a8daff3e6381e170e723ed1beea5c113b232" dependencies = [ "proc-macro2", "quote", @@ -4166,7 +4353,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "rustc-hex", ] @@ -4181,16 +4368,6 @@ dependencies = [ "serde", ] -[[package]] -name = "rpassword" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" -dependencies = [ - "libc", - "winapi 0.3.9", -] - [[package]] name = "rpassword" version = "6.0.1" @@ -4214,7 +4391,7 @@ dependencies = [ "netlink-proto", "nix 0.22.3", "thiserror", - "tokio 1.21.2", + "tokio 1.23.0", ] [[package]] @@ -4233,18 +4410,18 @@ dependencies = [ [[package]] name = "rust-fsm" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9ee4731a0f53c973772b83c43c57a26e146b6fa024af5aeab972b63d678b65" +checksum = "021d7de715253e45ad24a2fbb0725a0f7f271fd8d3163b130bd65ce2816a860d" dependencies = [ "rust-fsm-dsl", ] [[package]] name = "rust-fsm-dsl" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44237c429621e3606374941c3061fe95686bdaddb9b4f6524e4edc2d21da9c58" +checksum = "8a66b1273014079e4cf2b04aad1f3a2849e26e9a106f0411be2b1c15c23a791a" dependencies = [ "quote", "syn", @@ -4278,13 +4455,22 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver 0.9.0", +] + [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver", + "semver 1.0.16", ] [[package]] @@ -4311,15 +4497,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" [[package]] name = "ryu" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" [[package]] name = "same-file" @@ -4344,9 +4530,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" [[package]] name = "sct" @@ -4418,9 +4604,24 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.14" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "send_wrapper" @@ -4445,9 +4646,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" dependencies = [ "serde_derive", ] @@ -4461,15 +4662,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_bytes" -version = "0.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b" -dependencies = [ - "serde", -] - [[package]] name = "serde_cbor" version = "0.11.2" @@ -4482,9 +4674,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", @@ -4493,9 +4685,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ "itoa", "ryu", @@ -4504,29 +4696,20 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "serde_test" -version = "1.0.147" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "641666500e4e6fba7b91b73651a375cb53579468ab3c38389289b802797cad94" -dependencies = [ - "serde", -] - [[package]] name = "serde_yaml" -version = "0.9.14" +version = "0.9.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" +checksum = "92b5b431e8907b50339b51223b97d102db8d987ced36f6e4d03621db9316c834" dependencies = [ "indexmap", "itoa", @@ -4537,9 +4720,9 @@ dependencies = [ [[package]] name = "serial_test" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92761393ee4dc3ff8f4af487bd58f4307c9329bbedea02cac0089ad9c411e153" +checksum = "1c789ec87f4687d022a2405cf46e0cd6284889f1839de292cadeb6c6019506f2" dependencies = [ "dashmap", "futures", @@ -4551,11 +4734,10 @@ dependencies = [ [[package]] name = "serial_test_derive" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b6f5d1c3087fb119617cff2966fe3808a80e5eb59a8c1601d5994d66f4346a5" +checksum = "b64f9e531ce97c88b4778aad0ceee079216071cffec6ac9b904277f8f92e7fe3" dependencies = [ - "proc-macro-error", "proc-macro2", "quote", "syn", @@ -4575,14 +4757,12 @@ dependencies = [ ] [[package]] -name = "sha-1" -version = "0.10.0" +name = "sha1" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f" +checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" dependencies = [ - "cfg-if 1.0.0", - "cpufeatures", - "digest 0.10.5", + "sha1_smol", ] [[package]] @@ -4593,9 +4773,15 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if 1.0.0", "cpufeatures", - "digest 0.10.5", + "digest 0.10.6", ] +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + [[package]] name = "sha2" version = "0.9.9" @@ -4624,6 +4810,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook" version = "0.3.14" @@ -4750,12 +4942,70 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "standback" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" +dependencies = [ + "version_check 0.9.4", +] + [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stdweb" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" +dependencies = [ + "discard", + "rustc_version 0.2.3", + "stdweb-derive", + "stdweb-internal-macros", + "stdweb-internal-runtime", + "wasm-bindgen", +] + +[[package]] +name = "stdweb-derive" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_derive", + "syn", +] + +[[package]] +name = "stdweb-internal-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" +dependencies = [ + "base-x", + "proc-macro2", + "quote", + "serde", + "serde_derive", + "serde_json", + "sha1 0.6.1", + "syn", +] + +[[package]] +name = "stdweb-internal-runtime" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" + [[package]] name = "stop-token" version = "0.7.0" @@ -4768,6 +5018,12 @@ dependencies = [ "pin-project-lite 0.2.9", ] +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "strsim" version = "0.10.0" @@ -4788,9 +5044,9 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "syn" -version = "1.0.103" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" dependencies = [ "proc-macro2", "quote", @@ -4817,9 +5073,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.26.7" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c375d5fd899e32847b8566e10598d6e9f1d9b55ec6de3cdf9e7da4bdc51371bc" +checksum = "17351d0e9eb8841897b14e9669378f3c69fb57779cc04f8ca9a9d512edfb2563" dependencies = [ "cfg-if 1.0.0", "core-foundation-sys 0.8.3", @@ -4859,6 +5115,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "textwrap" version = "0.16.0" @@ -4867,18 +5132,18 @@ checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", @@ -4896,15 +5161,30 @@ dependencies = [ [[package]] name = "time" -version = "0.1.44" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" dependencies = [ "libc", "wasi 0.10.0+wasi-snapshot-preview1", "winapi 0.3.9", ] +[[package]] +name = "time" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4752a97f8eebd6854ff91f1c1824cd6160626ac4bd44287f7f4ea2035a02a242" +dependencies = [ + "const_fn", + "libc", + "standback", + "stdweb", + "time-macros 0.1.1", + "version_check 0.9.4", + "winapi 0.3.9", +] + [[package]] name = "time" version = "0.3.17" @@ -4916,7 +5196,7 @@ dependencies = [ "num_threads", "serde", "time-core", - "time-macros", + "time-macros 0.2.6", ] [[package]] @@ -4925,6 +5205,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +[[package]] +name = "time-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e9c6e26f12cb6d0dd7fc776bb67a706312e7299aed74c8dd5b17ebb27e2f1" +dependencies = [ + "proc-macro-hack", + "time-macros-impl", +] + [[package]] name = "time-macros" version = "0.2.6" @@ -4934,6 +5224,19 @@ dependencies = [ "time-core", ] +[[package]] +name = "time-macros-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "standback", + "syn", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -4998,12 +5301,12 @@ dependencies = [ [[package]] name = "tokio" -version = "1.21.2" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" dependencies = [ "autocfg", - "bytes 1.2.1", + "bytes 1.3.0", "libc", "memchr", "mio 0.8.5", @@ -5014,7 +5317,7 @@ dependencies = [ "socket2", "tokio-macros", "tracing", - "winapi 0.3.9", + "windows-sys 0.42.0", ] [[package]] @@ -5024,14 +5327,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" dependencies = [ "pin-project-lite 0.2.9", - "tokio 1.21.2", + "tokio 1.23.0", ] [[package]] name = "tokio-macros" -version = "1.8.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", @@ -5046,7 +5349,7 @@ checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite 0.2.9", - "tokio 1.21.2", + "tokio 1.23.0", ] [[package]] @@ -5055,35 +5358,35 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ - "bytes 1.2.1", + "bytes 1.3.0", "futures-core", "futures-io", "futures-sink", "pin-project-lite 0.2.9", - "tokio 1.21.2", + "tokio 1.23.0", "tracing", ] [[package]] name = "toml" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" dependencies = [ "serde", ] [[package]] name = "tonic" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55b9af819e54b8f33d453655bef9b9acc171568fb49523078d0cc4e7484200ec" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" dependencies = [ "async-stream", "async-trait", "axum", "base64 0.13.1", - "bytes 1.2.1", + "bytes 1.3.0", "futures-core", "futures-util", "h2", @@ -5095,7 +5398,7 @@ dependencies = [ "pin-project 1.0.12", "prost", "prost-derive", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-stream", "tokio-util", "tower", @@ -5107,9 +5410,9 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c6fd7c2581e36d63388a9e04c350c21beb7a8b059580b2e93993c526899ddc" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" dependencies = [ "prettyplease", "proc-macro2", @@ -5131,7 +5434,7 @@ dependencies = [ "pin-project-lite 0.2.9", "rand 0.8.5", "slab", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-util", "tower-layer", "tower-service", @@ -5140,12 +5443,12 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" dependencies = [ "bitflags", - "bytes 1.2.1", + "bytes 1.3.0", "futures-core", "futures-util", "http", @@ -5182,17 +5485,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-android" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12612be8f868a09c0ceae7113ff26afe79d81a24473a393cb9120ece162e86c0" -dependencies = [ - "android_log-sys", - "tracing", - "tracing-subscriber", -] - [[package]] name = "tracing-appender" version = "0.2.2" @@ -5281,6 +5573,22 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "tracing-oslog" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bc58223383423483e4bc056c7e7b3f77bdee924a9d33834112c69ead06dc847" +dependencies = [ + "bindgen 0.59.2", + "cc", + "cfg-if 1.0.0", + "fnv", + "once_cell", + "parking_lot 0.11.2", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "tracing-subscriber" version = "0.3.16" @@ -5330,7 +5638,7 @@ dependencies = [ "smallvec", "thiserror", "tinyvec", - "tokio 1.21.2", + "tokio 1.23.0", "tracing", "url", ] @@ -5350,7 +5658,7 @@ dependencies = [ "resolv-conf", "smallvec", "thiserror", - "tokio 1.21.2", + "tokio 1.23.0", "tracing", "trust-dns-proto", ] @@ -5375,25 +5683,25 @@ dependencies = [ "input_buffer", "log", "rand 0.7.3", - "sha-1 0.9.8", + "sha-1", "url", "utf-8", ] [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "30ee6ab729cd4cf0fd55218530c4522ed30b7b6081752839b68fcec8d0960788" dependencies = [ "base64 0.13.1", "byteorder", - "bytes 1.2.1", + "bytes 1.3.0", "http", "httparse", "log", "rand 0.8.5", - "sha-1 0.10.0", + "sha1 0.10.5", "thiserror", "url", "utf-8", @@ -5401,9 +5709,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" [[package]] name = "ucd-trie" @@ -5413,9 +5721,9 @@ checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" [[package]] name = "uint" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a45526d29728d135c2900b0d30573fe3ee79fceb12ef534c7bb30e810a91b601" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", "crunchy", @@ -5431,9 +5739,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" [[package]] name = "unicode-normalization" @@ -5474,15 +5782,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array", + "generic-array 0.14.6", "subtle", ] [[package]] name = "unsafe-libyaml" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" +checksum = "bc7ed8ba44ca06be78ea1ad2c3682a43349126c8818054231ee6f4748012aed2" [[package]] name = "untrusted" @@ -5520,7 +5828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" dependencies = [ "ctor", - "version_check", + "version_check 0.9.4", ] [[package]] @@ -5529,6 +5837,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "veilid-cli" version = "0.1.0" @@ -5540,7 +5854,7 @@ dependencies = [ "capnp-rpc", "capnpc", "cfg-if 1.0.0", - "clap", + "clap 3.2.23", "config", "crossbeam-channel", "cursive", @@ -5558,7 +5872,7 @@ dependencies = [ "serde_derive", "serial_test", "thiserror", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-util", "veilid-core", ] @@ -5572,7 +5886,7 @@ dependencies = [ "async-std", "async-std-resolver", "async-tls", - "async-tungstenite 0.18.0", + "async-tungstenite 0.19.0", "async_executors", "backtrace", "blake3", @@ -5595,7 +5909,7 @@ dependencies = [ "eyre", "flume", "futures-util", - "generic-array", + "generic-array 0.14.6", "getrandom 0.2.8", "hashlink 0.8.1", "hex", @@ -5612,26 +5926,24 @@ dependencies = [ "lazy_static", "libc", "maplit", - "ndk 0.6.0", + "ndk", "ndk-glue", - "nix 0.25.0", - "no-std-net", + "nix 0.26.1", "once_cell", "owning_ref", "owo-colors", + "paranoid-android", "parking_lot 0.12.1", "rand 0.7.3", "rkyv", "rtnetlink", "rusqlite", - "rust-fsm", "rustls", "rustls-pemfile", "secrecy", "send_wrapper 0.6.0", "serde", "serde-big-array", - "serde_bytes", "serde_json", "serial_test", "simplelog 0.12.0", @@ -5639,22 +5951,23 @@ dependencies = [ "static_assertions", "stop-token", "thiserror", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-stream", "tokio-util", "tracing", - "tracing-android", "tracing-error", + "tracing-oslog", "tracing-subscriber", "tracing-wasm", "trust-dns-resolver", + "veilid-tools", "wasm-bindgen", "wasm-bindgen-futures", "wasm-bindgen-test", "wasm-logger", "web-sys", "webpki 0.22.0", - "webpki-roots 0.22.5", + "webpki-roots 0.22.6", "wee_alloc", "winapi 0.3.9", "windows", @@ -5683,7 +5996,7 @@ dependencies = [ "parking_lot 0.12.1", "serde", "serde_json", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-stream", "tokio-util", "tracing", @@ -5698,14 +6011,14 @@ version = "0.1.0" dependencies = [ "ansi_term", "async-std", - "async-tungstenite 0.18.0", + "async-tungstenite 0.19.0", "backtrace", "bugsalot", "capnp", "capnp-rpc", "capnpc", "cfg-if 1.0.0", - "clap", + "clap 3.2.23", "color-eyre", "config", "console-subscriber", @@ -5717,12 +6030,12 @@ dependencies = [ "hostname", "json", "lazy_static", - "nix 0.25.0", + "nix 0.26.1", "opentelemetry", "opentelemetry-otlp", "opentelemetry-semantic-conventions", "parking_lot 0.12.1", - "rpassword 6.0.1", + "rpassword", "serde", "serde_derive", "serde_yaml", @@ -5730,7 +6043,7 @@ dependencies = [ "signal-hook", "signal-hook-async-std", "stop-token", - "tokio 1.21.2", + "tokio 1.23.0", "tokio-stream", "tokio-util", "tracing", @@ -5743,6 +6056,55 @@ dependencies = [ "windows-service", ] +[[package]] +name = "veilid-tools" +version = "0.1.0" +dependencies = [ + "android-logd-logger", + "async-lock", + "async-std", + "async_executors", + "backtrace", + "cfg-if 1.0.0", + "console_error_panic_hook", + "eyre", + "futures-util", + "jni", + "jni-sys", + "js-sys", + "lazy_static", + "libc", + "log", + "maplit", + "ndk", + "ndk-glue", + "nix 0.26.1", + "once_cell", + "oslog", + "owo-colors", + "paranoid-android", + "parking_lot 0.11.2", + "rand 0.7.3", + "rust-fsm", + "send_wrapper 0.6.0", + "serial_test", + "simplelog 0.12.0", + "static_assertions", + "stop-token", + "thiserror", + "tokio 1.23.0", + "tokio-util", + "tracing", + "tracing-oslog", + "tracing-subscriber", + "tracing-wasm", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "wasm-logger", + "wee_alloc", +] + [[package]] name = "veilid-wasm" version = "0.1.0" @@ -5767,6 +6129,12 @@ dependencies = [ "wee_alloc", ] +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "version_check" version = "0.9.4" @@ -5962,9 +6330,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.5" +version = "0.22.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" dependencies = [ "webpki 0.22.0", ] @@ -6229,9 +6597,9 @@ checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" [[package]] name = "winreg" -version = "0.7.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" +checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi 0.3.9", ] @@ -6256,7 +6624,7 @@ dependencies = [ "futures", "js-sys", "pharos", - "rustc_version", + "rustc_version 0.4.0", "send_wrapper 0.5.0", "thiserror", "wasm-bindgen", @@ -6266,9 +6634,9 @@ dependencies = [ [[package]] name = "wyz" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ "tap", ] @@ -6361,9 +6729,9 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.3.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" +checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 3ca2a512..27fc702e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ + "veilid-tools", "veilid-core", "veilid-server", "veilid-cli", @@ -8,7 +9,7 @@ members = [ "veilid-wasm", ] -exclude = [ "./external/keyring-rs", "./external/netlink", "./external/cursive", "./external/hashlink" ] +exclude = [ "./external/keyring-manager", "./external/netlink", "./external/cursive", "./external/hashlink" ] [patch.crates-io] cursive = { path = "./external/cursive/cursive" } diff --git a/Earthfile b/Earthfile index e8f17b66..a74ecafc 100644 --- a/Earthfile +++ b/Earthfile @@ -52,9 +52,9 @@ deps-android: FROM +deps-cross RUN apt-get install -y openjdk-9-jdk-headless RUN mkdir /Android; mkdir /Android/Sdk - RUN curl -o /Android/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-7583922_latest.zip + RUN curl -o /Android/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-9123335_latest.zip RUN cd /Android; unzip /Android/cmdline-tools.zip - RUN yes | /Android/cmdline-tools/bin/sdkmanager --sdk_root=/Android/Sdk build-tools\;30.0.3 ndk\;22.0.7026061 cmake\;3.18.1 platform-tools platforms\;android-30 + RUN yes | /Android/cmdline-tools/bin/sdkmanager --sdk_root=/Android/Sdk build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33 RUN apt-get clean # Just linux build not android @@ -65,13 +65,16 @@ deps-linux: # Code + Linux deps code-linux: FROM +deps-linux - COPY --dir .cargo external files scripts veilid-cli veilid-core veilid-server veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + COPY --dir .cargo external files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + RUN cat /veilid/scripts/earthly/cargo-linux/config.toml >> /veilid/.cargo/config.toml WORKDIR /veilid # Code + Linux + Android deps code-android: FROM +deps-android - COPY --dir .cargo external files scripts veilid-cli veilid-core veilid-server veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + COPY --dir .cargo external files scripts veilid-cli veilid-core veilid-server veilid-tools veilid-flutter veilid-wasm Cargo.lock Cargo.toml /veilid + RUN cat /veilid/scripts/earthly/cargo-linux/config.toml >> /veilid/.cargo/config.toml + RUN cat /veilid/scripts/earthly/cargo-android/config.toml >> /veilid/.cargo/config.toml WORKDIR /veilid # Clippy only @@ -93,7 +96,7 @@ build-linux-arm64: build-android: FROM +code-android WORKDIR /veilid/veilid-core - ENV PATH=$PATH:/Android/Sdk/ndk/22.0.7026061/toolchains/llvm/prebuilt/linux-x86_64/bin/ + ENV PATH=$PATH:/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin/ RUN cargo build --target aarch64-linux-android --release RUN cargo build --target armv7-linux-androideabi --release RUN cargo build --target i686-linux-android --release diff --git a/README.md b/README.md index e928750d..1dc9bd17 100644 --- a/README.md +++ b/README.md @@ -27,9 +27,11 @@ method is highly recommended as you may run into path problems with the 'flutter command line without it. If you do so, you may skip to [Run Veilid setup script](#Run Veilid setup script). -* build-tools;30.0.3 -* ndk;22.0.7026061 +* build-tools;33.0.1 +* ndk;25.1.8937393 * cmake;3.22.1 +* platform-tools +* platforms;android-33 #### Setup Dependencies using the CLI @@ -39,8 +41,10 @@ instructions for `sdkmanager` the command line to install the requisite package versions: ```shell -sdkmanager --install "build-tools;30.0.3" -sdkmanager --install "ndk;22.0.7026061" +sdkmanager --install "platform-tools" +sdkmanager --install "platforms;android-33" +sdkmanager --install "build-tools;33.0.1" +sdkmanager --install "ndk;25.1.8937393" sdkmanager --install "cmake;3.22.1" ``` @@ -50,7 +54,7 @@ your path. ```shell cat << EOF >> ~/.profile export ANDROID_SDK_ROOT= -export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/22.0.7026061 +export ANDROID_NDK_HOME=$ANDROID_SDK_ROOT/ndk/25.1.8937393 export PATH=\$PATH:$ANDROID_SDK_ROOT/platform-tools EOF ``` @@ -88,9 +92,10 @@ Development requires: You will need to use Android Studio [here](https://developer.android.com/studio) to maintain your Android dependencies. Use the SDK Manager in the IDE to install the following packages (use package details view to select version): -* Android SDK Build Tools (30.0.3) -* NDK (Side-by-side) (22.0.7026061) +* Android SDK Build Tools (33.0.1) +* NDK (Side-by-side) (25.1.8937393) * Cmake (3.22.1) +* Android SDK 33 * Android SDK Command Line Tools (latest) (7.0/latest) #### Setup command line environment @@ -101,7 +106,7 @@ your path. ```shell cat << EOF >> ~/.zshenv export ANDROID_SDK_ROOT=$HOME/Library/Android/sdk -export ANDROID_NDK_HOME=$HOME/Library/Android/sdk/ndk/22.0.7026061 +export ANDROID_NDK_HOME=$HOME/Library/Android/sdk/ndk/25.1.8937393 export PATH=\$PATH:$HOME/Library/Android/sdk/platform-tools EOF ``` diff --git a/doc/config/sample.config b/doc/config/sample.config index c7e65f0f..8c939d13 100644 --- a/doc/config/sample.config +++ b/doc/config/sample.config @@ -81,7 +81,6 @@ core: min_peer_refresh_time_ms: 2000 validate_dial_info_receipt_time_ms: 2000 upnp: true - natpmp: false detect_address_changes: true enable_local_peer_scope: false restricted_nat_retries: 0 diff --git a/doc/config/veilid-server-config.md b/doc/config/veilid-server-config.md index 83203e3b..8fbe74fe 100644 --- a/doc/config/veilid-server-config.md +++ b/doc/config/veilid-server-config.md @@ -193,7 +193,6 @@ network: bootstrap: ['bootstrap.dev.veilid.net'] bootstrap_nodes: [] upnp: true - natpmp: false detect_address_changes: true enable_local_peer_scope: false restricted_nat_retries: 0 diff --git a/external/keyring-manager b/external/keyring-manager index 1655f89c..b127b2d3 160000 --- a/external/keyring-manager +++ b/external/keyring-manager @@ -1 +1 @@ -Subproject commit 1655f89cf2ec70900c520080819d76ffad90adee +Subproject commit b127b2d3c653fea163a776dd58b3798f28aeeee3 diff --git a/external/keyvaluedb b/external/keyvaluedb index e30d0058..3408e0b2 160000 --- a/external/keyvaluedb +++ b/external/keyvaluedb @@ -1 +1 @@ -Subproject commit e30d0058defd9cfd7bd546bb177228edda8076ab +Subproject commit 3408e0b2ae3df0088e0714bc23fb33c82a58e22c diff --git a/external/no-std-net b/external/no-std-net deleted file mode 160000 index db4af788..00000000 --- a/external/no-std-net +++ /dev/null @@ -1 +0,0 @@ -Subproject commit db4af788049b5073567a36cb2e7b0445af66ab1c diff --git a/scripts/debug_main_node.sh b/scripts/deprecated/debug_main_node.sh similarity index 100% rename from scripts/debug_main_node.sh rename to scripts/deprecated/debug_main_node.sh diff --git a/scripts/debug_subnode_1.sh b/scripts/deprecated/debug_subnode_1.sh similarity index 100% rename from scripts/debug_subnode_1.sh rename to scripts/deprecated/debug_subnode_1.sh diff --git a/scripts/local-test.yml b/scripts/deprecated/local-test.yml similarity index 100% rename from scripts/local-test.yml rename to scripts/deprecated/local-test.yml diff --git a/scripts/run_2.sh b/scripts/deprecated/run_2.sh similarity index 100% rename from scripts/run_2.sh rename to scripts/deprecated/run_2.sh diff --git a/scripts/run_20.sh b/scripts/deprecated/run_20.sh similarity index 100% rename from scripts/run_20.sh rename to scripts/deprecated/run_20.sh diff --git a/scripts/run_3.sh b/scripts/deprecated/run_3.sh similarity index 100% rename from scripts/run_3.sh rename to scripts/deprecated/run_3.sh diff --git a/scripts/run_4.sh b/scripts/deprecated/run_4.sh similarity index 100% rename from scripts/run_4.sh rename to scripts/deprecated/run_4.sh diff --git a/scripts/run_8.sh b/scripts/deprecated/run_8.sh similarity index 100% rename from scripts/run_8.sh rename to scripts/deprecated/run_8.sh diff --git a/scripts/run_local_test.py b/scripts/deprecated/run_local_test.py similarity index 100% rename from scripts/run_local_test.py rename to scripts/deprecated/run_local_test.py diff --git a/scripts/earthly/cargo-android/config.toml b/scripts/earthly/cargo-android/config.toml new file mode 100644 index 00000000..e2552b9f --- /dev/null +++ b/scripts/earthly/cargo-android/config.toml @@ -0,0 +1,8 @@ +[target.aarch64-linux-android] +linker = "/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android33-clang" +[target.armv7-linux-androideabi] +linker = "/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi33-clang" +[target.x86_64-linux-android] +linker = "/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android33-clang" +[target.i686-linux-android] +linker = "/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android33-clang" \ No newline at end of file diff --git a/scripts/earthly/cargo-linux/config.toml b/scripts/earthly/cargo-linux/config.toml new file mode 100644 index 00000000..3c32d251 --- /dev/null +++ b/scripts/earthly/cargo-linux/config.toml @@ -0,0 +1,2 @@ +[target.aarch64-unknown-linux-gnu] +linker = "aarch64-linux-gnu-gcc" diff --git a/scripts/ios_build.sh b/scripts/ios_build.sh new file mode 100755 index 00000000..51307e68 --- /dev/null +++ b/scripts/ios_build.sh @@ -0,0 +1,73 @@ +#!/bin/bash +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +pushd $SCRIPTDIR >/dev/null + +CARGO=`which cargo` +CARGO=${CARGO:=~/.cargo/bin/cargo} +CARGO_DIR=$(dirname $CARGO) + +CARGO_MANIFEST_PATH=$(python3 -c "import os; import json; print(json.loads(os.popen('$CARGO locate-project').read())['root'])") +CARGO_WORKSPACE_PATH=$(python3 -c "import os; import json; print(json.loads(os.popen('$CARGO locate-project --workspace').read())['root'])") +TARGET_PATH=$(python3 -c "import os; print(os.path.realpath(\"$CARGO_WORKSPACE_PATH/../target\"))") +PACKAGE_NAME=$1 +shift + +if [ "$CONFIGURATION" == "Debug" ]; then + EXTRA_CARGO_OPTIONS="$@" + BUILD_MODE="debug" +else + EXTRA_CARGO_OPTIONS="$@ --release" + BUILD_MODE="release" +fi +ARCHS=${ARCHS:=arm64} + +if [ "$PLATFORM_NAME" == "iphonesimulator" ]; then + LIPO_OUT_NAME="lipo-ios-sim" +else + LIPO_OUT_NAME="lipo-ios" +fi + +for arch in $ARCHS +do + if [ "$arch" == "arm64" ]; then + echo arm64 + if [ "$PLATFORM_NAME" == "iphonesimulator" ]; then + CARGO_TARGET=aarch64-apple-ios-sim + else + CARGO_TARGET=aarch64-apple-ios + fi + CARGO_TOOLCHAIN= + elif [ "$arch" == "x86_64" ]; then + echo x86_64 + CARGO_TARGET=x86_64-apple-ios + CARGO_TOOLCHAIN= + else + echo Unsupported ARCH: $arch + continue + fi + + + + # Choose arm64 brew for unit tests by default if we are on M1 + if [ -f /opt/homebrew/bin/brew ]; then + HOMEBREW_DIR=/opt/homebrew/bin + elif [ -f /usr/local/bin/brew ]; then + HOMEBREW_DIR=/usr/local/bin + else + HOMEBREW_DIR=$(dirname `which brew`) + fi + + env -i PATH=/usr/bin:/bin:$HOMEBREW_DIR:$CARGO_DIR HOME="$HOME" USER="$USER" cargo $CARGO_TOOLCHAIN build $EXTRA_CARGO_OPTIONS --target $CARGO_TARGET --manifest-path $CARGO_MANIFEST_PATH + + LIPOS="$LIPOS $TARGET_PATH/$CARGO_TARGET/$BUILD_MODE/lib$PACKAGE_NAME.a" + +done + +# Make lipo build +mkdir -p "$TARGET_PATH/$LIPO_OUT_NAME/$BUILD_MODE/" +lipo $LIPOS -create -output "$TARGET_PATH/$LIPO_OUT_NAME/$BUILD_MODE/lib$PACKAGE_NAME.a" + +# Make most recent dylib available without build mode for flutter +cp "$TARGET_PATH/$LIPO_OUT_NAME/$BUILD_MODE/lib$PACKAGE_NAME.a" "$TARGET_PATH/$LIPO_OUT_NAME/lib$PACKAGE_NAME.a" + +popd >/dev/null \ No newline at end of file diff --git a/scripts/macos_build.sh b/scripts/macos_build.sh new file mode 100755 index 00000000..88fc470c --- /dev/null +++ b/scripts/macos_build.sh @@ -0,0 +1,63 @@ +#!/bin/bash +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +pushd $SCRIPTDIR >/dev/null + +CARGO=`which cargo` +CARGO=${CARGO:=~/.cargo/bin/cargo} +CARGO_DIR=$(dirname $CARGO) + +CARGO_MANIFEST_PATH=$(python3 -c "import os; import json; print(json.loads(os.popen('$CARGO locate-project').read())['root'])") +CARGO_WORKSPACE_PATH=$(python3 -c "import os; import json; print(json.loads(os.popen('$CARGO locate-project --workspace').read())['root'])") +TARGET_PATH=$(python3 -c "import os; print(os.path.realpath(\"$CARGO_WORKSPACE_PATH/../target\"))") +PACKAGE_NAME=$1 +shift + +if [ "$CONFIGURATION" == "Debug" ]; then + EXTRA_CARGO_OPTIONS="$@" + BUILD_MODE="debug" +else + EXTRA_CARGO_OPTIONS="$@ --release" + BUILD_MODE="release" +fi +ARCHS=${ARCHS:=arm64} + +LIPO_OUT_NAME="lipo-darwin" + +for arch in $ARCHS +do + if [ "$arch" == "arm64" ]; then + echo arm64 + CARGO_TARGET=aarch64-apple-darwin + CARGO_TOOLCHAIN= + elif [ "$arch" == "x86_64" ]; then + echo x86_64 + CARGO_TARGET=x86_64-apple-darwin + CARGO_TOOLCHAIN= + else + echo Unsupported ARCH: $arch + continue + fi + + # Choose arm64 brew for unit tests by default if we are on M1 + if [ -f /opt/homebrew/bin/brew ]; then + HOMEBREW_DIR=/opt/homebrew/bin + elif [ -f /usr/local/bin/brew ]; then + HOMEBREW_DIR=/usr/local/bin + else + HOMEBREW_DIR=$(dirname `which brew`) + fi + + env -i PATH=/usr/bin:/bin:$HOMEBREW_DIR:$CARGO_DIR HOME="$HOME" USER="$USER" cargo $CARGO_TOOLCHAIN build $EXTRA_CARGO_OPTIONS --target $CARGO_TARGET --manifest-path $CARGO_MANIFEST_PATH + + LIPOS="$LIPOS $TARGET_PATH/$CARGO_TARGET/$BUILD_MODE/lib$PACKAGE_NAME.dylib" + +done + +# Make lipo build +mkdir -p "$TARGET_PATH/$LIPO_OUT_NAME/$BUILD_MODE/" +lipo $LIPOS -create -output "$TARGET_PATH/$LIPO_OUT_NAME/$BUILD_MODE/lib$PACKAGE_NAME.dylib" + +# Make most recent dylib available without build mode for flutter +cp "$TARGET_PATH/$LIPO_OUT_NAME/$BUILD_MODE/lib$PACKAGE_NAME.dylib" "$TARGET_PATH/$LIPO_OUT_NAME/lib$PACKAGE_NAME.dylib" + +popd > /dev/null \ No newline at end of file diff --git a/scripts/new_android_sim.sh b/scripts/new_android_sim.sh new file mode 100755 index 00000000..eac100d0 --- /dev/null +++ b/scripts/new_android_sim.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +UNAME_M=`uname -m` +if [[ "$UNAME_M" == "arm64" ]]; then + ANDROID_ABI=arm64-v8a +elif [[ "$UNAME_M" == "x86_64" ]]; then + ANDROID_ABI=x86 +else + echo "Unknown platform" + exit 1 +fi +AVD_NAME="testavd" +AVD_TAG="google_atd" +AVD_IMAGE="system-images;android-30;$AVD_TAG;$ANDROID_ABI" +AVD_DEVICE="Nexus 10" +# Install AVD image +$ANDROID_SDK_ROOT/tools/bin/sdkmanager --install "$AVD_IMAGE" +# Make AVD +echo "no" | $ANDROID_SDK_ROOT/tools/bin/avdmanager --verbose create avd --force --name "$AVD_NAME" --package "$AVD_IMAGE" --tag "$AVD_TAG" --abi "$ANDROID_ABI" --device "$AVD_DEVICE" +# Run emulator +$ANDROID_SDK_ROOT/emulator/emulator -avd testavd -no-snapshot -no-boot-anim -no-window & +( trap exit SIGINT ; read -r -d '' _ /dev/null) +xcrun simctl boot $ID +xcrun simctl bootstatus $ID +echo Simulator ID is $ID +( trap exit SIGINT ; read -r -d '' _ /dev/null 2>&1 && pwd )" +if [[ "$(uname)" != "Linux" ]]; then + echo Not running Linux + exit 1 +fi + if [ "$(lsb_release -d | grep -qEi 'debian|buntu|mint')" ]; then echo Not a supported Linux exit 1 @@ -14,6 +21,14 @@ else exit 1 fi +# ensure Android Command Line Tools exist +if [ -d "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" ]; then + echo '[X] Android command line tools are installed' +else + echo 'Android command line tools are not installed' + exit 1 +fi + # ensure ANDROID_NDK_HOME is defined and exists if [ -d "$ANDROID_NDK_HOME" ]; then echo '[X] $ANDROID_NDK_HOME is defined and exists' @@ -79,6 +94,9 @@ cargo install wasm-bindgen-cli wasm-pack # Ensure packages are installed sudo apt-get install libc6-dev-i386 libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 openjdk-11-jdk llvm wabt checkinstall +# Ensure android sdk packages are installed +$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33 + # Install capnproto using the same mechanism as our earthly build $SCRIPTDIR/scripts/earthly/install_capnproto.sh # Install protoc using the same mechanism as our earthly build diff --git a/setup_macos.sh b/setup_macos.sh index 91d3c4e9..f5045efd 100755 --- a/setup_macos.sh +++ b/setup_macos.sh @@ -1,4 +1,6 @@ #!/bin/bash +set -eo pipefail + SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" if [ ! "$(uname)" == "Darwin" ]; then @@ -14,6 +16,14 @@ else exit 1 fi +# ensure Android Command Line Tools exist +if [ -d "$ANDROID_SDK_ROOT/cmdline-tools/latest/bin" ]; then + echo '[X] Android command line tools are installed' +else + echo 'Android command line tools are not installed' + exit 1 +fi + # ensure ANDROID_NDK_HOME is defined and exists if [ -d "$ANDROID_NDK_HOME" ]; then echo '[X] $ANDROID_NDK_HOME is defined and exists' @@ -86,6 +96,10 @@ else exit 1 fi + +# Ensure android sdk packages are installed +$ANDROID_SDK_ROOT/cmdline-tools/latest/bin/sdkmanager build-tools\;33.0.1 ndk\;25.1.8937393 cmake\;3.22.1 platform-tools platforms\;android-33 + # install targets rustup target add aarch64-apple-darwin aarch64-apple-ios x86_64-apple-darwin x86_64-apple-ios wasm32-unknown-unknown aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android @@ -108,5 +122,5 @@ if [ "$BREW_USER" == "" ]; then BREW_USER=`whoami` fi fi -sudo -H -u $BREW_USER brew install capnp cmake wabt llvm protobuf - +sudo -H -u $BREW_USER brew install capnp cmake wabt llvm protobuf openjdk@11 +sudo gem install cocoapods diff --git a/setup_windows.bat b/setup_windows.bat new file mode 100644 index 00000000..c502034b --- /dev/null +++ b/setup_windows.bat @@ -0,0 +1,38 @@ +@echo off +setlocal + +REM ############################################# + +PUSHD %~dp0 +SET ROOTDIR=%CD% +POPD + +IF NOT DEFINED ProgramFiles(x86) ( + echo This script requires a 64-bit Windows Installation. Exiting. + goto end +) + +FOR %%X IN (protoc.exe) DO (SET PROTOC_FOUND=%%~$PATH:X) +IF NOT DEFINED PROTOC_FOUND ( + echo protobuf compiler ^(protoc^) is required but it's not installed. Install protoc 21.10 or higher. Ensure it is in your path. Aborting. + echo protoc is available here: https://github.com/protocolbuffers/protobuf/releases/download/v21.10/protoc-21.10-win64.zip + goto end +) + +FOR %%X IN (capnp.exe) DO (SET CAPNP_FOUND=%%~$PATH:X) +IF NOT DEFINED CAPNP_FOUND ( + echo capnproto compiler ^(capnp^) is required but it's not installed. Install capnp 0.10.3 or higher. Ensure it is in your path. Aborting. + echo capnp is available here: https://capnproto.org/capnproto-c++-win32-0.10.3.zip + goto end +) + +FOR %%X IN (cargo.exe) DO (SET CARGO_FOUND=%%~$PATH:X) +IF NOT DEFINED CARGO_FOUND ( + echo rust ^(cargo^) is required but it's not installed. Install rust 1.65 or higher. Ensure it is in your path. Aborting. + echo install rust via rustup here: https://static.rust-lang.org/rustup/dist/x86_64-pc-windows-msvc/rustup-init.exe + goto ends +) + +echo Setup successful +:end +ENDLOCAL diff --git a/veilid-cli/Cargo.toml b/veilid-cli/Cargo.toml index fa613fde..92fccf4e 100644 --- a/veilid-cli/Cargo.toml +++ b/veilid-cli/Cargo.toml @@ -16,7 +16,7 @@ rt-async-std = [ "async-std", "veilid-core/rt-async-std", "cursive/rt-async-std" rt-tokio = [ "tokio", "tokio-util", "veilid-core/rt-tokio", "cursive/rt-tokio" ] [dependencies] -cursive = { path = "../external/cursive/cursive", default-features = false, features = [ "crossterm", "toml"]} +cursive = { path = "../external/cursive/cursive", default-features = false, features = [ "crossterm", "toml", "ansi" ]} async-std = { version = "^1.9", features = ["unstable", "attributes"], optional = true } tokio = { version = "^1", features = ["full"], optional = true } tokio-util = { version = "^0", features = ["compat"], optional = true} diff --git a/veilid-cli/src/client_api_connection.rs b/veilid-cli/src/client_api_connection.rs index 03b8343c..5999ab22 100644 --- a/veilid-cli/src/client_api_connection.rs +++ b/veilid-cli/src/client_api_connection.rs @@ -8,7 +8,7 @@ use serde::de::DeserializeOwned; use std::cell::RefCell; use std::net::SocketAddr; use std::rc::Rc; -use veilid_core::xx::*; +use veilid_core::tools::*; use veilid_core::*; macro_rules! capnp_failed { @@ -92,6 +92,9 @@ impl veilid_client::Server for VeilidClientImpl { VeilidUpdate::Config(config) => { self.comproc.update_config(config); } + VeilidUpdate::Route(route) => { + self.comproc.update_route(route); + } VeilidUpdate::Shutdown => self.comproc.update_shutdown(), } @@ -226,8 +229,8 @@ impl ClientApiConnection { // Wait until rpc system completion or disconnect was requested let res = rpc_jh.await; - #[cfg(feature = "rt-tokio")] - let res = res.map_err(|e| format!("join error: {}", e))?; + // #[cfg(feature = "rt-tokio")] + // let res = res.map_err(|e| format!("join error: {}", e))?; res.map_err(|e| format!("client RPC system error: {}", e)) } @@ -441,7 +444,11 @@ impl ClientApiConnection { res.map_err(map_to_string) } - pub async fn server_appcall_reply(&mut self, id: u64, msg: Vec) -> Result<(), String> { + pub async fn server_appcall_reply( + &mut self, + id: OperationId, + msg: Vec, + ) -> Result<(), String> { trace!("ClientApiConnection::appcall_reply"); let server = { let inner = self.inner.borrow(); @@ -452,7 +459,7 @@ impl ClientApiConnection { .clone() }; let mut request = server.borrow().app_call_reply_request(); - request.get().set_id(id); + request.get().set_id(id.as_u64()); request.get().set_message(&msg); let response = self .cancellable(request.send().promise) diff --git a/veilid-cli/src/command_processor.rs b/veilid-cli/src/command_processor.rs index e2457d77..8027eb92 100644 --- a/veilid-cli/src/command_processor.rs +++ b/veilid-cli/src/command_processor.rs @@ -1,13 +1,11 @@ use crate::client_api_connection::*; use crate::settings::Settings; -use crate::tools::*; use crate::ui::*; -use log::*; use std::cell::*; use std::net::SocketAddr; use std::rc::Rc; -use std::time::{Duration, SystemTime}; -use veilid_core::xx::{Eventual, EventualCommon}; +use std::time::SystemTime; +use veilid_core::tools::*; use veilid_core::*; pub fn convert_loglevel(s: &str) -> Result { @@ -49,7 +47,7 @@ struct CommandProcessorInner { autoreconnect: bool, server_addr: Option, connection_waker: Eventual, - last_call_id: Option, + last_call_id: Option, } type Handle = Rc>; @@ -251,7 +249,7 @@ reply - reply to an AppCall not handled directly by the server } Ok(v) => v, }; - (id, second) + (OperationId::new(id), second) } else { let id = match some_last_id { None => { @@ -366,7 +364,7 @@ reply - reply to an AppCall not handled directly by the server debug!("Connection lost, retrying in 2 seconds"); { let waker = self.inner_mut().connection_waker.instance_clone(()); - let _ = timeout(Duration::from_millis(2000), waker).await; + let _ = timeout(2000, waker).await; } self.inner_mut().connection_waker.reset(); first = false; @@ -390,20 +388,42 @@ reply - reply to an AppCall not handled directly by the server //////////////////////////////////////////// pub fn update_attachment(&mut self, attachment: veilid_core::VeilidStateAttachment) { - self.inner_mut().ui.set_attachment_state(attachment.state); + self.inner_mut().ui.set_attachment_state( + attachment.state, + attachment.public_internet_ready, + attachment.local_network_ready, + ); } pub fn update_network_status(&mut self, network: veilid_core::VeilidStateNetwork) { self.inner_mut().ui.set_network_status( network.started, - network.bps_down, - network.bps_up, + network.bps_down.as_u64(), + network.bps_up.as_u64(), network.peers, ); } pub fn update_config(&mut self, config: veilid_core::VeilidStateConfig) { self.inner_mut().ui.set_config(config.config) } + pub fn update_route(&mut self, route: veilid_core::VeilidStateRoute) { + let mut out = String::new(); + if !route.dead_routes.is_empty() { + out.push_str(&format!("Dead routes: {:?}", route.dead_routes)); + } + if !route.dead_remote_routes.is_empty() { + if !out.is_empty() { + out.push_str("\n"); + } + out.push_str(&format!( + "Dead remote routes: {:?}", + route.dead_remote_routes + )); + } + if !out.is_empty() { + self.inner().ui.add_node_event(out); + } + } pub fn update_log(&mut self, log: veilid_core::VeilidLog) { self.inner().ui.add_node_event(format!( @@ -455,7 +475,9 @@ reply - reply to an AppCall not handled directly by the server self.inner().ui.add_node_event(format!( "AppCall ({:?}) id = {:016x} : {}", - call.sender, call.id, strmsg + call.sender, + call.id.as_u64(), + strmsg )); self.inner_mut().last_call_id = Some(call.id); diff --git a/veilid-cli/src/main.rs b/veilid-cli/src/main.rs index 3e2b2b2a..9239478f 100644 --- a/veilid-cli/src/main.rs +++ b/veilid-cli/src/main.rs @@ -1,14 +1,15 @@ #![deny(clippy::all)] #![deny(unused_must_use)] +#![recursion_limit = "256"] -use veilid_core::xx::*; +use crate::tools::*; +use veilid_core::tools::*; use clap::{Arg, ColorChoice, Command}; use flexi_logger::*; use std::ffi::OsStr; use std::net::ToSocketAddrs; use std::path::Path; -use tools::*; mod client_api_connection; mod command_processor; diff --git a/veilid-cli/src/peers_table_view.rs b/veilid-cli/src/peers_table_view.rs index 1ba95dd3..870eaea5 100644 --- a/veilid-cli/src/peers_table_view.rs +++ b/veilid-cli/src/peers_table_view.rs @@ -1,7 +1,7 @@ use super::*; use cursive_table_view::*; use std::cmp::Ordering; -use veilid_core::PeerTableData; +use veilid_core::*; #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum PeerTableColumn { @@ -24,7 +24,8 @@ pub enum PeerTableColumn { // } // } -fn format_ts(ts: u64) -> String { +fn format_ts(ts: Timestamp) -> String { + let ts = ts.as_u64(); let secs = timestamp_to_secs(ts); if secs >= 1.0 { format!("{:.2}s", timestamp_to_secs(ts)) @@ -33,7 +34,8 @@ fn format_ts(ts: u64) -> String { } } -fn format_bps(bps: u64) -> String { +fn format_bps(bps: ByteCount) -> String { + let bps = bps.as_u64(); if bps >= 1024u64 * 1024u64 * 1024u64 { format!("{:.2}GB/s", (bps / (1024u64 * 1024u64)) as f64 / 1024.0) } else if bps >= 1024u64 * 1024u64 { diff --git a/veilid-cli/src/tools.rs b/veilid-cli/src/tools.rs index 3ec094d2..bf58d24c 100644 --- a/veilid-cli/src/tools.rs +++ b/veilid-cli/src/tools.rs @@ -3,32 +3,12 @@ use core::future::Future; cfg_if! { if #[cfg(feature="rt-async-std")] { - pub use async_std::task::JoinHandle; pub use async_std::net::TcpStream; - pub use async_std::future::TimeoutError; - pub fn spawn_local + 'static, T: 'static>(f: F) -> JoinHandle { - async_std::task::spawn_local(f) - } - pub fn spawn_detached_local + 'static, T: 'static>(f: F) { - let _ = async_std::task::spawn_local(f); - } - pub use async_std::task::sleep; - pub use async_std::future::timeout; pub fn block_on, T>(f: F) -> T { async_std::task::block_on(f) } } else if #[cfg(feature="rt-tokio")] { - pub use tokio::task::JoinHandle; pub use tokio::net::TcpStream; - pub use tokio::time::error::Elapsed as TimeoutError; - pub fn spawn_local + 'static, T: 'static>(f: F) -> JoinHandle { - tokio::task::spawn_local(f) - } - pub fn spawn_detached_local + 'static, T: 'static>(f: F) { - let _ = tokio::task::spawn_local(f); - } - pub use tokio::time::sleep; - pub use tokio::time::timeout; pub fn block_on, T>(f: F) -> T { let rt = tokio::runtime::Runtime::new().unwrap(); let local = tokio::task::LocalSet::new(); diff --git a/veilid-cli/src/ui.rs b/veilid-cli/src/ui.rs index 9f127056..71b67a7f 100644 --- a/veilid-cli/src/ui.rs +++ b/veilid-cli/src/ui.rs @@ -51,6 +51,8 @@ pub type UICallback = Box; struct UIState { attachment_state: Dirty, + public_internet_ready: Dirty, + local_network_ready: Dirty, network_started: Dirty, network_down_up: Dirty<(f32, f32)>, connection_state: Dirty, @@ -62,6 +64,8 @@ impl UIState { pub fn new() -> Self { Self { attachment_state: Dirty::new(AttachmentState::Detached), + public_internet_ready: Dirty::new(false), + local_network_ready: Dirty::new(false), network_started: Dirty::new(false), network_down_up: Dirty::new((0.0, 0.0)), connection_state: Dirty::new(ConnectionState::Disconnected), @@ -234,17 +238,28 @@ impl UI { fn peers(s: &mut Cursive) -> ViewRef { s.find_name("peers").unwrap() } - fn render_attachment_state<'a>(inner: &mut UIInner) -> &'a str { - match inner.ui_state.attachment_state.get() { - AttachmentState::Detached => " Detached [----]", - AttachmentState::Attaching => "Attaching [/ ]", - AttachmentState::AttachedWeak => " Attached [| ]", - AttachmentState::AttachedGood => " Attached [|| ]", - AttachmentState::AttachedStrong => " Attached [||| ]", - AttachmentState::FullyAttached => " Attached [||||]", - AttachmentState::OverAttached => " Attached [++++]", - AttachmentState::Detaching => "Detaching [////]", - } + fn render_attachment_state(inner: &mut UIInner) -> String { + let att = match inner.ui_state.attachment_state.get() { + AttachmentState::Detached => "[----]", + AttachmentState::Attaching => "[/ ]", + AttachmentState::AttachedWeak => "[| ]", + AttachmentState::AttachedGood => "[|| ]", + AttachmentState::AttachedStrong => "[||| ]", + AttachmentState::FullyAttached => "[||||]", + AttachmentState::OverAttached => "[++++]", + AttachmentState::Detaching => "[////]", + }; + let pi = if *inner.ui_state.public_internet_ready.get() { + "+P" + } else { + "-p" + }; + let ln = if *inner.ui_state.local_network_ready.get() { + "+L" + } else { + "-l" + }; + format!("{}{}{}", att, pi, ln) } fn render_network_status(inner: &mut UIInner) -> String { match inner.ui_state.network_started.get() { @@ -344,16 +359,10 @@ impl UI { Ok(_) => {} Err(e) => { let color = *Self::inner_mut(s).log_colors.get(&Level::Error).unwrap(); - - cursive_flexi_logger_view::push_to_log(StyledString::styled( - format!("> {}", text), - color, - )); cursive_flexi_logger_view::push_to_log(StyledString::styled( format!(" Error: {}", e), color, )); - return; } } // save to history unless it's a duplicate @@ -838,9 +847,20 @@ impl UI { inner.cmdproc = Some(cmdproc); let _ = inner.cb_sink.send(Box::new(UI::update_cb)); } - pub fn set_attachment_state(&mut self, state: AttachmentState) { + pub fn set_attachment_state( + &mut self, + state: AttachmentState, + public_internet_ready: bool, + local_network_ready: bool, + ) { let mut inner = self.inner.borrow_mut(); inner.ui_state.attachment_state.set(state); + inner + .ui_state + .public_internet_ready + .set(public_internet_ready); + inner.ui_state.local_network_ready.set(local_network_ready); + let _ = inner.cb_sink.send(Box::new(UI::update_cb)); } pub fn set_network_status( @@ -878,8 +898,12 @@ impl UI { pub fn add_node_event(&self, event: String) { let inner = self.inner.borrow(); let color = *inner.log_colors.get(&Level::Info).unwrap(); + let mut starting_style: Style = color.into(); for line in event.lines() { - cursive_flexi_logger_view::push_to_log(StyledString::styled(line, color)); + let (spanned_string, end_style) = + cursive::utils::markup::ansi::parse_with_starting_style(starting_style, line); + cursive_flexi_logger_view::push_to_log(spanned_string); + starting_style = end_style; } let _ = inner.cb_sink.send(Box::new(UI::update_cb)); } diff --git a/veilid-core/Cargo.toml b/veilid-core/Cargo.toml index e0ec79c8..78fccd45 100644 --- a/veilid-core/Cargo.toml +++ b/veilid-core/Cargo.toml @@ -11,20 +11,20 @@ crate-type = ["cdylib", "staticlib", "rlib"] [features] default = [] -rt-async-std = [ "async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket" ] -rt-tokio = [ "tokio", "tokio-util", "tokio-stream", "trust-dns-resolver/tokio-runtime", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", "rtnetlink?/tokio_socket" ] +rt-async-std = [ "async-std", "async-std-resolver", "async_executors/async_std", "rtnetlink?/smol_socket", "veilid-tools/rt-async-std" ] +rt-tokio = [ "tokio", "tokio-util", "tokio-stream", "trust-dns-resolver/tokio-runtime", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", "rtnetlink?/tokio_socket", "veilid-tools/rt-tokio" ] -android_tests = [] -ios_tests = [ "simplelog" ] +veilid_core_android_tests = [ "dep:paranoid-android" ] +veilid_core_ios_tests = [ "dep:tracing-oslog" ] tracking = [] [dependencies] +veilid-tools = { path = "../veilid-tools", features = [ "tracing" ] } tracing = { version = "^0", features = ["log", "attributes"] } tracing-subscriber = "^0" tracing-error = "^0" eyre = "^0" capnp = { version = "^0", default_features = false } -rust-fsm = "^0" static_assertions = "^1" cfg-if = "^1" thiserror = "^1" @@ -34,6 +34,8 @@ secrecy = "^0" chacha20poly1305 = "^0" chacha20 = "^0" hashlink = { path = "../external/hashlink", features = ["serde_impl"] } +serde = { version = "^1", features = ["derive" ] } +serde_json = { version = "^1" } serde-big-array = "^0" futures-util = { version = "^0", default_features = false, features = ["alloc"] } parking_lot = "^0" @@ -59,10 +61,10 @@ rtnetlink = { version = "^0", default-features = false, optional = true } async-std-resolver = { version = "^0", optional = true } trust-dns-resolver = { version = "^0", optional = true } keyvaluedb = { path = "../external/keyvaluedb/keyvaluedb" } -serde_bytes = { version = "^0" } -#rkyv = { version = "^0", default_features = false, features = ["std", "alloc", "strict", "size_64", "validation"] } -rkyv = { git = "https://github.com/crioux/rkyv.git", branch = "issue_326", default_features = false, features = ["std", "alloc", "strict", "size_64", "validation"] } +#rkyv = { version = "^0", default_features = false, features = ["std", "alloc", "strict", "size_32", "validation"] } +rkyv = { git = "https://github.com/rkyv/rkyv.git", rev = "57e2a8d", default_features = false, features = ["std", "alloc", "strict", "size_32", "validation"] } bytecheck = "^0" +data-encoding = { version = "^2" } # Dependencies for native builds only # Linux, Windows, Mac, iOS, Android @@ -84,9 +86,6 @@ rustls = "^0.19" rustls-pemfile = "^0.2" futures-util = { version = "^0", default-features = false, features = ["async-await", "sink", "std", "io"] } keyvaluedb-sqlite = { path = "../external/keyvaluedb/keyvaluedb-sqlite" } -data-encoding = { version = "^2" } -serde = { version = "^1", features = ["derive" ] } -serde_json = { version = "^1" } socket2 = "^0" bugsalot = "^0" chrono = "^0" @@ -98,11 +97,7 @@ nix = "^0" wasm-bindgen = "^0" js-sys = "^0" wasm-bindgen-futures = "^0" -no-std-net = { path = "../external/no-std-net", features = ["serde"] } keyvaluedb-web = { path = "../external/keyvaluedb/keyvaluedb-web" } -data-encoding = { version = "^2", default_features = false, features = ["alloc"] } -serde = { version = "^1", default-features = false, features = ["derive", "alloc"] } -serde_json = { version = "^1", default-features = false, features = ["alloc"] } getrandom = { version = "^0", features = ["js"] } ws_stream_wasm = "^0" async_executors = { version = "^0", default-features = false, features = [ "bindgen", "timer" ]} @@ -131,9 +126,9 @@ features = [ [target.'cfg(target_os = "android")'.dependencies] jni = "^0" jni-sys = "^0" -ndk = { version = "^0", features = ["trace"] } -ndk-glue = { version = "^0", features = ["logger"] } -tracing-android = { version = "^0" } +ndk = { version = "^0.7" } +ndk-glue = { version = "^0.7", features = ["logger"] } +paranoid-android = { version = "^0", optional = true } # Dependenices for all Unix (Linux, Android, MacOS, iOS) [target.'cfg(unix)'.dependencies] @@ -151,7 +146,7 @@ windows-permissions = "^0" # Dependencies for iOS [target.'cfg(target_os = "ios")'.dependencies] -simplelog = { version = "^0", optional = true } +tracing-oslog = { version = "^0", optional = true } # Rusqlite configuration to ensure platforms that don't come with sqlite get it bundled # Except WASM which doesn't use sqlite diff --git a/veilid-core/ios_build.sh b/veilid-core/ios_build.sh deleted file mode 100755 index 4eb08eca..00000000 --- a/veilid-core/ios_build.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/bash - -SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -CARGO_MANIFEST_PATH=$(python -c "import os; print(os.path.realpath(\"$SCRIPTDIR/Cargo.toml\"))") -# echo CARGO_MANIFEST_PATH: $CARGO_MANIFEST_PATH - -if [ "$CONFIGURATION" == "Debug" ]; then - EXTRA_CARGO_OPTIONS="$@" -else - EXTRA_CARGO_OPTIONS="$@ --release" -fi -ARCHS=${ARCHS:=arm64} -for arch in $ARCHS -do - if [ "$arch" == "arm64" ]; then - echo arm64 - CARGO_TARGET=aarch64-apple-ios - #CARGO_TOOLCHAIN=+ios-arm64-1.57.0 - CARGO_TOOLCHAIN= - elif [ "$arch" == "x86_64" ]; then - echo x86_64 - CARGO_TARGET=x86_64-apple-ios - CARGO_TOOLCHAIN= - else - echo Unsupported ARCH: $arch - continue - fi - - CARGO=`which cargo` - CARGO=${CARGO:=~/.cargo/bin/cargo} - CARGO_DIR=$(dirname $CARGO) - - # Choose arm64 brew for unit tests by default if we are on M1 - if [ -f /opt/homebrew/bin/brew ]; then - HOMEBREW_DIR=/opt/homebrew/bin - elif [ -f /usr/local/bin/brew ]; then - HOMEBREW_DIR=/usr/local/bin - else - HOMEBREW_DIR=$(dirname `which brew`) - fi - - env -i PATH=/usr/bin:/bin:$HOMEBREW_DIR:$CARGO_DIR HOME="$HOME" USER="$USER" cargo $CARGO_TOOLCHAIN build $EXTRA_CARGO_OPTIONS --target $CARGO_TARGET --manifest-path $CARGO_MANIFEST_PATH -done - diff --git a/veilid-core/proto/veilid.capnp b/veilid-core/proto/veilid.capnp index 05bd4de7..f38acab9 100644 --- a/veilid-core/proto/veilid.capnp +++ b/veilid-core/proto/veilid.capnp @@ -156,17 +156,12 @@ using ValueSeqNum = UInt32; # sequence numbers for v struct ValueKey @0xe64b0992c21a0736 { publicKey @0 :ValueID; # the location of the value - subkey @1 :Text; # the name of the subkey (or empty if the whole key) + subkey @1 :Text; # the name of the subkey (or empty for the default subkey) } -# struct ValueKeySeq { -# key @0 :ValueKey; # the location of the value -# seq @1 :ValueSeqNum; # the sequence number of the value subkey -# } - struct ValueData @0xb4b7416f169f2a3d { - data @0 :Data; # value or subvalue contents - seq @1 :ValueSeqNum; # sequence number of value + seq @0 :ValueSeqNum; # sequence number of value + data @1 :Data; # value or subvalue contents } # Operations @@ -188,6 +183,12 @@ enum DialInfoClass @0x880005edfdd38b1e { portRestrictedNAT @5; # P = Device without portmap behind address-and-port restricted NAT } +enum Sequencing @0xb6735890f7818a1c { + noPreference @0; + preferOrdered @1; + ensureOrdered @2; +} + struct DialInfoDetail @0x96423aa1d67b74d8 { dialInfo @0 :DialInfo; class @1 :DialInfoClass; @@ -266,9 +267,10 @@ struct PeerInfo @0xfe2d722d5d3c4bcb { struct RoutedOperation @0xcbcb8535b839e9dd { version @0 :UInt8; # crypto version in use for the data - signatures @1 :List(Signature); # signatures from nodes that have handled the private route - nonce @2 :Nonce; # nonce Xmsg - data @3 :Data; # operation encrypted with ENC(Xmsg,DH(PKapr,SKbsr)) + sequencing @1 :Sequencing; # sequencing preference to use to pass the message along + signatures @2 :List(Signature); # signatures from nodes that have handled the private route + nonce @3 :Nonce; # nonce Xmsg + data @4 :Data; # operation encrypted with ENC(Xmsg,DH(PKapr,SKbsr)) } struct OperationStatusQ @0x865d80cea70d884a { @@ -303,11 +305,6 @@ struct OperationRoute @0x96741859ce6ac7dd { operation @1 :RoutedOperation; # The operation to be routed } -struct OperationNodeInfoUpdate @0xc9647b32a48b66ce { - signedNodeInfo @0 :SignedNodeInfo; # Our signed node info -} - - struct OperationAppCallQ @0xade67b9f09784507 { message @0 :Data; # Opaque request to application } @@ -466,12 +463,12 @@ struct Question @0xd8510bc33492ef70 { findNodeQ @3 :OperationFindNodeQ; # Routable operations - getValueQ @4 :OperationGetValueQ; - setValueQ @5 :OperationSetValueQ; - watchValueQ @6 :OperationWatchValueQ; - supplyBlockQ @7 :OperationSupplyBlockQ; - findBlockQ @8 :OperationFindBlockQ; - appCallQ @9 :OperationAppCallQ; + appCallQ @4 :OperationAppCallQ; + getValueQ @5 :OperationGetValueQ; + setValueQ @6 :OperationSetValueQ; + watchValueQ @7 :OperationWatchValueQ; + supplyBlockQ @8 :OperationSupplyBlockQ; + findBlockQ @9 :OperationFindBlockQ; # Tunnel operations startTunnelQ @10 :OperationStartTunnelQ; @@ -486,13 +483,12 @@ struct Statement @0x990e20828f404ae1 { # Direct operations validateDialInfo @0 :OperationValidateDialInfo; route @1 :OperationRoute; - nodeInfoUpdate @2 :OperationNodeInfoUpdate; # Routable operations - valueChanged @3 :OperationValueChanged; - signal @4 :OperationSignal; - returnReceipt @5 :OperationReturnReceipt; - appMessage @6 :OperationAppMessage; + signal @2 :OperationSignal; + returnReceipt @3 :OperationReturnReceipt; + appMessage @4 :OperationAppMessage; + valueChanged @5 :OperationValueChanged; } } @@ -504,12 +500,12 @@ struct Answer @0xacacb8b6988c1058 { findNodeA @1 :OperationFindNodeA; # Routable operations - getValueA @2 :OperationGetValueA; - setValueA @3 :OperationSetValueA; - watchValueA @4 :OperationWatchValueA; - supplyBlockA @5 :OperationSupplyBlockA; - findBlockA @6 :OperationFindBlockA; - appCallA @7 :OperationAppCallA; + appCallA @2 :OperationAppCallA; + getValueA @3 :OperationGetValueA; + setValueA @4 :OperationSetValueA; + watchValueA @5 :OperationWatchValueA; + supplyBlockA @6 :OperationSupplyBlockA; + findBlockA @7 :OperationFindBlockA; # Tunnel operations startTunnelA @8 :OperationStartTunnelA; @@ -521,9 +517,10 @@ struct Answer @0xacacb8b6988c1058 { struct Operation @0xbf2811c435403c3b { opId @0 :UInt64; # Random RPC ID. Must be random to foil reply forgery attacks. senderNodeInfo @1 :SignedNodeInfo; # (optional) SignedNodeInfo for the sender to be cached by the receiver. + targetNodeInfoTs @2 :UInt64; # Timestamp the sender believes the target's node info to be at or zero if not sent kind :union { - question @2 :Question; - statement @3 :Statement; - answer @4 :Answer; + question @3 :Question; + statement @4 :Statement; + answer @5 :Answer; } } diff --git a/veilid-core/run_tests.sh b/veilid-core/run_tests.sh new file mode 100755 index 00000000..0a8ec9af --- /dev/null +++ b/veilid-core/run_tests.sh @@ -0,0 +1,65 @@ +#!/bin/bash +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +pushd $SCRIPTDIR 2>/dev/null +if [[ "$1" == "wasm" ]]; then + WASM_BINDGEN_TEST_TIMEOUT=120 wasm-pack test --firefox --headless +elif [[ "$1" == "ios" ]]; then + SYMROOT=/tmp/testout + APPNAME=veilidcore-tests + BUNDLENAME=com.veilid.veilidcore-tests + ID="$2" + if [[ "$ID" == "" ]]; then + echo "No emulator ID specified" + exit 1 + fi + + # Build for simulator + xcrun xcodebuild -project src/tests/ios/$APPNAME/$APPNAME.xcodeproj/ -scheme $APPNAME -destination "generic/platform=iOS Simulator" SYMROOT=$SYMROOT + + # Run in temporary simulator + xcrun simctl install $ID $SYMROOT/Debug-iphonesimulator/$APPNAME.app + xcrun simctl spawn $ID log stream --level debug --predicate "subsystem == \"$BUNDLENAME\"" & + xcrun simctl launch --console $ID $BUNDLENAME + sleep 1 # Ensure the last log lines print + kill -INT %1 + + # Clean up build output + rm -rf /tmp/testout + +elif [[ "$1" == "android" ]]; then + ID="$2" + if [[ "$ID" == "" ]]; then + echo "No emulator ID specified" + exit 1 + fi + APPNAME=veilid_core_android_tests + APPID=com.veilid.veilid_core_android_tests + ACTIVITYNAME=MainActivity + pushd src/tests/android/$APPNAME >/dev/null + # Build apk + ./gradlew assembleDebug + # Wait for boot + adb -s $ID wait-for-device + # Install app + adb -s $ID install -r ./app/build/outputs/apk/debug/app-debug.apk + # Start activity + adb -s $ID shell am start-activity -W $APPID/.$ACTIVITYNAME + # Get the pid of the program + APP_PID=`adb -s $ID shell pidof -s $APPID` + # Print the logcat + adb -s $ID shell logcat --pid=$APP_PID veilid-core:V *:S & + # Wait for the pid to be done + while [ "$(adb -s $ID shell pidof -s $APPID)" != "" ]; do + sleep 1 + done + # Terminate logcat + kill %1 + # Finished + popd >/dev/null + +else + cargo test --features=rt-tokio + cargo test --features=rt-async-std +fi +popd 2>/dev/null \ No newline at end of file diff --git a/veilid-core/run_windows_tests.bat b/veilid-core/run_windows_tests.bat new file mode 100644 index 00000000..7ab6b2a3 --- /dev/null +++ b/veilid-core/run_windows_tests.bat @@ -0,0 +1,4 @@ +@echo off +cargo test --features=rt-tokio -- --nocapture +cargo test --features=rt-async-std -- --nocapture + diff --git a/veilid-core/src/api_tracing_layer.rs b/veilid-core/src/api_tracing_layer.rs index dac00241..8de371ec 100644 --- a/veilid-core/src/api_tracing_layer.rs +++ b/veilid-core/src/api_tracing_layer.rs @@ -1,6 +1,6 @@ use crate::core_context::*; use crate::veilid_api::*; -use crate::xx::*; +use crate::*; use core::fmt::Write; use once_cell::sync::OnceCell; use tracing_subscriber::*; diff --git a/veilid-core/src/attachment_manager.rs b/veilid-core/src/attachment_manager.rs index 48447e3f..d5a1323d 100644 --- a/veilid-core/src/attachment_manager.rs +++ b/veilid-core/src/attachment_manager.rs @@ -1,111 +1,13 @@ -use crate::callback_state_machine::*; use crate::crypto::Crypto; use crate::network_manager::*; use crate::routing_table::*; -use crate::xx::*; use crate::*; -use core::convert::TryFrom; -use core::fmt; -use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; -use serde::*; - -state_machine! { - derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize,) - pub Attachment(Detached) -//--- - Detached(AttachRequested) => Attaching [StartAttachment], - Attaching => { - AttachmentStopped => Detached, - WeakPeers => AttachedWeak, - GoodPeers => AttachedGood, - StrongPeers => AttachedStrong, - FullPeers => FullyAttached, - TooManyPeers => OverAttached, - DetachRequested => Detaching [StopAttachment] - }, - AttachedWeak => { - NoPeers => Attaching, - GoodPeers => AttachedGood, - StrongPeers => AttachedStrong, - FullPeers => FullyAttached, - TooManyPeers => OverAttached, - DetachRequested => Detaching [StopAttachment] - }, - AttachedGood => { - NoPeers => Attaching, - WeakPeers => AttachedWeak, - StrongPeers => AttachedStrong, - FullPeers => FullyAttached, - TooManyPeers => OverAttached, - DetachRequested => Detaching [StopAttachment] - }, - AttachedStrong => { - NoPeers => Attaching, - WeakPeers => AttachedWeak, - GoodPeers => AttachedGood, - FullPeers => FullyAttached, - TooManyPeers => OverAttached, - DetachRequested => Detaching [StopAttachment] - }, - FullyAttached => { - NoPeers => Attaching, - WeakPeers => AttachedWeak, - GoodPeers => AttachedGood, - StrongPeers => AttachedStrong, - TooManyPeers => OverAttached, - DetachRequested => Detaching [StopAttachment] - }, - OverAttached => { - NoPeers => Attaching, - WeakPeers => AttachedWeak, - GoodPeers => AttachedGood, - StrongPeers => AttachedStrong, - FullPeers => FullyAttached, - DetachRequested => Detaching [StopAttachment] - }, - Detaching => { - AttachmentStopped => Detached, - }, -} - -impl fmt::Display for AttachmentState { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let out = match self { - AttachmentState::Attaching => "attaching".to_owned(), - AttachmentState::AttachedWeak => "attached_weak".to_owned(), - AttachmentState::AttachedGood => "attached_good".to_owned(), - AttachmentState::AttachedStrong => "attached_strong".to_owned(), - AttachmentState::FullyAttached => "fully_attached".to_owned(), - AttachmentState::OverAttached => "over_attached".to_owned(), - AttachmentState::Detaching => "detaching".to_owned(), - AttachmentState::Detached => "detached".to_owned(), - }; - write!(f, "{}", out) - } -} - -impl TryFrom for AttachmentState { - type Error = (); - - fn try_from(s: String) -> Result { - Ok(match s.as_str() { - "attaching" => AttachmentState::Attaching, - "attached_weak" => AttachmentState::AttachedWeak, - "attached_good" => AttachmentState::AttachedGood, - "attached_strong" => AttachmentState::AttachedStrong, - "fully_attached" => AttachmentState::FullyAttached, - "over_attached" => AttachmentState::OverAttached, - "detaching" => AttachmentState::Detaching, - "detached" => AttachmentState::Detached, - _ => return Err(()), - }) - } -} pub struct AttachmentManagerInner { - attachment_machine: CallbackStateMachine, + last_attachment_state: AttachmentState, + last_routing_table_health: Option, maintain_peers: bool, - attach_timestamp: Option, + attach_ts: Option, update_callback: Option, attachment_maintainer_jh: Option>, } @@ -142,9 +44,10 @@ impl AttachmentManager { } fn new_inner() -> AttachmentManagerInner { AttachmentManagerInner { - attachment_machine: CallbackStateMachine::new(), + last_attachment_state: AttachmentState::Detached, + last_routing_table_health: None, maintain_peers: false, - attach_timestamp: None, + attach_ts: None, update_callback: None, attachment_maintainer_jh: None, } @@ -177,84 +80,107 @@ impl AttachmentManager { } pub fn is_attached(&self) -> bool { - let s = self.inner.lock().attachment_machine.state(); + let s = self.inner.lock().last_attachment_state; !matches!(s, AttachmentState::Detached | AttachmentState::Detaching) } pub fn is_detached(&self) -> bool { - let s = self.inner.lock().attachment_machine.state(); + let s = self.inner.lock().last_attachment_state; matches!(s, AttachmentState::Detached) } - pub fn get_attach_timestamp(&self) -> Option { - self.inner.lock().attach_timestamp + pub fn get_attach_timestamp(&self) -> Option { + self.inner.lock().attach_ts } fn translate_routing_table_health( - health: RoutingTableHealth, + health: &RoutingTableHealth, config: &VeilidConfigRoutingTable, - ) -> AttachmentInput { + ) -> AttachmentState { if health.reliable_entry_count >= config.limit_over_attached.try_into().unwrap() { - return AttachmentInput::TooManyPeers; + return AttachmentState::OverAttached; } if health.reliable_entry_count >= config.limit_fully_attached.try_into().unwrap() { - return AttachmentInput::FullPeers; + return AttachmentState::FullyAttached; } if health.reliable_entry_count >= config.limit_attached_strong.try_into().unwrap() { - return AttachmentInput::StrongPeers; + return AttachmentState::AttachedStrong; } if health.reliable_entry_count >= config.limit_attached_good.try_into().unwrap() { - return AttachmentInput::GoodPeers; + return AttachmentState::AttachedGood; } if health.reliable_entry_count >= config.limit_attached_weak.try_into().unwrap() || health.unreliable_entry_count >= config.limit_attached_weak.try_into().unwrap() { - return AttachmentInput::WeakPeers; - } - AttachmentInput::NoPeers - } - fn translate_attachment_state(state: &AttachmentState) -> AttachmentInput { - match state { - AttachmentState::OverAttached => AttachmentInput::TooManyPeers, - AttachmentState::FullyAttached => AttachmentInput::FullPeers, - AttachmentState::AttachedStrong => AttachmentInput::StrongPeers, - AttachmentState::AttachedGood => AttachmentInput::GoodPeers, - AttachmentState::AttachedWeak => AttachmentInput::WeakPeers, - AttachmentState::Attaching => AttachmentInput::NoPeers, - _ => panic!("Invalid state"), + return AttachmentState::AttachedWeak; } + AttachmentState::Attaching } - async fn update_attachment(&self) { - let new_peer_state_input = { - let inner = self.inner.lock(); + /// Update attachment and network readiness state + /// and possibly send a VeilidUpdate::Attachment + fn update_attachment(&self) { + // update the routing table health + let routing_table = self.network_manager().routing_table(); + let health = routing_table.get_routing_table_health(); + let opt_update = { + let mut inner = self.inner.lock(); - let old_peer_state_input = - AttachmentManager::translate_attachment_state(&inner.attachment_machine.state()); + // Check if the routing table health is different + if let Some(last_routing_table_health) = &inner.last_routing_table_health { + // If things are the same, just return + if last_routing_table_health == &health { + return; + } + } - // get reliable peer count from routing table - let routing_table = self.network_manager().routing_table(); - let health = routing_table.get_routing_table_health(); + // Swap in new health numbers + let opt_previous_health = inner.last_routing_table_health.take(); + inner.last_routing_table_health = Some(health.clone()); + + // Calculate new attachment state let config = self.config(); let routing_table_config = &config.get().network.routing_table; + let previous_attachment_state = inner.last_attachment_state; + inner.last_attachment_state = + AttachmentManager::translate_routing_table_health(&health, routing_table_config); - let new_peer_state_input = - AttachmentManager::translate_routing_table_health(health, routing_table_config); + // If we don't have an update callback yet for some reason, just return now + let Some(update_callback) = inner.update_callback.clone() else { + return; + }; - if old_peer_state_input == new_peer_state_input { - None + // Send update if one of: + // * the attachment state has changed + // * routing domain readiness has changed + // * this is our first routing table health check + let send_update = previous_attachment_state != inner.last_attachment_state + || opt_previous_health + .map(|x| { + x.public_internet_ready != health.public_internet_ready + || x.local_network_ready != health.local_network_ready + }) + .unwrap_or(true); + if send_update { + Some((update_callback, Self::get_veilid_state_inner(&*inner))) } else { - Some(new_peer_state_input) + None } }; - if let Some(next_input) = new_peer_state_input { - let _ = self.process_input(&next_input).await; + + // Send the update outside of the lock + if let Some(update) = opt_update { + (update.0)(VeilidUpdate::Attachment(update.1)); } } #[instrument(level = "debug", skip(self))] async fn attachment_maintainer(self) { - debug!("attachment starting"); - self.inner.lock().attach_timestamp = Some(intf::get_timestamp()); + { + let mut inner = self.inner.lock(); + inner.last_attachment_state = AttachmentState::Attaching; + inner.attach_ts = Some(get_aligned_timestamp()); + debug!("attachment starting"); + } let netman = self.network_manager(); let mut restart; @@ -283,13 +209,21 @@ impl AttachmentManager { break; } - self.update_attachment().await; + // Update attachment and network readiness state + // and possibly send a VeilidUpdate::Attachment + self.update_attachment(); // sleep should be at the end in case maintain_peers changes state - intf::sleep(1000).await; + sleep(1000).await; } debug!("stopped maintaining peers"); + if !restart { + let mut inner = self.inner.lock(); + inner.last_attachment_state = AttachmentState::Detaching; + debug!("attachment stopping"); + } + debug!("stopping network"); netman.shutdown().await; @@ -299,16 +233,15 @@ impl AttachmentManager { debug!("completely restarting attachment"); // chill out for a second first, give network stack time to settle out - intf::sleep(1000).await; + sleep(1000).await; } - trace!("stopping attachment"); - let attachment_machine = self.inner.lock().attachment_machine.clone(); - let _output = attachment_machine - .consume(&AttachmentInput::AttachmentStopped) - .await; - debug!("attachment stopped"); - self.inner.lock().attach_timestamp = None; + { + let mut inner = self.inner.lock(); + inner.last_attachment_state = AttachmentState::Detached; + inner.attach_ts = None; + debug!("attachment stopped"); + } } #[instrument(level = "debug", skip_all, err)] @@ -317,15 +250,7 @@ impl AttachmentManager { { let mut inner = self.inner.lock(); inner.update_callback = Some(update_callback.clone()); - let update_callback2 = update_callback.clone(); - inner.attachment_machine.set_state_change_callback(Arc::new( - move |_old_state: AttachmentState, new_state: AttachmentState| { - update_callback2(VeilidUpdate::Attachment(VeilidStateAttachment { - state: new_state, - })) - }, - )); - }; + } self.network_manager().init(update_callback).await?; @@ -341,18 +266,20 @@ impl AttachmentManager { } #[instrument(level = "trace", skip(self))] - fn attach(&self) { + pub async fn attach(&self) -> bool { // Create long-running connection maintenance routine let mut inner = self.inner.lock(); if inner.attachment_maintainer_jh.is_some() { - return; + return false; } inner.maintain_peers = true; - inner.attachment_maintainer_jh = Some(intf::spawn(self.clone().attachment_maintainer())); + inner.attachment_maintainer_jh = Some(spawn(self.clone().attachment_maintainer())); + + true } #[instrument(level = "trace", skip(self))] - async fn detach(&self) { + pub async fn detach(&self) -> bool { let attachment_maintainer_jh = { let mut inner = self.inner.lock(); let attachment_maintainer_jh = inner.attachment_maintainer_jh.take(); @@ -364,57 +291,34 @@ impl AttachmentManager { }; if let Some(jh) = attachment_maintainer_jh { jh.await; + true + } else { + false } } - async fn handle_output(&self, output: &AttachmentOutput) { - match output { - AttachmentOutput::StartAttachment => self.attach(), - AttachmentOutput::StopAttachment => self.detach().await, + pub fn get_attachment_state(&self) -> AttachmentState { + self.inner.lock().last_attachment_state + } + + fn get_veilid_state_inner(inner: &AttachmentManagerInner) -> VeilidStateAttachment { + VeilidStateAttachment { + state: inner.last_attachment_state, + public_internet_ready: inner + .last_routing_table_health + .as_ref() + .map(|x| x.public_internet_ready) + .unwrap_or(false), + local_network_ready: inner + .last_routing_table_health + .as_ref() + .map(|x| x.local_network_ready) + .unwrap_or(false), } } - async fn process_input(&self, input: &AttachmentInput) -> EyreResult<()> { - let attachment_machine = self.inner.lock().attachment_machine.clone(); - let output = attachment_machine.consume(input).await; - match output { - Err(e) => Err(eyre!( - "invalid input '{:?}' for state machine in state '{:?}': {:?}", - input, - attachment_machine.state(), - e - )), - Ok(v) => { - if let Some(o) = v { - self.handle_output(&o).await; - } - Ok(()) - } - } - } - - #[instrument(level = "trace", skip(self), err)] - pub async fn request_attach(&self) -> EyreResult<()> { - self.process_input(&AttachmentInput::AttachRequested) - .await - .map_err(|e| eyre!("Attach request failed: {}", e)) - } - - #[instrument(level = "trace", skip(self), err)] - pub async fn request_detach(&self) -> EyreResult<()> { - self.process_input(&AttachmentInput::DetachRequested) - .await - .map_err(|e| eyre!("Detach request failed: {}", e)) - } - - pub fn get_state(&self) -> AttachmentState { - let attachment_machine = self.inner.lock().attachment_machine.clone(); - attachment_machine.state() - } - pub fn get_veilid_state(&self) -> VeilidStateAttachment { - VeilidStateAttachment { - state: self.get_state(), - } + let inner = self.inner.lock(); + Self::get_veilid_state_inner(&*inner) } } diff --git a/veilid-core/src/core_context.rs b/veilid-core/src/core_context.rs index 6f39129a..c8d7a434 100644 --- a/veilid-core/src/core_context.rs +++ b/veilid-core/src/core_context.rs @@ -3,7 +3,7 @@ use crate::attachment_manager::*; use crate::crypto::Crypto; use crate::veilid_api::*; use crate::veilid_config::*; -use crate::xx::*; +use crate::*; pub type UpdateCallback = Arc; @@ -201,9 +201,8 @@ impl VeilidCoreContext { ) -> Result { cfg_if! { if #[cfg(target_os = "android")] { - if crate::intf::utils::android::ANDROID_GLOBALS.lock().is_none() { - error!("Android globals are not set up"); - return Err(VeilidAPIError::Internal { message: "Android globals are not set up".to_owned() }); + if !crate::intf::android::is_android_ready() { + apibail_internal!("Android globals are not set up"); } } } @@ -251,7 +250,7 @@ pub async fn api_startup( // See if we have an API started up already let mut initialized_lock = INITIALIZED.lock().await; if *initialized_lock { - return Err(VeilidAPIError::AlreadyInitialized); + apibail_already_initialized!(); } // Create core context @@ -274,7 +273,7 @@ pub async fn api_startup_json( // See if we have an API started up already let mut initialized_lock = INITIALIZED.lock().await; if *initialized_lock { - return Err(VeilidAPIError::AlreadyInitialized); + apibail_already_initialized!(); } // Create core context diff --git a/veilid-core/src/crypto/envelope.rs b/veilid-core/src/crypto/envelope.rs index 32a02447..7d0b6734 100644 --- a/veilid-core/src/crypto/envelope.rs +++ b/veilid-core/src/crypto/envelope.rs @@ -2,7 +2,6 @@ #![allow(clippy::absurd_extreme_comparisons)] use super::*; use crate::routing_table::VersionRange; -use crate::xx::*; use crate::*; use core::convert::TryInto; @@ -45,7 +44,7 @@ pub struct Envelope { version: u8, min_version: u8, max_version: u8, - timestamp: u64, + timestamp: Timestamp, nonce: EnvelopeNonce, sender_id: DHTKey, recipient_id: DHTKey, @@ -54,7 +53,7 @@ pub struct Envelope { impl Envelope { pub fn new( version: u8, - timestamp: u64, + timestamp: Timestamp, nonce: EnvelopeNonce, sender_id: DHTKey, recipient_id: DHTKey, @@ -76,7 +75,7 @@ impl Envelope { // Ensure we are at least the length of the envelope // Silent drop here, as we use zero length packets as part of the protocol for hole punching if data.len() < MIN_ENVELOPE_SIZE { - return Err(VeilidAPIError::generic("envelope data too small")); + apibail_generic!("envelope data too small"); } // Verify magic number @@ -84,31 +83,28 @@ impl Envelope { .try_into() .map_err(VeilidAPIError::internal)?; if magic != *ENVELOPE_MAGIC { - return Err(VeilidAPIError::generic("bad magic number")); + apibail_generic!("bad magic number"); } // Check version let version = data[0x04]; if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION { - return Err(VeilidAPIError::parse_error( - "unsupported cryptography version", - version, - )); + apibail_parse_error!("unsupported cryptography version", version); } // Get min version let min_version = data[0x05]; if min_version > version { - return Err(VeilidAPIError::parse_error("version too low", version)); + apibail_parse_error!("version too low", version); } // Get max version let max_version = data[0x06]; if version > max_version { - return Err(VeilidAPIError::parse_error("version too high", version)); + apibail_parse_error!("version too high", version); } if min_version > max_version { - return Err(VeilidAPIError::generic("version information invalid")); + apibail_generic!("version information invalid"); } // Get size and ensure it matches the size of the envelope and is less than the maximum message size @@ -118,25 +114,26 @@ impl Envelope { .map_err(VeilidAPIError::internal)?, ); if (size as usize) > MAX_ENVELOPE_SIZE { - return Err(VeilidAPIError::parse_error("envelope too large", size)); + apibail_parse_error!("envelope too large", size); } if (size as usize) != data.len() { - return Err(VeilidAPIError::parse_error( + apibail_parse_error!( "size doesn't match envelope size", format!( "size doesn't match envelope size: size={} data.len()={}", size, data.len() - ), - )); + ) + ); } // Get the timestamp - let timestamp: u64 = u64::from_le_bytes( + let timestamp: Timestamp = u64::from_le_bytes( data[0x0A..0x12] .try_into() .map_err(VeilidAPIError::internal)?, - ); + ) + .into(); // Get nonce and sender node id let nonce: EnvelopeNonce = data[0x12..0x2A] @@ -153,10 +150,10 @@ impl Envelope { // Ensure sender_id and recipient_id are not the same if sender_id == recipient_id { - return Err(VeilidAPIError::parse_error( + apibail_parse_error!( "sender_id should not be same as recipient_id", - recipient_id.encode(), - )); + recipient_id.encode() + ); } // Get signature @@ -206,10 +203,7 @@ impl Envelope { // Ensure body isn't too long let envelope_size: usize = body.len() + MIN_ENVELOPE_SIZE; if envelope_size > MAX_ENVELOPE_SIZE { - return Err(VeilidAPIError::parse_error( - "envelope size is too large", - envelope_size, - )); + apibail_parse_error!("envelope size is too large", envelope_size); } let mut data = vec![0u8; envelope_size]; @@ -224,7 +218,7 @@ impl Envelope { // Write size data[0x08..0x0A].copy_from_slice(&(envelope_size as u16).to_le_bytes()); // Write timestamp - data[0x0A..0x12].copy_from_slice(&self.timestamp.to_le_bytes()); + data[0x0A..0x12].copy_from_slice(&self.timestamp.as_u64().to_le_bytes()); // Write nonce data[0x12..0x2A].copy_from_slice(&self.nonce); // Write sender node id @@ -267,7 +261,7 @@ impl Envelope { } } - pub fn get_timestamp(&self) -> u64 { + pub fn get_timestamp(&self) -> Timestamp { self.timestamp } diff --git a/veilid-core/src/crypto/key.rs b/veilid-core/src/crypto/key.rs index c5468084..218ffcb8 100644 --- a/veilid-core/src/crypto/key.rs +++ b/veilid-core/src/crypto/key.rs @@ -1,5 +1,3 @@ -use crate::veilid_rng::*; -use crate::xx::*; use crate::*; use core::cmp::{Eq, Ord, PartialEq, PartialOrd}; diff --git a/veilid-core/src/crypto/mod.rs b/veilid-core/src/crypto/mod.rs index ebede803..104dd600 100644 --- a/veilid-core/src/crypto/mod.rs +++ b/veilid-core/src/crypto/mod.rs @@ -13,7 +13,6 @@ pub use value::*; pub const MIN_CRYPTO_VERSION: u8 = 0u8; pub const MAX_CRYPTO_VERSION: u8 = 0u8; -use crate::xx::*; use crate::*; use chacha20::cipher::{KeyIvInit, StreamCipher}; use chacha20::XChaCha20; @@ -25,6 +24,7 @@ use ed25519_dalek as ed; use hashlink::linked_hash_map::Entry; use hashlink::LruCache; use serde::{Deserialize, Serialize}; + use x25519_dalek as xd; pub type SharedSecret = [u8; 32]; @@ -132,12 +132,12 @@ impl Crypto { drop(db); table_store.delete("crypto_caches").await?; db = table_store.open("crypto_caches", 1).await?; - db.store(0, b"node_id", &node_id.unwrap().bytes)?; + db.store(0, b"node_id", &node_id.unwrap().bytes).await?; } // Schedule flushing let this = self.clone(); - let flush_future = intf::interval(60000, move || { + let flush_future = interval(60000, move || { let this = this.clone(); async move { if let Err(e) = this.flush().await { @@ -159,7 +159,7 @@ impl Crypto { }; let db = table_store.open("crypto_caches", 1).await?; - db.store(0, b"dh_cache", &cache_bytes)?; + db.store(0, b"dh_cache", &cache_bytes).await?; Ok(()) } @@ -229,13 +229,13 @@ impl Crypto { pub fn get_random_nonce() -> Nonce { let mut nonce = [0u8; 24]; - intf::random_bytes(&mut nonce).unwrap(); + random_bytes(&mut nonce).unwrap(); nonce } pub fn get_random_secret() -> SharedSecret { let mut s = [0u8; 32]; - intf::random_bytes(&mut s).unwrap(); + random_bytes(&mut s).unwrap(); s } diff --git a/veilid-core/src/crypto/receipt.rs b/veilid-core/src/crypto/receipt.rs index d67fea51..d59e5f36 100644 --- a/veilid-core/src/crypto/receipt.rs +++ b/veilid-core/src/crypto/receipt.rs @@ -1,7 +1,6 @@ #![allow(dead_code)] #![allow(clippy::absurd_extreme_comparisons)] use super::*; -use crate::xx::*; use crate::*; use core::convert::TryInto; use data_encoding::BASE64URL_NOPAD; @@ -59,10 +58,10 @@ impl Receipt { extra_data: D, ) -> Result { if extra_data.as_ref().len() > MAX_EXTRA_DATA_SIZE { - return Err(VeilidAPIError::parse_error( + apibail_parse_error!( "extra data too large for receipt", - extra_data.as_ref().len(), - )); + extra_data.as_ref().len() + ); } Ok(Self { version, @@ -75,7 +74,7 @@ impl Receipt { pub fn from_signed_data(data: &[u8]) -> Result { // Ensure we are at least the length of the envelope if data.len() < MIN_RECEIPT_SIZE { - return Err(VeilidAPIError::parse_error("receipt too small", data.len())); + apibail_parse_error!("receipt too small", data.len()); } // Verify magic number @@ -83,16 +82,13 @@ impl Receipt { .try_into() .map_err(VeilidAPIError::internal)?; if magic != *RECEIPT_MAGIC { - return Err(VeilidAPIError::generic("bad magic number")); + apibail_generic!("bad magic number"); } // Check version let version = data[0x04]; if version > MAX_CRYPTO_VERSION || version < MIN_CRYPTO_VERSION { - return Err(VeilidAPIError::parse_error( - "unsupported cryptography version", - version, - )); + apibail_parse_error!("unsupported cryptography version", version); } // Get size and ensure it matches the size of the envelope and is less than the maximum message size @@ -102,16 +98,13 @@ impl Receipt { .map_err(VeilidAPIError::internal)?, ); if (size as usize) > MAX_RECEIPT_SIZE { - return Err(VeilidAPIError::parse_error( - "receipt size is too large", - size, - )); + apibail_parse_error!("receipt size is too large", size); } if (size as usize) != data.len() { - return Err(VeilidAPIError::parse_error( + apibail_parse_error!( "size doesn't match receipt size", - format!("size={} data.len()={}", size, data.len()), - )); + format!("size={} data.len()={}", size, data.len()) + ); } // Get sender id @@ -153,10 +146,7 @@ impl Receipt { // Ensure extra data isn't too long let receipt_size: usize = self.extra_data.len() + MIN_RECEIPT_SIZE; if receipt_size > MAX_RECEIPT_SIZE { - return Err(VeilidAPIError::parse_error( - "receipt too large", - receipt_size, - )); + apibail_parse_error!("receipt too large", receipt_size); } let mut data: Vec = vec![0u8; receipt_size]; diff --git a/veilid-core/src/crypto/tests/test_crypto.rs b/veilid-core/src/crypto/tests/test_crypto.rs index e1d09294..5710c8d9 100644 --- a/veilid-core/src/crypto/tests/test_crypto.rs +++ b/veilid-core/src/crypto/tests/test_crypto.rs @@ -1,7 +1,5 @@ use super::*; use crate::tests::common::test_veilid_config::*; -use crate::xx::*; -use crate::*; static LOREM_IPSUM:&[u8] = b"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "; diff --git a/veilid-core/src/crypto/tests/test_dht_key.rs b/veilid-core/src/crypto/tests/test_dht_key.rs index d6876ac3..26526d7b 100644 --- a/veilid-core/src/crypto/tests/test_dht_key.rs +++ b/veilid-core/src/crypto/tests/test_dht_key.rs @@ -1,7 +1,6 @@ #![allow(clippy::bool_assert_comparison)] use super::*; -use crate::xx::*; use core::convert::TryFrom; static LOREM_IPSUM:&str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. "; diff --git a/veilid-core/src/crypto/tests/test_envelope_receipt.rs b/veilid-core/src/crypto/tests/test_envelope_receipt.rs index 01b7ddf7..a5b2e45e 100644 --- a/veilid-core/src/crypto/tests/test_envelope_receipt.rs +++ b/veilid-core/src/crypto/tests/test_envelope_receipt.rs @@ -1,7 +1,5 @@ use super::*; use crate::tests::common::test_veilid_config::*; -use crate::xx::*; -use crate::*; pub async fn test_envelope_round_trip() { info!("--- test envelope round trip ---"); @@ -14,7 +12,7 @@ pub async fn test_envelope_round_trip() { let crypto = api.crypto().unwrap(); // Create envelope - let ts = 0x12345678ABCDEF69u64; + let ts = Timestamp::from(0x12345678ABCDEF69u64); let nonce = Crypto::get_random_nonce(); let (sender_id, sender_secret) = generate_secret(); let (recipient_id, recipient_secret) = generate_secret(); diff --git a/veilid-core/src/intf/native/utils/android/get_directories.rs b/veilid-core/src/intf/native/android/get_directories.rs similarity index 95% rename from veilid-core/src/intf/native/utils/android/get_directories.rs rename to veilid-core/src/intf/native/android/get_directories.rs index 5bd07e5c..f9a8ea30 100644 --- a/veilid-core/src/intf/native/utils/android/get_directories.rs +++ b/veilid-core/src/intf/native/android/get_directories.rs @@ -1,6 +1,7 @@ use super::*; -use crate::xx::*; +use jni::objects::JString; +#[allow(dead_code)] pub fn get_files_dir() -> String { let aglock = ANDROID_GLOBALS.lock(); let ag = aglock.as_ref().unwrap(); @@ -25,6 +26,7 @@ pub fn get_files_dir() -> String { .unwrap() } +#[allow(dead_code)] pub fn get_cache_dir() -> String { let aglock = ANDROID_GLOBALS.lock(); let ag = aglock.as_ref().unwrap(); diff --git a/veilid-core/src/intf/native/android/mod.rs b/veilid-core/src/intf/native/android/mod.rs new file mode 100644 index 00000000..00a34def --- /dev/null +++ b/veilid-core/src/intf/native/android/mod.rs @@ -0,0 +1,53 @@ +mod get_directories; +pub use get_directories::*; + +use crate::*; +use jni::errors::Result as JniResult; +use jni::{objects::GlobalRef, objects::JObject, JNIEnv, JavaVM}; +use lazy_static::*; + +pub struct AndroidGlobals { + pub vm: JavaVM, + pub ctx: GlobalRef, +} + +impl Drop for AndroidGlobals { + fn drop(&mut self) { + // Ensure we're attached before dropping GlobalRef + self.vm.attach_current_thread_as_daemon().unwrap(); + } +} + +lazy_static! { + pub static ref ANDROID_GLOBALS: Arc>> = Arc::new(Mutex::new(None)); +} + +pub fn veilid_core_setup_android(env: JNIEnv, ctx: JObject) { + *ANDROID_GLOBALS.lock() = Some(AndroidGlobals { + vm: env.get_java_vm().unwrap(), + ctx: env.new_global_ref(ctx).unwrap(), + }); +} + +pub fn is_android_ready() -> bool { + ANDROID_GLOBALS.lock().is_some() +} + +pub fn get_android_globals() -> (JavaVM, GlobalRef) { + let globals_locked = ANDROID_GLOBALS.lock(); + let globals = globals_locked.as_ref().unwrap(); + let env = globals.vm.attach_current_thread_as_daemon().unwrap(); + let vm = env.get_java_vm().unwrap(); + let ctx = globals.ctx.clone(); + (vm, ctx) +} + +pub fn with_null_local_frame<'b, T, F>(env: JNIEnv<'b>, s: i32, f: F) -> JniResult +where + F: FnOnce() -> JniResult, +{ + env.push_local_frame(s)?; + let out = f(); + env.pop_local_frame(JObject::null())?; + out +} diff --git a/veilid-core/src/intf/native/block_store.rs b/veilid-core/src/intf/native/block_store.rs index 6df36e6b..a5ced7e5 100644 --- a/veilid-core/src/intf/native/block_store.rs +++ b/veilid-core/src/intf/native/block_store.rs @@ -1,4 +1,3 @@ -use crate::xx::*; use crate::*; struct BlockStoreInner { diff --git a/veilid-core/src/intf/native/mod.rs b/veilid-core/src/intf/native/mod.rs index 663d9082..786b2dd1 100644 --- a/veilid-core/src/intf/native/mod.rs +++ b/veilid-core/src/intf/native/mod.rs @@ -2,9 +2,12 @@ mod block_store; mod protected_store; mod system; mod table_store; -pub mod utils; pub use block_store::*; pub use protected_store::*; pub use system::*; pub use table_store::*; + +#[cfg(target_os = "android")] +pub mod android; +pub mod network_interfaces; diff --git a/veilid-core/src/intf/native/utils/network_interfaces/apple.rs b/veilid-core/src/intf/native/network_interfaces/apple.rs similarity index 99% rename from veilid-core/src/intf/native/utils/network_interfaces/apple.rs rename to veilid-core/src/intf/native/network_interfaces/apple.rs index 97b6d8fd..c643b2ad 100644 --- a/veilid-core/src/intf/native/utils/network_interfaces/apple.rs +++ b/veilid-core/src/intf/native/network_interfaces/apple.rs @@ -1,5 +1,5 @@ use super::*; -use crate::*; + use libc::{ close, freeifaddrs, getifaddrs, if_nametoindex, ifaddrs, ioctl, pid_t, sockaddr, sockaddr_in6, socket, sysctl, time_t, AF_INET6, CTL_NET, IFF_BROADCAST, IFF_LOOPBACK, IFF_RUNNING, IFNAMSIZ, diff --git a/veilid-core/src/intf/native/utils/network_interfaces/mod.rs b/veilid-core/src/intf/native/network_interfaces/mod.rs similarity index 99% rename from veilid-core/src/intf/native/utils/network_interfaces/mod.rs rename to veilid-core/src/intf/native/network_interfaces/mod.rs index 1b14053f..3a05cb98 100644 --- a/veilid-core/src/intf/native/utils/network_interfaces/mod.rs +++ b/veilid-core/src/intf/native/network_interfaces/mod.rs @@ -1,7 +1,7 @@ -use crate::xx::*; -use core::fmt; mod tools; +use crate::*; + cfg_if::cfg_if! { if #[cfg(any(target_os = "linux", target_os = "android"))] { mod netlink; diff --git a/veilid-core/src/intf/native/utils/network_interfaces/netlink.rs b/veilid-core/src/intf/native/network_interfaces/netlink.rs similarity index 99% rename from veilid-core/src/intf/native/utils/network_interfaces/netlink.rs rename to veilid-core/src/intf/native/network_interfaces/netlink.rs index fc53362e..94f53a1f 100644 --- a/veilid-core/src/intf/native/utils/network_interfaces/netlink.rs +++ b/veilid-core/src/intf/native/network_interfaces/netlink.rs @@ -1,5 +1,4 @@ use super::*; -use crate::*; use alloc::collections::btree_map::Entry; use futures_util::stream::TryStreamExt; @@ -322,7 +321,7 @@ impl PlatformSupportNetlink { .wrap_err("failed to create rtnetlink socket")?; // Spawn a connection handler - let connection_jh = intf::spawn(connection); + let connection_jh = spawn(connection); // Save the connection self.connection_jh = Some(connection_jh); diff --git a/veilid-core/src/intf/native/utils/network_interfaces/sockaddr_tools.rs b/veilid-core/src/intf/native/network_interfaces/sockaddr_tools.rs similarity index 100% rename from veilid-core/src/intf/native/utils/network_interfaces/sockaddr_tools.rs rename to veilid-core/src/intf/native/network_interfaces/sockaddr_tools.rs diff --git a/veilid-core/src/intf/native/utils/network_interfaces/tools.rs b/veilid-core/src/intf/native/network_interfaces/tools.rs similarity index 100% rename from veilid-core/src/intf/native/utils/network_interfaces/tools.rs rename to veilid-core/src/intf/native/network_interfaces/tools.rs diff --git a/veilid-core/src/intf/native/utils/network_interfaces/windows.rs b/veilid-core/src/intf/native/network_interfaces/windows.rs similarity index 99% rename from veilid-core/src/intf/native/utils/network_interfaces/windows.rs rename to veilid-core/src/intf/native/network_interfaces/windows.rs index d4ca6ec1..53b74ba2 100644 --- a/veilid-core/src/intf/native/utils/network_interfaces/windows.rs +++ b/veilid-core/src/intf/native/network_interfaces/windows.rs @@ -63,7 +63,8 @@ impl PlatformSupportWindows { // } // Iterate all the interfaces - let windows_interfaces = WindowsInterfaces::new().wrap_err("failed to get windows interfaces")?; + let windows_interfaces = + WindowsInterfaces::new().wrap_err("failed to get windows interfaces")?; for windows_interface in windows_interfaces.iter() { // Get name let intf_name = windows_interface.name(); diff --git a/veilid-core/src/intf/native/protected_store.rs b/veilid-core/src/intf/native/protected_store.rs index a3c35f68..7ad89551 100644 --- a/veilid-core/src/intf/native/protected_store.rs +++ b/veilid-core/src/intf/native/protected_store.rs @@ -1,4 +1,3 @@ -use crate::xx::*; use crate::*; use data_encoding::BASE64URL_NOPAD; use keyring_manager::*; @@ -56,7 +55,7 @@ impl ProtectedStore { // Attempt to open the secure keyring cfg_if! { if #[cfg(target_os = "android")] { - inner.keyring_manager = KeyringManager::new_secure(&c.program_name, intf::native::utils::android::get_android_globals()).ok(); + inner.keyring_manager = KeyringManager::new_secure(&c.program_name, crate::intf::android::get_android_globals()).ok(); } else { inner.keyring_manager = KeyringManager::new_secure(&c.program_name).ok(); } diff --git a/veilid-core/src/intf/native/system.rs b/veilid-core/src/intf/native/system.rs index 56154e47..5491bff4 100644 --- a/veilid-core/src/intf/native/system.rs +++ b/veilid-core/src/intf/native/system.rs @@ -1,201 +1,11 @@ #![allow(dead_code)] -use crate::xx::*; -use rand::prelude::*; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -pub fn get_timestamp() -> u64 { - match SystemTime::now().duration_since(UNIX_EPOCH) { - Ok(n) => n.as_micros() as u64, - Err(_) => panic!("SystemTime before UNIX_EPOCH!"), - } -} - -// pub fn get_timestamp_string() -> String { -// let dt = chrono::Utc::now(); -// dt.time().format("%H:%M:%S.3f").to_string() -// } - -pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> { - let mut rng = rand::thread_rng(); - rng.try_fill_bytes(dest).wrap_err("failed to fill bytes") -} - -pub fn get_random_u32() -> u32 { - let mut rng = rand::thread_rng(); - rng.next_u32() -} - -pub fn get_random_u64() -> u64 { - let mut rng = rand::thread_rng(); - rng.next_u64() -} - -pub async fn sleep(millis: u32) { - if millis == 0 { - cfg_if! { - if #[cfg(feature="rt-async-std")] { - async_std::task::yield_now().await; - } else if #[cfg(feature="rt-tokio")] { - tokio::task::yield_now().await; - } - } - } else { - cfg_if! { - if #[cfg(feature="rt-async-std")] { - async_std::task::sleep(Duration::from_millis(u64::from(millis))).await; - } else if #[cfg(feature="rt-tokio")] { - tokio::time::sleep(Duration::from_millis(u64::from(millis))).await; - } - } - } -} - -pub fn system_boxed<'a, Out>( - future: impl Future + Send + 'a, -) -> SendPinBoxFutureLifetime<'a, Out> { - Box::pin(future) -} - -pub fn spawn(future: impl Future + Send + 'static) -> MustJoinHandle -where - Out: Send + 'static, -{ - cfg_if! { - if #[cfg(feature="rt-async-std")] { - MustJoinHandle::new(async_std::task::spawn(future)) - } else if #[cfg(feature="rt-tokio")] { - MustJoinHandle::new(tokio::task::spawn(future)) - } - } -} - -pub fn spawn_local(future: impl Future + 'static) -> MustJoinHandle -where - Out: 'static, -{ - cfg_if! { - if #[cfg(feature="rt-async-std")] { - MustJoinHandle::new(async_std::task::spawn_local(future)) - } else if #[cfg(feature="rt-tokio")] { - MustJoinHandle::new(tokio::task::spawn_local(future)) - } - } -} - -// pub fn spawn_with_local_set( -// future: impl Future + Send + 'static, -// ) -> MustJoinHandle -// where -// Out: Send + 'static, -// { -// cfg_if! { -// if #[cfg(feature="rt-async-std")] { -// spawn(future) -// } else if #[cfg(feature="rt-tokio")] { -// MustJoinHandle::new(tokio::task::spawn_blocking(move || { -// let rt = tokio::runtime::Handle::current(); -// rt.block_on(async { -// let local = tokio::task::LocalSet::new(); -// local.run_until(future).await -// }) -// })) -// } -// } -// } - -pub fn spawn_detached(future: impl Future + Send + 'static) -where - Out: Send + 'static, -{ - cfg_if! { - if #[cfg(feature="rt-async-std")] { - drop(async_std::task::spawn(future)); - } else if #[cfg(feature="rt-tokio")] { - drop(tokio::task::spawn(future)); - } - } -} - -pub fn interval(freq_ms: u32, callback: F) -> SendPinBoxFuture<()> -where - F: Fn() -> FUT + Send + Sync + 'static, - FUT: Future + Send, -{ - let e = Eventual::new(); - - let ie = e.clone(); - let jh = spawn(async move { - while timeout(freq_ms, ie.instance_clone(())).await.is_err() { - callback().await; - } - }); - - Box::pin(async move { - e.resolve().await; - jh.await; - }) -} - -pub async fn timeout(dur_ms: u32, f: F) -> Result -where - F: Future, -{ - cfg_if! { - if #[cfg(feature="rt-async-std")] { - async_std::future::timeout(Duration::from_millis(dur_ms as u64), f).await.map_err(|e| e.into()) - } else if #[cfg(feature="rt-tokio")] { - tokio::time::timeout(Duration::from_millis(dur_ms as u64), f).await.map_err(|e| e.into()) - } - } -} - -pub async fn blocking_wrapper(blocking_task: F, err_result: R) -> R -where - F: FnOnce() -> R + Send + 'static, - R: Send + 'static, -{ - // run blocking stuff in blocking thread - cfg_if! { - if #[cfg(feature="rt-async-std")] { - async_std::task::spawn_blocking(blocking_task).await - } else if #[cfg(feature="rt-tokio")] { - tokio::task::spawn_blocking(blocking_task).await.unwrap_or(err_result) - } else { - #[compile_error("must use an executor")] - } - } -} - -pub fn get_concurrency() -> u32 { - std::thread::available_parallelism() - .map(|x| x.get()) - .unwrap_or_else(|e| { - warn!("unable to get concurrency defaulting to single core: {}", e); - 1 - }) as u32 -} +use crate::*; pub async fn get_outbound_relay_peer() -> Option { panic!("Native Veilid should never require an outbound relay"); } -/* -pub fn async_callback(fut: F, ok_fn: OF, err_fn: EF) -where - F: Future> + Send + 'static, - OF: FnOnce(T) + Send + 'static, - EF: FnOnce(E) + Send + 'static, -{ - spawn(Box::pin(async move { - match fut.await { - Ok(v) => ok_fn(v), - Err(e) => err_fn(e), - }; - })); -} -*/ - ///////////////////////////////////////////////////////////////////////////////// // Resolver // diff --git a/veilid-core/src/intf/native/table_store.rs b/veilid-core/src/intf/native/table_store.rs index 5d5ceb14..a09b8e4d 100644 --- a/veilid-core/src/intf/native/table_store.rs +++ b/veilid-core/src/intf/native/table_store.rs @@ -1,5 +1,5 @@ -use crate::intf::table_db::*; -use crate::xx::*; +use crate::intf::table_db::TableDBInner; +pub use crate::intf::table_db::{TableDB, TableDBTransaction}; use crate::*; use keyvaluedb_sqlite::*; use std::path::PathBuf; diff --git a/veilid-core/src/intf/native/utils/android/mod.rs b/veilid-core/src/intf/native/utils/android/mod.rs deleted file mode 100644 index d7bdd15b..00000000 --- a/veilid-core/src/intf/native/utils/android/mod.rs +++ /dev/null @@ -1,109 +0,0 @@ -// xxx : support for android older than API 24, if we need it someday -//mod android_get_if_addrs; -//pub use android_get_if_addrs::*; - -mod get_directories; -pub use get_directories::*; - -use crate::veilid_config::VeilidConfigLogLevel; -use crate::xx::*; -use crate::*; -use backtrace::Backtrace; -use jni::errors::Result as JniResult; -use jni::{objects::GlobalRef, objects::JObject, objects::JString, JNIEnv, JavaVM}; -use lazy_static::*; -use std::panic; -use tracing::*; -use tracing_subscriber::prelude::*; -use tracing_subscriber::*; - -pub struct AndroidGlobals { - pub vm: JavaVM, - pub ctx: GlobalRef, -} - -impl Drop for AndroidGlobals { - fn drop(&mut self) { - // Ensure we're attached before dropping GlobalRef - self.vm.attach_current_thread_as_daemon().unwrap(); - } -} - -lazy_static! { - pub static ref ANDROID_GLOBALS: Arc>> = Arc::new(Mutex::new(None)); -} - -pub fn veilid_core_setup_android_no_log<'a>(env: JNIEnv<'a>, ctx: JObject<'a>) { - *ANDROID_GLOBALS.lock() = Some(AndroidGlobals { - vm: env.get_java_vm().unwrap(), - ctx: env.new_global_ref(ctx).unwrap(), - }); -} - -pub fn veilid_core_setup_android<'a>( - env: JNIEnv<'a>, - ctx: JObject<'a>, - log_tag: &'a str, - log_level: VeilidConfigLogLevel, -) { - // Set up subscriber and layers - let subscriber = Registry::default(); - let mut layers = Vec::new(); - let mut filters = BTreeMap::new(); - let filter = VeilidLayerFilter::new(log_level, None); - let layer = tracing_android::layer(log_tag) - .expect("failed to set up android logging") - .with_filter(filter.clone()); - filters.insert("system", filter); - layers.push(layer.boxed()); - - let subscriber = subscriber.with(layers); - subscriber - .try_init() - .expect("failed to init android tracing"); - - // Set up panic hook for backtraces - panic::set_hook(Box::new(|panic_info| { - let bt = Backtrace::new(); - if let Some(location) = panic_info.location() { - error!( - "panic occurred in file '{}' at line {}", - location.file(), - location.line(), - ); - } else { - error!("panic occurred but can't get location information..."); - } - if let Some(s) = panic_info.payload().downcast_ref::<&str>() { - error!("panic payload: {:?}", s); - } else if let Some(s) = panic_info.payload().downcast_ref::() { - error!("panic payload: {:?}", s); - } else if let Some(a) = panic_info.payload().downcast_ref::() { - error!("panic payload: {:?}", a); - } else { - error!("no panic payload"); - } - error!("Backtrace:\n{:?}", bt); - })); - - veilid_core_setup_android_no_log(env, ctx); -} - -pub fn get_android_globals() -> (JavaVM, GlobalRef) { - let globals_locked = ANDROID_GLOBALS.lock(); - let globals = globals_locked.as_ref().unwrap(); - let env = globals.vm.attach_current_thread_as_daemon().unwrap(); - let vm = env.get_java_vm().unwrap(); - let ctx = globals.ctx.clone(); - (vm, ctx) -} - -pub fn with_null_local_frame<'b, T, F>(env: JNIEnv<'b>, s: i32, f: F) -> JniResult -where - F: FnOnce() -> JniResult, -{ - env.push_local_frame(s)?; - let out = f(); - env.pop_local_frame(JObject::null())?; - out -} diff --git a/veilid-core/src/intf/native/utils/ios_test_setup.rs b/veilid-core/src/intf/native/utils/ios_test_setup.rs deleted file mode 100644 index 1a6b4f02..00000000 --- a/veilid-core/src/intf/native/utils/ios_test_setup.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::xx::*; -use backtrace::Backtrace; -use log::*; -use simplelog::*; -use std::fs::OpenOptions; -use std::panic; -use std::path::{Path, PathBuf}; - -pub fn veilid_core_setup<'a>( - log_tag: &'a str, - terminal_log: Option, - file_log: Option<(Level, &Path)>, -) { - if let Err(e) = veilid_core_setup_internal(log_tag, terminal_log, file_log) { - panic!("failed to set up veilid-core: {}", e); - } -} - -fn veilid_core_setup_internal<'a>( - _log_tag: &'a str, - terminal_log: Option, - file_log: Option<(Level, &Path)>, -) -> Result<(), String> { - let mut logs: Vec> = Vec::new(); - - let mut cb = ConfigBuilder::new(); - for ig in veilid_core::DEFAULT_LOG_IGNORE_LIST { - cb.add_filter_ignore_str(ig); - } - - if let Some(level) = terminal_log { - logs.push(TermLogger::new( - level.to_level_filter(), - cb.build(), - TerminalMode::Mixed, - ColorChoice::Auto, - )) - } - if let Some((level, log_path)) = file_log { - let logfile = OpenOptions::new() - .truncate(true) - .create(true) - .write(true) - .open(log_path) - .map_err(|e| { - format!( - "log open error: {} path={:?} all_dirs={:?}", - e, - log_path, - std::fs::read_dir(std::env::var("HOME").unwrap()) - .unwrap() - .map(|d| d.unwrap().path()) - .collect::>() - ) - })?; - logs.push(WriteLogger::new( - level.to_level_filter(), - cb.build(), - logfile, - )) - } - CombinedLogger::init(logs).map_err(|e| format!("logger init error: {}", e))?; - - panic::set_hook(Box::new(|panic_info| { - let bt = Backtrace::new(); - if let Some(location) = panic_info.location() { - error!( - "panic occurred in file '{}' at line {}", - location.file(), - location.line(), - ); - } else { - error!("panic occurred but can't get location information..."); - } - if let Some(s) = panic_info.payload().downcast_ref::<&str>() { - error!("panic payload: {:?}", s); - } else if let Some(s) = panic_info.payload().downcast_ref::() { - error!("panic payload: {:?}", s); - } else if let Some(a) = panic_info.payload().downcast_ref::() { - error!("panic payload: {:?}", a); - } else { - error!("no panic payload"); - } - error!("Backtrace:\n{:?}", bt); - })); - - Ok(()) -} diff --git a/veilid-core/src/intf/native/utils/mod.rs b/veilid-core/src/intf/native/utils/mod.rs deleted file mode 100644 index d49d97ff..00000000 --- a/veilid-core/src/intf/native/utils/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[cfg(target_os = "android")] -pub mod android; -#[cfg(all(target_os = "ios", feature = "ios_tests"))] -pub mod ios_test_setup; -pub mod network_interfaces; diff --git a/veilid-core/src/intf/table_db.rs b/veilid-core/src/intf/table_db.rs index 6e080247..6c93dcbf 100644 --- a/veilid-core/src/intf/table_db.rs +++ b/veilid-core/src/intf/table_db.rs @@ -1,4 +1,3 @@ -use crate::xx::*; use crate::*; use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; @@ -18,13 +17,19 @@ pub struct TableDBInner { database: Database, } +impl fmt::Debug for TableDBInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "TableDBInner(table={})", self.table) + } +} + impl Drop for TableDBInner { fn drop(&mut self) { self.table_store.on_table_db_drop(self.table.clone()); } } -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct TableDB { inner: Arc>, } @@ -69,51 +74,51 @@ impl TableDB { } /// Start a TableDB write transaction. The transaction object must be committed or rolled back before dropping. - pub fn transact<'a>(&'a self) -> TableDBTransaction<'a> { + pub fn transact(&self) -> TableDBTransaction { let dbt = { let db = &self.inner.lock().database; db.transaction() }; - TableDBTransaction::new(self, dbt) + TableDBTransaction::new(self.clone(), dbt) } /// Store a key with a value in a column in the TableDB. Performs a single transaction immediately. - pub fn store(&self, col: u32, key: &[u8], value: &[u8]) -> EyreResult<()> { - let db = &self.inner.lock().database; + pub async fn store(&self, col: u32, key: &[u8], value: &[u8]) -> EyreResult<()> { + let db = self.inner.lock().database.clone(); let mut dbt = db.transaction(); dbt.put(col, key, value); - db.write(dbt).wrap_err("failed to store key") + db.write(dbt).await.wrap_err("failed to store key") } /// Store a key in rkyv format with a value in a column in the TableDB. Performs a single transaction immediately. - pub fn store_rkyv(&self, col: u32, key: &[u8], value: &T) -> EyreResult<()> + pub async fn store_rkyv(&self, col: u32, key: &[u8], value: &T) -> EyreResult<()> where T: RkyvSerialize>, { let v = to_rkyv(value)?; - let db = &self.inner.lock().database; + let db = self.inner.lock().database.clone(); let mut dbt = db.transaction(); dbt.put(col, key, v.as_slice()); - db.write(dbt).wrap_err("failed to store key") + db.write(dbt).await.wrap_err("failed to store key") } /// Store a key in json format with a value in a column in the TableDB. Performs a single transaction immediately. - pub fn store_json(&self, col: u32, key: &[u8], value: &T) -> EyreResult<()> + pub async fn store_json(&self, col: u32, key: &[u8], value: &T) -> EyreResult<()> where T: serde::Serialize, { let v = serde_json::to_vec(value)?; - let db = &self.inner.lock().database; + let db = self.inner.lock().database.clone(); let mut dbt = db.transaction(); dbt.put(col, key, v.as_slice()); - db.write(dbt).wrap_err("failed to store key") + db.write(dbt).await.wrap_err("failed to store key") } /// Read a key from a column in the TableDB immediately. pub fn load(&self, col: u32, key: &[u8]) -> EyreResult>> { - let db = &self.inner.lock().database; + let db = self.inner.lock().database.clone(); db.get(col, key).wrap_err("failed to get key") } @@ -126,7 +131,7 @@ impl TableDB { ::Archived: RkyvDeserialize, { - let db = &self.inner.lock().database; + let db = self.inner.lock().database.clone(); let out = db.get(col, key).wrap_err("failed to get key")?; let b = match out { Some(v) => v, @@ -143,7 +148,7 @@ impl TableDB { where T: for<'de> serde::Deserialize<'de>, { - let db = &self.inner.lock().database; + let db = self.inner.lock().database.clone(); let out = db.get(col, key).wrap_err("failed to get key")?; let b = match out { Some(v) => v, @@ -156,15 +161,15 @@ impl TableDB { } /// Delete key with from a column in the TableDB - pub fn delete(&self, col: u32, key: &[u8]) -> EyreResult { - let db = &self.inner.lock().database; + pub async fn delete(&self, col: u32, key: &[u8]) -> EyreResult { + let db = self.inner.lock().database.clone(); let found = db.get(col, key).wrap_err("failed to get key")?; match found { None => Ok(false), Some(_) => { let mut dbt = db.transaction(); dbt.delete(col, key); - db.write(dbt).wrap_err("failed to delete key")?; + db.write(dbt).await.wrap_err("failed to delete key")?; Ok(true) } } @@ -173,70 +178,96 @@ impl TableDB { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/// A TableDB transaction -/// Atomically commits a group of writes or deletes to the TableDB -pub struct TableDBTransaction<'a> { - db: &'a TableDB, +struct TableDBTransactionInner { dbt: Option, - _phantom: core::marker::PhantomData<&'a ()>, } -impl<'a> TableDBTransaction<'a> { - fn new(db: &'a TableDB, dbt: DBTransaction) -> Self { +impl fmt::Debug for TableDBTransactionInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "TableDBTransactionInner({})", + match &self.dbt { + Some(dbt) => format!("len={}", dbt.ops.len()), + None => "".to_owned(), + } + ) + } +} + +/// A TableDB transaction +/// Atomically commits a group of writes or deletes to the TableDB +#[derive(Debug, Clone)] +pub struct TableDBTransaction { + db: TableDB, + inner: Arc>, +} + +impl TableDBTransaction { + fn new(db: TableDB, dbt: DBTransaction) -> Self { Self { db, - dbt: Some(dbt), - _phantom: Default::default(), + inner: Arc::new(Mutex::new(TableDBTransactionInner { dbt: Some(dbt) })), } } /// Commit the transaction. Performs all actions atomically. - pub fn commit(mut self) -> EyreResult<()> { - self.db - .inner - .lock() - .database - .write(self.dbt.take().unwrap()) - .wrap_err("commit failed") + pub async fn commit(self) -> EyreResult<()> { + let dbt = { + let mut inner = self.inner.lock(); + inner + .dbt + .take() + .ok_or_else(|| eyre!("transaction already completed"))? + }; + let db = self.db.inner.lock().database.clone(); + db.write(dbt) + .await + .wrap_err("commit failed, transaction lost") } /// Rollback the transaction. Does nothing to the TableDB. - pub fn rollback(mut self) { - self.dbt = None; + pub fn rollback(self) { + let mut inner = self.inner.lock(); + inner.dbt = None; } /// Store a key with a value in a column in the TableDB - pub fn store(&mut self, col: u32, key: &[u8], value: &[u8]) { - self.dbt.as_mut().unwrap().put(col, key, value); + pub fn store(&self, col: u32, key: &[u8], value: &[u8]) { + let mut inner = self.inner.lock(); + inner.dbt.as_mut().unwrap().put(col, key, value); } /// Store a key in rkyv format with a value in a column in the TableDB - pub fn store_rkyv(&mut self, col: u32, key: &[u8], value: &T) -> EyreResult<()> + pub fn store_rkyv(&self, col: u32, key: &[u8], value: &T) -> EyreResult<()> where T: RkyvSerialize>, { let v = to_rkyv(value)?; - self.dbt.as_mut().unwrap().put(col, key, v.as_slice()); + let mut inner = self.inner.lock(); + inner.dbt.as_mut().unwrap().put(col, key, v.as_slice()); Ok(()) } /// Store a key in rkyv format with a value in a column in the TableDB - pub fn store_json(&mut self, col: u32, key: &[u8], value: &T) -> EyreResult<()> + pub fn store_json(&self, col: u32, key: &[u8], value: &T) -> EyreResult<()> where T: serde::Serialize, { let v = serde_json::to_vec(value)?; - self.dbt.as_mut().unwrap().put(col, key, v.as_slice()); + let mut inner = self.inner.lock(); + inner.dbt.as_mut().unwrap().put(col, key, v.as_slice()); Ok(()) } /// Delete key with from a column in the TableDB - pub fn delete(&mut self, col: u32, key: &[u8]) { - self.dbt.as_mut().unwrap().delete(col, key); + pub fn delete(&self, col: u32, key: &[u8]) { + let mut inner = self.inner.lock(); + inner.dbt.as_mut().unwrap().delete(col, key); } } -impl<'a> Drop for TableDBTransaction<'a> { +impl Drop for TableDBTransactionInner { fn drop(&mut self) { if self.dbt.is_some() { warn!("Dropped transaction without commit or rollback"); diff --git a/veilid-core/src/intf/wasm/block_store.rs b/veilid-core/src/intf/wasm/block_store.rs index 3b207947..2bd7d280 100644 --- a/veilid-core/src/intf/wasm/block_store.rs +++ b/veilid-core/src/intf/wasm/block_store.rs @@ -1,5 +1,3 @@ - -use crate::xx::*; use crate::*; struct BlockStoreInner { diff --git a/veilid-core/src/intf/wasm/mod.rs b/veilid-core/src/intf/wasm/mod.rs index 6b6e6967..53faa230 100644 --- a/veilid-core/src/intf/wasm/mod.rs +++ b/veilid-core/src/intf/wasm/mod.rs @@ -3,10 +3,7 @@ mod protected_store; mod system; mod table_store; -pub mod utils; - pub use block_store::*; pub use protected_store::*; pub use system::*; pub use table_store::*; -use utils::*; diff --git a/veilid-core/src/intf/wasm/protected_store.rs b/veilid-core/src/intf/wasm/protected_store.rs index 67a4d1da..9a2c1646 100644 --- a/veilid-core/src/intf/wasm/protected_store.rs +++ b/veilid-core/src/intf/wasm/protected_store.rs @@ -1,11 +1,7 @@ -use super::*; -use crate::xx::*; use crate::*; use data_encoding::BASE64URL_NOPAD; -use js_sys::*; -use send_wrapper::*; -use serde::{Deserialize, Serialize}; -use wasm_bindgen_futures::*; +use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; + use web_sys::*; #[derive(Clone)] @@ -44,15 +40,6 @@ impl ProtectedStore { #[instrument(level = "debug", skip(self))] pub async fn terminate(&self) {} - fn keyring_name(&self) -> String { - let c = self.config.get(); - if c.namespace.is_empty() { - "veilid_protected_store".to_owned() - } else { - format!("veilid_protected_store_{}", c.namespace) - } - } - fn browser_key_name(&self, key: &str) -> String { let c = self.config.get(); if c.namespace.is_empty() { @@ -136,22 +123,31 @@ impl ProtectedStore { } #[instrument(level = "trace", skip(self, value))] - pub async fn save_user_secret_frozen(&self, key: &str, value: &T) -> EyreResult + pub async fn save_user_secret_rkyv(&self, key: &str, value: &T) -> EyreResult where T: RkyvSerialize>, { - let v = to_frozen(value)?; + let v = to_rkyv(value)?; + self.save_user_secret(&key, &v).await + } + + #[instrument(level = "trace", skip(self, value))] + pub async fn save_user_secret_json(&self, key: &str, value: &T) -> EyreResult + where + T: serde::Serialize, + { + let v = serde_json::to_vec(value)?; self.save_user_secret(&key, &v).await } #[instrument(level = "trace", skip(self))] - pub async fn load_user_secret_frozen(&self, key: &str) -> EyreResult> + pub async fn load_user_secret_rkyv(&self, key: &str) -> EyreResult> where T: RkyvArchive, ::Archived: for<'t> bytecheck::CheckBytes>, ::Archived: - rkyv::Deserialize, + RkyvDeserialize, { let out = self.load_user_secret(key).await?; let b = match out { @@ -161,7 +157,24 @@ impl ProtectedStore { } }; - let obj = from_frozen(&b)?; + let obj = from_rkyv(b)?; + Ok(Some(obj)) + } + + #[instrument(level = "trace", skip(self))] + pub async fn load_user_secret_json(&self, key: &str) -> EyreResult> + where + T: for<'de> serde::de::Deserialize<'de>, + { + let out = self.load_user_secret(key).await?; + let b = match out { + Some(v) => v, + None => { + return Ok(None); + } + }; + + let obj = serde_json::from_slice(&b)?; Ok(Some(obj)) } diff --git a/veilid-core/src/intf/wasm/system.rs b/veilid-core/src/intf/wasm/system.rs index 00158ad8..95e4e544 100644 --- a/veilid-core/src/intf/wasm/system.rs +++ b/veilid-core/src/intf/wasm/system.rs @@ -1,159 +1,6 @@ -use super::utils; -use crate::xx::*; use crate::*; -use async_executors::{Bindgen, LocalSpawnHandleExt, SpawnHandleExt, Timer}; -use futures_util::future::{select, Either}; -use js_sys::*; -//use wasm_bindgen_futures::*; -//use web_sys::*; -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(catch, structural, js_namespace = global, js_name = setTimeout)] - fn nodejs_global_set_timeout_with_callback_and_timeout_and_arguments_0( - handler: &::js_sys::Function, - timeout: u32, - ) -> Result; -} - -pub fn get_timestamp() -> u64 { - if utils::is_browser() { - return (Date::now() * 1000.0f64) as u64; - } else { - panic!("WASM requires browser environment"); - } -} - -// pub fn get_timestamp_string() -> String { -// let date = Date::new_0(); -// let hours = Date::get_utc_hours(&date); -// let minutes = Date::get_utc_minutes(&date); -// let seconds = Date::get_utc_seconds(&date); -// let milliseconds = Date::get_utc_milliseconds(&date); -// format!( -// "{:02}:{:02}:{:02}.{}", -// hours, minutes, seconds, milliseconds -// ) -// } - -pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> { - let len = dest.len(); - let u32len = len / 4; - let remlen = len % 4; - - for n in 0..u32len { - let r = (Math::random() * (u32::max_value() as f64)) as u32; - - dest[n * 4 + 0] = (r & 0xFF) as u8; - dest[n * 4 + 1] = ((r >> 8) & 0xFF) as u8; - dest[n * 4 + 2] = ((r >> 16) & 0xFF) as u8; - dest[n * 4 + 3] = ((r >> 24) & 0xFF) as u8; - } - if remlen > 0 { - let r = (Math::random() * (u32::max_value() as f64)) as u32; - for n in 0..remlen { - dest[u32len * 4 + n] = ((r >> (n * 8)) & 0xFF) as u8; - } - } - - Ok(()) -} - -pub fn get_random_u32() -> u32 { - (Math::random() * (u32::max_value() as f64)) as u32 -} - -pub fn get_random_u64() -> u64 { - let v1: u32 = get_random_u32(); - let v2: u32 = get_random_u32(); - ((v1 as u64) << 32) | ((v2 as u32) as u64) -} - -pub async fn sleep(millis: u32) { - Bindgen.sleep(Duration::from_millis(millis.into())).await -} - -pub fn system_boxed<'a, Out>( - future: impl Future + Send + 'a, -) -> SendPinBoxFutureLifetime<'a, Out> { - Box::pin(future) -} - -pub fn spawn(future: impl Future + Send + 'static) -> MustJoinHandle -where - Out: Send + 'static, -{ - MustJoinHandle::new( - Bindgen - .spawn_handle(future) - .expect("wasm-bindgen-futures spawn should never error out"), - ) -} - -pub fn spawn_local(future: impl Future + 'static) -> MustJoinHandle -where - Out: 'static, -{ - MustJoinHandle::new( - Bindgen - .spawn_handle_local(future) - .expect("wasm-bindgen-futures spawn_local should never error out"), - ) -} - -// pub fn spawn_with_local_set( -// future: impl Future + Send + 'static, -// ) -> MustJoinHandle -// where -// Out: Send + 'static, -// { -// spawn(future) -// } - -pub fn spawn_detached(future: impl Future + Send + 'static) -where - Out: Send + 'static, -{ - Bindgen - .spawn_handle_local(future) - .expect("wasm-bindgen-futures spawn_local should never error out") - .detach() -} - -pub fn interval(freq_ms: u32, callback: F) -> SendPinBoxFuture<()> -where - F: Fn() -> FUT + Send + Sync + 'static, - FUT: Future + Send, -{ - let e = Eventual::new(); - - let ie = e.clone(); - let jh = spawn(Box::pin(async move { - while timeout(freq_ms, ie.instance_clone(())).await.is_err() { - callback().await; - } - })); - - Box::pin(async move { - e.resolve().await; - jh.await; - }) -} - -pub async fn timeout(dur_ms: u32, f: F) -> Result -where - F: Future, -{ - match select(Box::pin(intf::sleep(dur_ms)), Box::pin(f)).await { - Either::Left((_x, _b)) => Err(TimeoutError()), - Either::Right((y, _a)) => Ok(y), - } -} - -// xxx: for now until wasm threads are more stable, and/or we bother with web workers -pub fn get_concurrency() -> u32 { - 1 -} +//use js_sys::*; pub async fn get_outbound_relay_peer() -> Option { // unimplemented! @@ -161,7 +8,7 @@ pub async fn get_outbound_relay_peer() -> Option { } // pub async fn get_pwa_web_server_config() -> { -// if utils::is_browser() { +// if is_browser() { // let win = window().unwrap(); // let doc = win.document().unwrap(); diff --git a/veilid-core/src/intf/wasm/table_store.rs b/veilid-core/src/intf/wasm/table_store.rs index b49481ff..f401220d 100644 --- a/veilid-core/src/intf/wasm/table_store.rs +++ b/veilid-core/src/intf/wasm/table_store.rs @@ -1,7 +1,5 @@ -use super::*; - -use crate::intf::table_db::*; -use crate::xx::*; +use crate::intf::table_db::TableDBInner; +pub use crate::intf::table_db::{TableDB, TableDBTransaction}; use crate::*; use keyvaluedb_web::*; @@ -104,9 +102,11 @@ impl TableStore { let db = Database::open(table_name.clone(), column_count) .await .wrap_err("failed to open tabledb")?; - info!( + trace!( "opened table store '{}' with table name '{:?}' with {} columns", - name, table_name, column_count + name, + table_name, + column_count ); let table_db = TableDB::new(table_name.clone(), self.clone(), db); @@ -136,7 +136,7 @@ impl TableStore { } } - if utils::is_browser() { + if is_browser() { let out = match Database::delete(table_name.clone()).await { Ok(_) => true, Err(_) => false, diff --git a/veilid-core/src/lib.rs b/veilid-core/src/lib.rs index adc8d656..f6cc2f76 100644 --- a/veilid-core/src/lib.rs +++ b/veilid-core/src/lib.rs @@ -1,5 +1,6 @@ #![deny(clippy::all)] #![deny(unused_must_use)] +#![recursion_limit = "256"] cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { @@ -20,7 +21,6 @@ extern crate alloc; mod api_tracing_layer; mod attachment_manager; -mod callback_state_machine; mod core_context; mod crypto; mod intf; @@ -32,17 +32,13 @@ mod veilid_api; #[macro_use] mod veilid_config; mod veilid_layer_filter; -mod veilid_rng; - -#[macro_use] -pub mod xx; pub use self::api_tracing_layer::ApiTracingLayer; -pub use self::attachment_manager::AttachmentState; pub use self::core_context::{api_startup, api_startup_json, UpdateCallback}; pub use self::veilid_api::*; pub use self::veilid_config::*; pub use self::veilid_layer_filter::*; +pub use veilid_tools as tools; pub mod veilid_capnp { include!(concat!(env!("OUT_DIR"), "/proto/veilid_capnp.rs")); @@ -62,7 +58,7 @@ pub fn veilid_version() -> (u32, u32, u32) { } #[cfg(target_os = "android")] -pub use intf::utils::android::{veilid_core_setup_android, veilid_core_setup_android_no_log}; +pub use intf::android::veilid_core_setup_android; pub static DEFAULT_LOG_IGNORE_LIST: [&str; 21] = [ "mio", @@ -87,3 +83,5 @@ pub static DEFAULT_LOG_IGNORE_LIST: [&str; 21] = [ "trust_dns_proto", "attohttpc", ]; + +use veilid_tools::*; diff --git a/veilid-core/src/network_manager/connection_handle.rs b/veilid-core/src/network_manager/connection_handle.rs index 9eeb1b63..3dc0adb5 100644 --- a/veilid-core/src/network_manager/connection_handle.rs +++ b/veilid-core/src/network_manager/connection_handle.rs @@ -2,7 +2,7 @@ use super::*; #[derive(Clone, Debug)] pub struct ConnectionHandle { - id: u64, + id: NetworkConnectionId, descriptor: ConnectionDescriptor, channel: flume::Sender<(Option, Vec)>, } @@ -15,7 +15,7 @@ pub enum ConnectionHandleSendResult { impl ConnectionHandle { pub(super) fn new( - id: u64, + id: NetworkConnectionId, descriptor: ConnectionDescriptor, channel: flume::Sender<(Option, Vec)>, ) -> Self { @@ -26,7 +26,7 @@ impl ConnectionHandle { } } - pub fn connection_id(&self) -> u64 { + pub fn connection_id(&self) -> NetworkConnectionId { self.id } diff --git a/veilid-core/src/network_manager/connection_limits.rs b/veilid-core/src/network_manager/connection_limits.rs index 9c2121c0..c118c4e9 100644 --- a/veilid-core/src/network_manager/connection_limits.rs +++ b/veilid-core/src/network_manager/connection_limits.rs @@ -21,8 +21,8 @@ pub struct ConnectionLimits { max_connection_frequency_per_min: usize, conn_count_by_ip4: BTreeMap, conn_count_by_ip6_prefix: BTreeMap, - conn_timestamps_by_ip4: BTreeMap>, - conn_timestamps_by_ip6_prefix: BTreeMap>, + conn_timestamps_by_ip4: BTreeMap>, + conn_timestamps_by_ip6_prefix: BTreeMap>, } impl ConnectionLimits { @@ -41,14 +41,14 @@ impl ConnectionLimits { } } - fn purge_old_timestamps(&mut self, cur_ts: u64) { + fn purge_old_timestamps(&mut self, cur_ts: Timestamp) { // v4 { let mut dead_keys = Vec::::new(); for (key, value) in &mut self.conn_timestamps_by_ip4 { value.retain(|v| { // keep timestamps that are less than a minute away - cur_ts.saturating_sub(*v) < 60_000_000u64 + cur_ts.saturating_sub(*v) < TimestampDuration::new(60_000_000u64) }); if value.is_empty() { dead_keys.push(*key); @@ -64,7 +64,7 @@ impl ConnectionLimits { for (key, value) in &mut self.conn_timestamps_by_ip6_prefix { value.retain(|v| { // keep timestamps that are less than a minute away - cur_ts.saturating_sub(*v) < 60_000_000u64 + cur_ts.saturating_sub(*v) < TimestampDuration::new(60_000_000u64) }); if value.is_empty() { dead_keys.push(*key); @@ -78,7 +78,7 @@ impl ConnectionLimits { pub fn add(&mut self, addr: IpAddr) -> Result<(), AddressFilterError> { let ipblock = ip_to_ipblock(self.max_connections_per_ip6_prefix_size, addr); - let ts = intf::get_timestamp(); + let ts = get_aligned_timestamp(); self.purge_old_timestamps(ts); @@ -95,7 +95,7 @@ impl ConnectionLimits { let tstamps = &mut self.conn_timestamps_by_ip4.entry(v4).or_default(); tstamps.retain(|v| { // keep timestamps that are less than a minute away - ts.saturating_sub(*v) < 60_000_000u64 + ts.saturating_sub(*v) < TimestampDuration::new(60_000_000u64) }); assert!(tstamps.len() <= self.max_connection_frequency_per_min); if tstamps.len() == self.max_connection_frequency_per_min { @@ -134,7 +134,7 @@ impl ConnectionLimits { pub fn remove(&mut self, addr: IpAddr) -> Result<(), AddressNotInTableError> { let ipblock = ip_to_ipblock(self.max_connections_per_ip6_prefix_size, addr); - let ts = intf::get_timestamp(); + let ts = get_aligned_timestamp(); self.purge_old_timestamps(ts); match ipblock { diff --git a/veilid-core/src/network_manager/connection_manager.rs b/veilid-core/src/network_manager/connection_manager.rs index e83f069d..342925c9 100644 --- a/veilid-core/src/network_manager/connection_manager.rs +++ b/veilid-core/src/network_manager/connection_manager.rs @@ -1,5 +1,4 @@ use super::*; -use crate::xx::*; use connection_table::*; use network_connection::*; use stop_token::future::FutureExt; @@ -49,9 +48,9 @@ impl ConnectionManager { async_processor_jh: MustJoinHandle<()>, ) -> ConnectionManagerInner { ConnectionManagerInner { - next_id: 0, + next_id: 0.into(), stop_source: Some(stop_source), - sender: sender, + sender, async_processor_jh: Some(async_processor_jh), } } @@ -150,7 +149,7 @@ impl ConnectionManager { ) -> EyreResult> { // Get next connection id to use let id = inner.next_id; - inner.next_id += 1; + inner.next_id += 1u64; log_net!( "on_new_protocol_network_connection: id={} prot_conn={:?}", id, @@ -320,7 +319,7 @@ impl ConnectionManager { }; log_net!(debug "get_or_create_connection retries left: {}", retry_count); retry_count -= 1; - intf::sleep(500).await; + sleep(500).await; }); // Add to the connection table @@ -399,7 +398,7 @@ impl ConnectionManager { // Callback from network connection receive loop when it exits // cleans up the entry in the connection table #[instrument(level = "trace", skip(self))] - pub(super) async fn report_connection_finished(&self, connection_id: u64) { + pub(super) async fn report_connection_finished(&self, connection_id: NetworkConnectionId) { // Get channel sender let sender = { let mut inner = self.arc.inner.lock(); diff --git a/veilid-core/src/network_manager/mod.rs b/veilid-core/src/network_manager/mod.rs index 6c87aaf1..923c6ea3 100644 --- a/veilid-core/src/network_manager/mod.rs +++ b/veilid-core/src/network_manager/mod.rs @@ -23,7 +23,7 @@ pub use network_connection::*; use connection_handle::*; use connection_limits::*; use crypto::*; -use futures_util::stream::{FuturesOrdered, FuturesUnordered, StreamExt}; +use futures_util::stream::FuturesUnordered; use hashlink::LruCache; use intf::*; #[cfg(not(target_arch = "wasm32"))] @@ -33,30 +33,18 @@ use routing_table::*; use rpc_processor::*; #[cfg(target_arch = "wasm32")] use wasm::*; -use xx::*; //////////////////////////////////////////////////////////////////////////////////////// -pub const RELAY_MANAGEMENT_INTERVAL_SECS: u32 = 1; -pub const PRIVATE_ROUTE_MANAGEMENT_INTERVAL_SECS: u32 = 1; pub const MAX_MESSAGE_SIZE: usize = MAX_ENVELOPE_SIZE; pub const IPADDR_TABLE_SIZE: usize = 1024; -pub const IPADDR_MAX_INACTIVE_DURATION_US: u64 = 300_000_000u64; // 5 minutes +pub const IPADDR_MAX_INACTIVE_DURATION_US: TimestampDuration = TimestampDuration::new(300_000_000u64); // 5 minutes pub const PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT: usize = 3; pub const PUBLIC_ADDRESS_CHECK_CACHE_SIZE: usize = 8; pub const PUBLIC_ADDRESS_CHECK_TASK_INTERVAL_SECS: u32 = 60; -pub const PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US: u64 = 300_000_000u64; // 5 minutes -pub const PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US: u64 = 3600_000_000u64; // 60 minutes +pub const PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US: TimestampDuration = TimestampDuration::new(300_000_000u64); // 5 minutes +pub const PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US: TimestampDuration = TimestampDuration::new(3600_000_000u64); // 60 minutes pub const BOOT_MAGIC: &[u8; 4] = b"BOOT"; -pub const BOOTSTRAP_TXT_VERSION: u8 = 0; - -#[derive(Clone, Debug)] -pub struct BootstrapRecord { - min_version: u8, - max_version: u8, - dial_info_details: Vec, -} -pub type BootstrapRecordMap = BTreeMap; #[derive(Copy, Clone, Debug, Default)] pub struct ProtocolConfig { @@ -79,7 +67,7 @@ struct NetworkComponents { // Statistics per address #[derive(Clone, Default)] pub struct PerAddressStats { - last_seen_ts: u64, + last_seen_ts: Timestamp, transfer_stats_accounting: TransferStatsAccounting, transfer_stats: TransferStatsDownUp, } @@ -111,7 +99,7 @@ impl Default for NetworkManagerStats { #[derive(Debug)] struct ClientWhitelistEntry { - last_seen_ts: u64, + last_seen_ts: Timestamp, } #[derive(Copy, Clone, Debug)] @@ -150,7 +138,7 @@ struct NetworkManagerInner { public_address_check_cache: BTreeMap>, public_address_inconsistencies_table: - BTreeMap>, + BTreeMap>, } struct NetworkManagerUnlockedInner { @@ -166,13 +154,7 @@ struct NetworkManagerUnlockedInner { update_callback: RwLock>, // Background processes rolling_transfers_task: TickTask, - relay_management_task: TickTask, - private_route_management_task: TickTask, - bootstrap_task: TickTask, - peer_minimum_refresh_task: TickTask, - ping_validator_task: TickTask, public_address_check_task: TickTask, - node_info_update_single_future: MustJoinSingleFuture<()>, } #[derive(Clone)] @@ -197,7 +179,6 @@ impl NetworkManager { block_store: BlockStore, crypto: Crypto, ) -> NetworkManagerUnlockedInner { - let min_peer_refresh_time_ms = config.get().network.dht.min_peer_refresh_time_ms; NetworkManagerUnlockedInner { config, protected_store, @@ -208,13 +189,7 @@ impl NetworkManager { components: RwLock::new(None), update_callback: RwLock::new(None), rolling_transfers_task: TickTask::new(ROLLING_TRANSFERS_INTERVAL_SECS), - relay_management_task: TickTask::new(RELAY_MANAGEMENT_INTERVAL_SECS), - private_route_management_task: TickTask::new(PRIVATE_ROUTE_MANAGEMENT_INTERVAL_SECS), - bootstrap_task: TickTask::new(1), - peer_minimum_refresh_task: TickTask::new_ms(min_peer_refresh_time_ms), - ping_validator_task: TickTask::new(1), public_address_check_task: TickTask::new(PUBLIC_ADDRESS_CHECK_TASK_INTERVAL_SECS), - node_info_update_single_future: MustJoinSingleFuture::new(), } } @@ -235,116 +210,9 @@ impl NetworkManager { crypto, )), }; - // Set rolling transfers tick task - { - let this2 = this.clone(); - this.unlocked_inner - .rolling_transfers_task - .set_routine(move |s, l, t| { - Box::pin( - this2 - .clone() - .rolling_transfers_task_routine(s, l, t) - .instrument(trace_span!( - parent: None, - "NetworkManager rolling transfers task routine" - )), - ) - }); - } - // Set relay management tick task - { - let this2 = this.clone(); - this.unlocked_inner - .relay_management_task - .set_routine(move |s, l, t| { - Box::pin( - this2 - .clone() - .relay_management_task_routine(s, l, t) - .instrument(trace_span!(parent: None, "relay management task routine")), - ) - }); - } - // Set private route management tick task - { - let this2 = this.clone(); - this.unlocked_inner - .private_route_management_task - .set_routine(move |s, l, t| { - Box::pin( - this2 - .clone() - .private_route_management_task_routine(s, l, t) - .instrument(trace_span!( - parent: None, - "private route management task routine" - )), - ) - }); - } - // Set bootstrap tick task - { - let this2 = this.clone(); - this.unlocked_inner - .bootstrap_task - .set_routine(move |s, _l, _t| { - Box::pin( - this2 - .clone() - .bootstrap_task_routine(s) - .instrument(trace_span!(parent: None, "bootstrap task routine")), - ) - }); - } - // Set peer minimum refresh tick task - { - let this2 = this.clone(); - this.unlocked_inner - .peer_minimum_refresh_task - .set_routine(move |s, _l, _t| { - Box::pin( - this2 - .clone() - .peer_minimum_refresh_task_routine(s) - .instrument(trace_span!( - parent: None, - "peer minimum refresh task routine" - )), - ) - }); - } - // Set ping validator tick task - { - let this2 = this.clone(); - this.unlocked_inner - .ping_validator_task - .set_routine(move |s, l, t| { - Box::pin( - this2 - .clone() - .ping_validator_task_routine(s, l, t) - .instrument(trace_span!(parent: None, "ping validator task routine")), - ) - }); - } - // Set public address check task - { - let this2 = this.clone(); - this.unlocked_inner - .public_address_check_task - .set_routine(move |s, l, t| { - Box::pin( - this2 - .clone() - .public_address_check_task_routine(s, l, t) - .instrument(trace_span!( - parent: None, - "public address check task routine" - )), - ) - }); - } + + this.start_tasks(); + this } pub fn config(&self) -> VeilidConfig { @@ -412,6 +280,14 @@ impl NetworkManager { .connection_manager .clone() } + pub fn update_callback(&self) -> UpdateCallback { + self.unlocked_inner + .update_callback + .read() + .as_ref() + .unwrap() + .clone() + } #[instrument(level = "debug", skip_all, err)] pub async fn init(&self, update_callback: UpdateCallback) -> EyreResult<()> { @@ -492,36 +368,7 @@ impl NetworkManager { debug!("starting network manager shutdown"); // Cancel all tasks - debug!("stopping rolling transfers task"); - if let Err(e) = self.unlocked_inner.rolling_transfers_task.stop().await { - warn!("rolling_transfers_task not stopped: {}", e); - } - debug!("stopping relay management task"); - if let Err(e) = self.unlocked_inner.relay_management_task.stop().await { - warn!("relay_management_task not stopped: {}", e); - } - debug!("stopping bootstrap task"); - if let Err(e) = self.unlocked_inner.bootstrap_task.stop().await { - error!("bootstrap_task not stopped: {}", e); - } - debug!("stopping peer minimum refresh task"); - if let Err(e) = self.unlocked_inner.peer_minimum_refresh_task.stop().await { - error!("peer_minimum_refresh_task not stopped: {}", e); - } - debug!("stopping ping_validator task"); - if let Err(e) = self.unlocked_inner.ping_validator_task.stop().await { - error!("ping_validator_task not stopped: {}", e); - } - debug!("stopping node info update singlefuture"); - if self - .unlocked_inner - .node_info_update_single_future - .join() - .await - .is_err() - { - error!("node_info_update_single_future not stopped"); - } + self.stop_tasks().await; // Shutdown network components if they started up debug!("shutting down network components"); @@ -553,11 +400,11 @@ impl NetworkManager { let mut inner = self.inner.lock(); match inner.client_whitelist.entry(client) { hashlink::lru_cache::Entry::Occupied(mut entry) => { - entry.get_mut().last_seen_ts = intf::get_timestamp() + entry.get_mut().last_seen_ts = get_aligned_timestamp() } hashlink::lru_cache::Entry::Vacant(entry) => { entry.insert(ClientWhitelistEntry { - last_seen_ts: intf::get_timestamp(), + last_seen_ts: get_aligned_timestamp(), }); } } @@ -569,7 +416,7 @@ impl NetworkManager { match inner.client_whitelist.entry(client) { hashlink::lru_cache::Entry::Occupied(mut entry) => { - entry.get_mut().last_seen_ts = intf::get_timestamp(); + entry.get_mut().last_seen_ts = get_aligned_timestamp(); true } hashlink::lru_cache::Entry::Vacant(_) => false, @@ -579,7 +426,7 @@ impl NetworkManager { pub fn purge_client_whitelist(&self) { let timeout_ms = self.with_config(|c| c.network.client_whitelist_timeout_ms); let mut inner = self.inner.lock(); - let cutoff_timestamp = intf::get_timestamp() - ((timeout_ms as u64) * 1000u64); + let cutoff_timestamp = get_aligned_timestamp() - TimestampDuration::new((timeout_ms as u64) * 1000u64); // Remove clients from the whitelist that haven't been since since our whitelist timeout while inner .client_whitelist @@ -597,58 +444,19 @@ impl NetworkManager { net.needs_restart() } - pub async fn tick(&self) -> EyreResult<()> { - let routing_table = self.routing_table(); - let net = self.net(); - let receipt_manager = self.receipt_manager(); - - // Run the rolling transfers task - self.unlocked_inner.rolling_transfers_task.tick().await?; - - // Run the relay management task - self.unlocked_inner.relay_management_task.tick().await?; - - // See how many live PublicInternet entries we have - let live_public_internet_entry_count = routing_table.get_entry_count( - RoutingDomain::PublicInternet.into(), - BucketEntryState::Unreliable, - ); - let min_peer_count = self.with_config(|c| c.network.dht.min_peer_count as usize); - - // If none, then add the bootstrap nodes to it - if live_public_internet_entry_count == 0 { - self.unlocked_inner.bootstrap_task.tick().await?; - } - // If we still don't have enough peers, find nodes until we do - else if !self.unlocked_inner.bootstrap_task.is_running() - && live_public_internet_entry_count < min_peer_count - { - self.unlocked_inner.peer_minimum_refresh_task.tick().await?; - } - - // Ping validate some nodes to groom the table - self.unlocked_inner.ping_validator_task.tick().await?; - - // Run the routing table tick - routing_table.tick().await?; - - // Run the low level network tick - net.tick().await?; - - // Run the receipt manager tick - receipt_manager.tick().await?; - - // Purge the client whitelist - self.purge_client_whitelist(); - - Ok(()) - } - /// Get our node's capabilities in the PublicInternet routing domain fn generate_public_internet_node_status(&self) -> PublicInternetNodeStatus { - let own_peer_info = self + let Some(own_peer_info) = self .routing_table() - .get_own_peer_info(RoutingDomain::PublicInternet); + .get_own_peer_info(RoutingDomain::PublicInternet) else { + return PublicInternetNodeStatus { + will_route: false, + will_tunnel: false, + will_signal: false, + will_relay: false, + will_validate_dial_info: false, + }; + }; let own_node_info = own_peer_info.signed_node_info.node_info(); let will_route = own_node_info.can_inbound_relay(); // xxx: eventually this may have more criteria added @@ -667,9 +475,14 @@ impl NetworkManager { } /// Get our node's capabilities in the LocalNetwork routing domain fn generate_local_network_node_status(&self) -> LocalNetworkNodeStatus { - let own_peer_info = self + let Some(own_peer_info) = self .routing_table() - .get_own_peer_info(RoutingDomain::LocalNetwork); + .get_own_peer_info(RoutingDomain::LocalNetwork) else { + return LocalNetworkNodeStatus { + will_relay: false, + will_validate_dial_info: false, + }; + }; let own_node_info = own_peer_info.signed_node_info.node_info(); @@ -713,7 +526,7 @@ impl NetworkManager { .wrap_err("failed to generate signed receipt")?; // Record the receipt for later - let exp_ts = intf::get_timestamp() + expiration_us; + let exp_ts = get_aligned_timestamp() + expiration_us; receipt_manager.record_receipt(receipt, exp_ts, expected_returns, callback); Ok(out) @@ -737,7 +550,7 @@ impl NetworkManager { .wrap_err("failed to generate signed receipt")?; // Record the receipt for later - let exp_ts = intf::get_timestamp() + expiration_us; + let exp_ts = get_aligned_timestamp() + expiration_us; let eventual = SingleShotEventual::new(Some(ReceiptEvent::Cancelled)); let instance = eventual.instance(); receipt_manager.record_single_shot_receipt(receipt, exp_ts, eventual); @@ -904,7 +717,7 @@ impl NetworkManager { // XXX: do we need a delay here? or another hole punch packet? // Set the hole punch as our 'last connection' to ensure we return the receipt over the direct hole punch - peer_nr.set_last_connection(connection_descriptor, intf::get_timestamp()); + peer_nr.set_last_connection(connection_descriptor, get_aligned_timestamp()); // Return the receipt using the same dial info send the receipt to it rpc.rpc_call_return_receipt(Destination::direct(peer_nr), receipt) @@ -928,7 +741,7 @@ impl NetworkManager { let node_id_secret = routing_table.node_id_secret(); // Get timestamp, nonce - let ts = intf::get_timestamp(); + let ts = get_aligned_timestamp(); let nonce = Crypto::get_random_nonce(); // Encode envelope @@ -1001,7 +814,7 @@ impl NetworkManager { // Send receipt directly log_net!(debug "send_out_of_band_receipt: dial_info={}", dial_info); - network_result_value_or_log!(debug self + network_result_value_or_log!(self .net() .send_data_unbound_to_dial_info(dial_info, rcpt_data) .await? => { @@ -1031,10 +844,17 @@ impl NetworkManager { ); let (receipt, eventual_value) = self.generate_single_shot_receipt(receipt_timeout, [])?; + // Get target routing domain + let Some(routing_domain) = target_nr.best_routing_domain() else { + return Ok(NetworkResult::no_connection_other("No routing domain for target")); + }; + // Get our peer info - let peer_info = self + let Some(peer_info) = self .routing_table() - .get_own_peer_info(RoutingDomain::PublicInternet); + .get_own_peer_info(routing_domain) else { + return Ok(NetworkResult::no_connection_other("Own peer info not available")); + }; // Issue the signal let rpc = self.rpc_processor(); @@ -1098,17 +918,11 @@ impl NetworkManager { data: Vec, ) -> EyreResult> { // Ensure we are filtered down to UDP (the only hole punch protocol supported today) - // and only in the PublicInternet routing domain assert!(target_nr .filter_ref() .map(|nrf| nrf.dial_info_filter.protocol_type_set == ProtocolTypeSet::only(ProtocolType::UDP)) .unwrap_or_default()); - assert!(target_nr - .filter_ref() - .map(|nrf| nrf.routing_domain_set - == RoutingDomainSet::only(RoutingDomain::PublicInternet)) - .unwrap_or_default()); // Build a return receipt for the signal let receipt_timeout = ms_to_us( @@ -1119,10 +933,18 @@ impl NetworkManager { .hole_punch_receipt_time_ms, ); let (receipt, eventual_value) = self.generate_single_shot_receipt(receipt_timeout, [])?; + + // Get target routing domain + let Some(routing_domain) = target_nr.best_routing_domain() else { + return Ok(NetworkResult::no_connection_other("No routing domain for target")); + }; + // Get our peer info - let peer_info = self + let Some(peer_info) = self .routing_table() - .get_own_peer_info(RoutingDomain::PublicInternet); + .get_own_peer_info(routing_domain) else { + return Ok(NetworkResult::no_connection_other("Own peer info not available")); + }; // Get the udp direct dialinfo for the hole punch let hole_punch_did = target_nr @@ -1214,7 +1036,8 @@ impl NetworkManager { }; // Node A is our own node - let peer_a = routing_table.get_own_peer_info(routing_domain); + // Use whatever node info we've calculated so far + let peer_a = routing_table.get_best_effort_own_peer_info(routing_domain); // Node B is the target node let peer_b = match target_node_ref.make_peer_info(routing_domain) { @@ -1313,8 +1136,7 @@ impl NetworkManager { // ); // Update timestamp for this last connection since we just sent to it - node_ref - .set_last_connection(connection_descriptor, intf::get_timestamp()); + node_ref.set_last_connection(connection_descriptor, get_aligned_timestamp()); return Ok(NetworkResult::value(SendDataKind::Existing( connection_descriptor, @@ -1346,7 +1168,7 @@ impl NetworkManager { this.net().send_data_to_dial_info(dial_info, data).await? ); // If we connected to this node directly, save off the last connection so we can use it again - node_ref.set_last_connection(connection_descriptor, intf::get_timestamp()); + node_ref.set_last_connection(connection_descriptor, get_aligned_timestamp()); Ok(NetworkResult::value(SendDataKind::Direct( connection_descriptor, @@ -1421,7 +1243,7 @@ impl NetworkManager { let timeout_ms = self.with_config(|c| c.network.rpc.timeout_ms); // Send boot magic to requested peer address let data = BOOT_MAGIC.to_vec(); - let out_data: Vec = network_result_value_or_log!(debug self + let out_data: Vec = network_result_value_or_log!(self .net() .send_recv_data_unbound_to_dial_info(dial_info, data, timeout_ms) .await? => @@ -1463,7 +1285,7 @@ impl NetworkManager { // Network accounting self.stats_packet_rcvd( connection_descriptor.remote_address().to_ip_addr(), - data.len() as u64, + ByteCount::new(data.len() as u64), ); // If this is a zero length packet, just drop it, because these are used for hole punching @@ -1493,13 +1315,13 @@ impl NetworkManager { // Is this a direct bootstrap request instead of an envelope? if data[0..4] == *BOOT_MAGIC { - network_result_value_or_log!(debug self.handle_boot_request(connection_descriptor).await? => {}); + network_result_value_or_log!(self.handle_boot_request(connection_descriptor).await? => {}); return Ok(true); } // Is this an out-of-band receipt instead of an envelope? if data[0..4] == *RECEIPT_MAGIC { - network_result_value_or_log!(debug self.handle_out_of_band_receipt(data).await => {}); + network_result_value_or_log!(self.handle_out_of_band_receipt(data).await => {}); return Ok(true); } @@ -1515,28 +1337,28 @@ impl NetworkManager { // Get timestamp range let (tsbehind, tsahead) = self.with_config(|c| { ( - c.network.rpc.max_timestamp_behind_ms.map(ms_to_us), - c.network.rpc.max_timestamp_ahead_ms.map(ms_to_us), + c.network.rpc.max_timestamp_behind_ms.map(ms_to_us).map(TimestampDuration::new), + c.network.rpc.max_timestamp_ahead_ms.map(ms_to_us).map(TimestampDuration::new), ) }); // Validate timestamp isn't too old - let ts = intf::get_timestamp(); + let ts = get_aligned_timestamp(); let ets = envelope.get_timestamp(); if let Some(tsbehind) = tsbehind { - if tsbehind > 0 && (ts > ets && ts - ets > tsbehind) { + if tsbehind.as_u64() != 0 && (ts > ets && ts.saturating_sub(ets) > tsbehind) { log_net!(debug "envelope time was too far in the past: {}ms ", - timestamp_to_secs(ts - ets) * 1000f64 + timestamp_to_secs(ts.saturating_sub(ets).as_u64()) * 1000f64 ); return Ok(false); } } if let Some(tsahead) = tsahead { - if tsahead > 0 && (ts < ets && ets - ts > tsahead) { + if tsahead.as_u64() != 0 && (ts < ets && ets.saturating_sub(ts) > tsahead) { log_net!(debug "envelope time was too far in the future: {}ms", - timestamp_to_secs(ets - ts) * 1000f64 + timestamp_to_secs(ets.saturating_sub(ts).as_u64()) * 1000f64 ); return Ok(false); } @@ -1557,9 +1379,13 @@ impl NetworkManager { let some_relay_nr = if self.check_client_whitelist(sender_id) { // Full relay allowed, do a full resolve_node - rpc.resolve_node(recipient_id).await.wrap_err( - "failed to resolve recipient node for relay, dropping outbound relayed packet", - )? + match rpc.resolve_node(recipient_id).await { + Ok(v) => v, + Err(e) => { + log_net!(debug "failed to resolve recipient node for relay, dropping outbound relayed packet: {}" ,e); + return Ok(false); + } + } } else { // If this is not a node in the client whitelist, only allow inbound relay // which only performs a lightweight lookup before passing the packet back out @@ -1574,9 +1400,14 @@ impl NetworkManager { if let Some(relay_nr) = some_relay_nr { // Relay the packet to the desired destination log_net!("relaying {} bytes to {}", data.len(), relay_nr); - network_result_value_or_log!(debug self.send_data(relay_nr, data.to_vec()) - .await - .wrap_err("failed to forward envelope")? => { + network_result_value_or_log!(match self.send_data(relay_nr, data.to_vec()) + .await { + Ok(v) => v, + Err(e) => { + log_net!(debug "failed to forward envelope: {}" ,e); + return Ok(false); + } + } => { return Ok(false); } ); @@ -1589,10 +1420,15 @@ impl NetworkManager { let node_id_secret = routing_table.node_id_secret(); // Decrypt the envelope body - // xxx: punish nodes that send messages that fail to decrypt eventually - let body = envelope - .decrypt_body(self.crypto(), data, &node_id_secret) - .wrap_err("failed to decrypt envelope body")?; + let body = match envelope + .decrypt_body(self.crypto(), data, &node_id_secret) { + Ok(v) => v, + Err(e) => { + log_net!(debug "failed to decrypt envelope body: {}",e); + // xxx: punish nodes that send messages that fail to decrypt eventually + return Ok(false); + } + }; // Cache the envelope information in the routing table let source_noderef = match routing_table.register_node_with_existing_connection( @@ -1625,7 +1461,7 @@ impl NetworkManager { } // Callbacks from low level network for statistics gathering - pub fn stats_packet_sent(&self, addr: IpAddr, bytes: u64) { + pub fn stats_packet_sent(&self, addr: IpAddr, bytes: ByteCount) { let inner = &mut *self.inner.lock(); inner .stats @@ -1641,7 +1477,7 @@ impl NetworkManager { .add_up(bytes); } - pub fn stats_packet_rcvd(&self, addr: IpAddr, bytes: u64) { + pub fn stats_packet_rcvd(&self, addr: IpAddr, bytes: ByteCount) { let inner = &mut *self.inner.lock(); inner .stats @@ -1675,9 +1511,10 @@ impl NetworkManager { if !has_state { return VeilidStateNetwork { started: false, - bps_down: 0, - bps_up: 0, + bps_down: 0.into(), + bps_up: 0.into(), peers: Vec::new(), + }; } let routing_table = self.routing_table(); @@ -1828,7 +1665,7 @@ impl NetworkManager { // public dialinfo let inconsistent = if inconsistencies.len() >= PUBLIC_ADDRESS_CHANGE_DETECTION_COUNT { - let exp_ts = intf::get_timestamp() + PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US; + let exp_ts = get_aligned_timestamp() + PUBLIC_ADDRESS_INCONSISTENCY_TIMEOUT_US; for i in &inconsistencies { pait.insert(*i, exp_ts); } @@ -1841,8 +1678,8 @@ impl NetworkManager { .public_address_inconsistencies_table .entry(key) .or_insert_with(|| HashMap::new()); - let exp_ts = intf::get_timestamp() - + PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US; + let exp_ts = + get_aligned_timestamp() + PUBLIC_ADDRESS_INCONSISTENCY_PUNISHMENT_TIMEOUT_US; for i in inconsistencies { pait.insert(i, exp_ts); } @@ -1860,7 +1697,7 @@ impl NetworkManager { // } inconsistent - } else { + } else if matches!(public_internet_network_class, NetworkClass::OutboundOnly) { // If we are currently outbound only, we don't have any public dial info // but if we are starting to see consistent socket address from multiple reporting peers // then we may be become inbound capable, so zap the network class so we can re-detect it and any public dial info @@ -1888,6 +1725,10 @@ impl NetworkManager { } } consistent + } else { + // If we are a webapp we never do this. + // If we have invalid network class, then public address detection is already going to happen via the network_class_discovery task + false }; if needs_public_address_detection { @@ -1910,62 +1751,4 @@ impl NetworkManager { } } - // Inform routing table entries that our dial info has changed - pub async fn send_node_info_updates(&self, routing_domain: RoutingDomain, all: bool) { - let this = self.clone(); - - // Run in background only once - let _ = self - .clone() - .unlocked_inner - .node_info_update_single_future - .single_spawn( - async move { - // Only update if we actually have valid signed node info for this routing domain - if !this.routing_table().has_valid_own_node_info(routing_domain) { - trace!( - "not sending node info update because our network class is not yet valid" - ); - return; - } - - // Get the list of refs to all nodes to update - let cur_ts = intf::get_timestamp(); - let node_refs = - this.routing_table() - .get_nodes_needing_updates(routing_domain, cur_ts, all); - - // Send the updates - log_net!(debug "Sending node info updates to {} nodes", node_refs.len()); - let mut unord = FuturesUnordered::new(); - for nr in node_refs { - let rpc = this.rpc_processor(); - unord.push( - async move { - // Update the node - if let Err(e) = rpc - .rpc_call_node_info_update(nr.clone(), routing_domain) - .await - { - // Not fatal, but we should be able to see if this is happening - trace!("failed to send node info update to {:?}: {}", nr, e); - return; - } - - // Mark the node as having seen our node info - nr.set_seen_our_node_info(routing_domain); - } - .instrument(Span::current()), - ); - } - - // Wait for futures to complete - while unord.next().await.is_some() {} - - log_rtab!(debug "Finished sending node updates"); - } - .instrument(Span::current()), - ) - .await; - } } diff --git a/veilid-core/src/network_manager/native/igd_manager.rs b/veilid-core/src/network_manager/native/igd_manager.rs index fe06c70f..ecf60618 100644 --- a/veilid-core/src/network_manager/native/igd_manager.rs +++ b/veilid-core/src/network_manager/native/igd_manager.rs @@ -1,12 +1,12 @@ use super::*; -use crate::xx::*; use igd::*; use std::net::UdpSocket; + const UPNP_GATEWAY_DETECT_TIMEOUT_MS: u32 = 5_000; const UPNP_MAPPING_LIFETIME_MS: u32 = 120_000; const UPNP_MAPPING_ATTEMPTS: u32 = 3; -const UPNP_MAPPING_LIFETIME_US:u64 = (UPNP_MAPPING_LIFETIME_MS as u64) * 1000u64; +const UPNP_MAPPING_LIFETIME_US:TimestampDuration = TimestampDuration::new(UPNP_MAPPING_LIFETIME_MS as u64 * 1000u64); #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] struct PortMapKey { @@ -19,8 +19,8 @@ struct PortMapKey { struct PortMapValue { ext_ip: IpAddr, mapped_port: u16, - timestamp: u64, - renewal_lifetime: u64, + timestamp: Timestamp, + renewal_lifetime: TimestampDuration, renewal_attempts: u32, } @@ -177,7 +177,7 @@ impl IGDManager { mapped_port: u16, ) -> Option<()> { let this = self.clone(); - intf::blocking_wrapper(move || { + blocking_wrapper(move || { let mut inner = this.inner.lock(); // If we already have this port mapped, just return the existing portmap @@ -216,7 +216,7 @@ impl IGDManager { expected_external_address: Option, ) -> Option { let this = self.clone(); - intf::blocking_wrapper(move || { + blocking_wrapper(move || { let mut inner = this.inner.lock(); // If we already have this port mapped, just return the existing portmap @@ -276,7 +276,7 @@ impl IGDManager { }; // Add to mapping list to keep alive - let timestamp = intf::get_timestamp(); + let timestamp = get_aligned_timestamp(); inner.port_maps.insert(PortMapKey { llpt, at, @@ -285,7 +285,7 @@ impl IGDManager { ext_ip, mapped_port, timestamp, - renewal_lifetime: (UPNP_MAPPING_LIFETIME_MS / 2) as u64 * 1000u64, + renewal_lifetime: ((UPNP_MAPPING_LIFETIME_MS / 2) as u64 * 1000u64).into(), renewal_attempts: 0, }); @@ -302,7 +302,7 @@ impl IGDManager { let mut renews: Vec<(PortMapKey, PortMapValue)> = Vec::new(); { let inner = self.inner.lock(); - let now = intf::get_timestamp(); + let now = get_aligned_timestamp(); for (k, v) in &inner.port_maps { let mapping_lifetime = now.saturating_sub(v.timestamp); @@ -324,7 +324,7 @@ impl IGDManager { } let this = self.clone(); - intf::blocking_wrapper(move || { + blocking_wrapper(move || { let mut inner = this.inner.lock(); // Process full renewals @@ -357,8 +357,8 @@ impl IGDManager { inner.port_maps.insert(k, PortMapValue { ext_ip: v.ext_ip, mapped_port, - timestamp: intf::get_timestamp(), - renewal_lifetime: (UPNP_MAPPING_LIFETIME_MS / 2) as u64 * 1000u64, + timestamp: get_aligned_timestamp(), + renewal_lifetime: TimestampDuration::new((UPNP_MAPPING_LIFETIME_MS / 2) as u64 * 1000u64), renewal_attempts: 0, }); }, @@ -398,8 +398,8 @@ impl IGDManager { inner.port_maps.insert(k, PortMapValue { ext_ip: v.ext_ip, mapped_port: v.mapped_port, - timestamp: intf::get_timestamp(), - renewal_lifetime: (UPNP_MAPPING_LIFETIME_MS / 2) as u64 * 1000u64, + timestamp: get_aligned_timestamp(), + renewal_lifetime: ((UPNP_MAPPING_LIFETIME_MS / 2) as u64 * 1000u64).into(), renewal_attempts: 0, }); }, @@ -407,7 +407,7 @@ impl IGDManager { log_net!(debug "failed to renew mapped port {:?} -> {:?}: {}", v, k, e); // Get closer to the maximum renewal timeline by a factor of two each time - v.renewal_lifetime = (v.renewal_lifetime + UPNP_MAPPING_LIFETIME_US) / 2; + v.renewal_lifetime = (v.renewal_lifetime + UPNP_MAPPING_LIFETIME_US) / 2u64; v.renewal_attempts += 1; // Store new value to try again diff --git a/veilid-core/src/network_manager/native/mod.rs b/veilid-core/src/network_manager/native/mod.rs index f13e3fb7..c38f799b 100644 --- a/veilid-core/src/network_manager/native/mod.rs +++ b/veilid-core/src/network_manager/native/mod.rs @@ -1,5 +1,4 @@ mod igd_manager; -mod natpmp_manager; mod network_class_discovery; mod network_tcp; mod network_udp; @@ -9,12 +8,12 @@ mod start_protocols; use super::*; use crate::routing_table::*; use connection_manager::*; +use network_interfaces::*; use network_tcp::*; use protocol::tcp::RawTcpProtocolHandler; use protocol::udp::RawUdpProtocolHandler; use protocol::ws::WebsocketProtocolHandler; pub use protocol::*; -use utils::network_interfaces::*; use async_tls::TlsAcceptor; use futures_util::StreamExt; @@ -94,11 +93,9 @@ struct NetworkUnlockedInner { update_network_class_task: TickTask, network_interfaces_task: TickTask, upnp_task: TickTask, - natpmp_task: TickTask, // Managers igd_manager: igd_manager::IGDManager, - natpmp_manager: natpmp_manager::NATPMPManager, } #[derive(Clone)] @@ -150,9 +147,7 @@ impl Network { update_network_class_task: TickTask::new(1), network_interfaces_task: TickTask::new(5), upnp_task: TickTask::new(1), - natpmp_task: TickTask::new(1), igd_manager: igd_manager::IGDManager::new(config.clone()), - natpmp_manager: natpmp_manager::NATPMPManager::new(config), } } @@ -196,13 +191,6 @@ impl Network { .upnp_task .set_routine(move |s, l, t| Box::pin(this2.clone().upnp_task_routine(s, l, t))); } - // Set natpmp tick task - { - let this2 = this.clone(); - this.unlocked_inner - .natpmp_task - .set_routine(move |s, l, t| Box::pin(this2.clone().natpmp_task_routine(s, l, t))); - } this } @@ -418,7 +406,7 @@ impl Network { } // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), data_len as u64); + .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::Value(())) } @@ -452,7 +440,7 @@ impl Network { .await .wrap_err("send message failure")?); self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), data_len as u64); + .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); // receive single response let mut out = vec![0u8; MAX_MESSAGE_SIZE]; @@ -466,7 +454,7 @@ impl Network { let recv_socket_addr = recv_addr.remote_address().to_socket_addr(); self.network_manager() - .stats_packet_rcvd(recv_socket_addr.ip(), recv_len as u64); + .stats_packet_rcvd(recv_socket_addr.ip(), ByteCount::new(recv_len as u64)); // if the from address is not the same as the one we sent to, then drop this if recv_socket_addr != peer_socket_addr { @@ -493,7 +481,7 @@ impl Network { network_result_try!(pnc.send(data).await.wrap_err("send failure")?); self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), data_len as u64); + .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); let out = network_result_try!(network_result_try!(timeout(timeout_ms, pnc.recv()) .await @@ -501,7 +489,7 @@ impl Network { .wrap_err("recv failure")?); self.network_manager() - .stats_packet_rcvd(dial_info.to_ip_addr(), out.len() as u64); + .stats_packet_rcvd(dial_info.to_ip_addr(), ByteCount::new(out.len() as u64)); Ok(NetworkResult::Value(out)) } @@ -524,14 +512,14 @@ impl Network { &peer_socket_addr, &descriptor.local().map(|sa| sa.to_socket_addr()), ) { - network_result_value_or_log!(debug ph.clone() + network_result_value_or_log!(ph.clone() .send_message(data.clone(), peer_socket_addr) .await .wrap_err("sending data to existing conection")? => { return Ok(Some(data)); } ); // Network accounting self.network_manager() - .stats_packet_sent(peer_socket_addr.ip(), data_len as u64); + .stats_packet_sent(peer_socket_addr.ip(), ByteCount::new(data_len as u64)); // Data was consumed return Ok(None); @@ -548,7 +536,7 @@ impl Network { // Network accounting self.network_manager().stats_packet_sent( descriptor.remote().to_socket_addr().ip(), - data_len as u64, + ByteCount::new(data_len as u64), ); // Data was consumed @@ -607,7 +595,7 @@ impl Network { // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), data_len as u64); + .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::value(connection_descriptor)) } @@ -722,8 +710,8 @@ impl Network { } ProtocolConfig { - inbound, outbound, + inbound, family_global, family_local, } @@ -770,13 +758,13 @@ impl Network { // if we have static public dialinfo, upgrade our network class editor_public_internet.setup_network( - protocol_config.inbound, protocol_config.outbound, + protocol_config.inbound, protocol_config.family_global, ); editor_local_network.setup_network( - protocol_config.inbound, protocol_config.outbound, + protocol_config.inbound, protocol_config.family_local, ); let detect_address_changes = { @@ -843,13 +831,13 @@ impl Network { debug!("clearing dial info"); let mut editor = routing_table.edit_routing_domain(RoutingDomain::PublicInternet); - editor.disable_node_info_updates(); editor.clear_dial_info_details(); + editor.set_network_class(None); editor.commit().await; let mut editor = routing_table.edit_routing_domain(RoutingDomain::LocalNetwork); - editor.disable_node_info_updates(); editor.clear_dial_info_details(); + editor.set_network_class(None); editor.commit().await; // Reset state including network class @@ -904,31 +892,11 @@ impl Network { Ok(()) } - #[instrument(level = "trace", skip(self), err)] - pub async fn natpmp_task_routine( - self, - stop_token: StopToken, - _l: u64, - _t: u64, - ) -> EyreResult<()> { - if !self.unlocked_inner.natpmp_manager.tick().await? { - info!("natpmp failed, restarting local network"); - let mut inner = self.inner.lock(); - inner.network_needs_restart = true; - } - - Ok(()) - } - pub async fn tick(&self) -> EyreResult<()> { - let (detect_address_changes, upnp, natpmp) = { + let (detect_address_changes, upnp) = { let config = self.network_manager().config(); let c = config.get(); - ( - c.network.detect_address_changes, - c.network.upnp, - c.network.natpmp, - ) + (c.network.detect_address_changes, c.network.upnp) }; // If we need to figure out our network class, tick the task for it @@ -962,11 +930,6 @@ impl Network { self.unlocked_inner.upnp_task.tick().await?; } - // If we need to tick natpmp, do it - if natpmp && !self.needs_restart() { - self.unlocked_inner.natpmp_task.tick().await?; - } - Ok(()) } } diff --git a/veilid-core/src/network_manager/native/natpmp_manager.rs b/veilid-core/src/network_manager/native/natpmp_manager.rs deleted file mode 100644 index 4342abfc..00000000 --- a/veilid-core/src/network_manager/native/natpmp_manager.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::*; - -pub struct NATPMPManager { - config: VeilidConfig, -} - -impl NATPMPManager { - // - - pub fn new(config: VeilidConfig) -> Self { - Self { config } - } - - pub async fn tick(&self) -> EyreResult { - // xxx - Ok(true) - } -} diff --git a/veilid-core/src/network_manager/native/network_class_discovery.rs b/veilid-core/src/network_manager/native/network_class_discovery.rs index 8c24687d..86acc197 100644 --- a/veilid-core/src/network_manager/native/network_class_discovery.rs +++ b/veilid-core/src/network_manager/native/network_class_discovery.rs @@ -83,7 +83,7 @@ impl DiscoveryContext { async fn request_public_address(&self, node_ref: NodeRef) -> Option { let rpc = self.routing_table.rpc_processor(); - let res = network_result_value_or_log!(debug match rpc.rpc_call_status(Destination::direct(node_ref.clone())).await { + let res = network_result_value_or_log!(match rpc.rpc_call_status(Destination::direct(node_ref.clone())).await { Ok(v) => v, Err(e) => { log_net!(error @@ -275,7 +275,7 @@ impl DiscoveryContext { LowLevelProtocolType::UDP => "udp", LowLevelProtocolType::TCP => "tcp", }); - intf::sleep(PORT_MAP_VALIDATE_DELAY_MS).await + sleep(PORT_MAP_VALIDATE_DELAY_MS).await } else { break; } @@ -304,9 +304,9 @@ impl DiscoveryContext { #[instrument(level = "trace", skip(self), ret)] async fn try_port_mapping(&self) -> Option { - let (enable_upnp, _enable_natpmp) = { + let enable_upnp = { let c = self.net.config.get(); - (c.network.upnp, c.network.natpmp) + c.network.upnp }; if enable_upnp { @@ -434,15 +434,6 @@ impl DiscoveryContext { return Ok(true); } - // XXX: is this necessary? - // Redo our external_1 dial info detection because a failed port mapping attempt - // may cause it to become invalid - // Get our external address from some fast node, call it node 1 - // if !self.protocol_get_external_address_1().await { - // // If we couldn't get an external address, then we should just try the whole network class detection again later - // return Ok(false); - // } - // Get the external dial info for our use here let (node_1, external_1_dial_info, external_1_address, protocol_type, address_type) = { let inner = self.inner.lock(); diff --git a/veilid-core/src/network_manager/native/network_tcp.rs b/veilid-core/src/network_manager/native/network_tcp.rs index ebca4378..b537160a 100644 --- a/veilid-core/src/network_manager/native/network_tcp.rs +++ b/veilid-core/src/network_manager/native/network_tcp.rs @@ -58,7 +58,7 @@ impl Network { // Don't waste more than N seconds getting it though, in case someone // is trying to DoS us with a bunch of connections or something // read a chunk of the stream - intf::timeout( + timeout( tls_connection_initial_timeout_ms, ps.peek_exact(&mut first_packet), ) diff --git a/veilid-core/src/network_manager/native/network_udp.rs b/veilid-core/src/network_manager/native/network_udp.rs index b00bf643..71bb9ece 100644 --- a/veilid-core/src/network_manager/native/network_udp.rs +++ b/veilid-core/src/network_manager/native/network_udp.rs @@ -10,7 +10,7 @@ impl Network { c.network.protocol.udp.socket_pool_size }; if task_count == 0 { - task_count = intf::get_concurrency() / 2; + task_count = get_concurrency() / 2; if task_count == 0 { task_count = 1; } @@ -65,7 +65,7 @@ impl Network { // Network accounting network_manager.stats_packet_rcvd( descriptor.remote_address().to_ip_addr(), - size as u64, + ByteCount::new(size as u64), ); // Pass it up for processing diff --git a/veilid-core/src/network_manager/native/protocol/mod.rs b/veilid-core/src/network_manager/native/protocol/mod.rs index 4c2dab23..0a41a77b 100644 --- a/veilid-core/src/network_manager/native/protocol/mod.rs +++ b/veilid-core/src/network_manager/native/protocol/mod.rs @@ -5,7 +5,6 @@ pub mod wrtc; pub mod ws; use super::*; -use crate::xx::*; use std::io; #[derive(Debug)] diff --git a/veilid-core/src/network_manager/native/protocol/sockets.rs b/veilid-core/src/network_manager/native/protocol/sockets.rs index c8918e33..608e19b5 100644 --- a/veilid-core/src/network_manager/native/protocol/sockets.rs +++ b/veilid-core/src/network_manager/native/protocol/sockets.rs @@ -1,4 +1,3 @@ -use crate::xx::*; use crate::*; use async_io::Async; use std::io; @@ -196,7 +195,7 @@ pub async fn nonblocking_connect( let async_stream = Async::new(std::net::TcpStream::from(socket))?; // The stream becomes writable when connected - timeout_or_try!(intf::timeout(timeout_ms, async_stream.writable()) + timeout_or_try!(timeout(timeout_ms, async_stream.writable()) .await .into_timeout_or() .into_result()?); diff --git a/veilid-core/src/network_manager/native/protocol/udp.rs b/veilid-core/src/network_manager/native/protocol/udp.rs index 6af66b1d..a8fe5ce8 100644 --- a/veilid-core/src/network_manager/native/protocol/udp.rs +++ b/veilid-core/src/network_manager/native/protocol/udp.rs @@ -14,7 +14,7 @@ impl RawUdpProtocolHandler { // #[instrument(level = "trace", err, skip(self, data), fields(data.len = data.len(), ret.len, ret.descriptor))] pub async fn recv_message(&self, data: &mut [u8]) -> io::Result<(usize, ConnectionDescriptor)> { let (size, descriptor) = loop { - let (size, remote_addr) = network_result_value_or_log!(debug self.socket.recv_from(data).await.into_network_result()? => continue); + let (size, remote_addr) = network_result_value_or_log!(self.socket.recv_from(data).await.into_network_result()? => continue); if size > MAX_MESSAGE_SIZE { log_net!(debug "{}({}) at {}@{}:{}", "Invalid message".green(), "received too large UDP message", file!(), line!(), column!()); continue; diff --git a/veilid-core/src/network_manager/network_connection.rs b/veilid-core/src/network_manager/network_connection.rs index cffc91ae..6c832c42 100644 --- a/veilid-core/src/network_manager/network_connection.rs +++ b/veilid-core/src/network_manager/network_connection.rs @@ -78,19 +78,19 @@ enum RecvLoopAction { #[derive(Debug, Clone)] pub struct NetworkConnectionStats { - last_message_sent_time: Option, - last_message_recv_time: Option, + last_message_sent_time: Option, + last_message_recv_time: Option, } -pub type NetworkConnectionId = u64; +pub type NetworkConnectionId = AlignedU64; #[derive(Debug)] pub struct NetworkConnection { connection_id: NetworkConnectionId, descriptor: ConnectionDescriptor, processor: Option>, - established_time: u64, + established_time: Timestamp, stats: Arc>, sender: flume::Sender<(Option, Vec)>, stop_source: Option, @@ -99,13 +99,13 @@ pub struct NetworkConnection { impl NetworkConnection { pub(super) fn dummy(id: NetworkConnectionId, descriptor: ConnectionDescriptor) -> Self { // Create handle for sending (dummy is immediately disconnected) - let (sender, _receiver) = flume::bounded(intf::get_concurrency() as usize); + let (sender, _receiver) = flume::bounded(get_concurrency() as usize); Self { connection_id: id, descriptor, processor: None, - established_time: intf::get_timestamp(), + established_time: get_aligned_timestamp(), stats: Arc::new(Mutex::new(NetworkConnectionStats { last_message_sent_time: None, last_message_recv_time: None, @@ -125,7 +125,7 @@ impl NetworkConnection { let descriptor = protocol_connection.descriptor(); // Create handle for sending - let (sender, receiver) = flume::bounded(intf::get_concurrency() as usize); + let (sender, receiver) = flume::bounded(get_concurrency() as usize); // Create stats let stats = Arc::new(Mutex::new(NetworkConnectionStats { @@ -137,7 +137,7 @@ impl NetworkConnection { let local_stop_token = stop_source.token(); // Spawn connection processor and pass in protocol connection - let processor = intf::spawn(Self::process_connection( + let processor = spawn(Self::process_connection( connection_manager, local_stop_token, manager_stop_token, @@ -153,7 +153,7 @@ impl NetworkConnection { connection_id, descriptor, processor: Some(processor), - established_time: intf::get_timestamp(), + established_time: get_aligned_timestamp(), stats, sender, stop_source: Some(stop_source), @@ -185,7 +185,7 @@ impl NetworkConnection { stats: Arc>, message: Vec, ) -> io::Result> { - let ts = intf::get_timestamp(); + let ts = get_aligned_timestamp(); let out = network_result_try!(protocol_connection.send(message).await?); let mut stats = stats.lock(); @@ -199,7 +199,7 @@ impl NetworkConnection { protocol_connection: &ProtocolNetworkConnection, stats: Arc>, ) -> io::Result>> { - let ts = intf::get_timestamp(); + let ts = get_aligned_timestamp(); let out = network_result_try!(protocol_connection.recv().await?); let mut stats = stats.lock(); @@ -217,7 +217,7 @@ impl NetworkConnection { } #[allow(dead_code)] - pub fn established_time(&self) -> u64 { + pub fn established_time(&self) -> Timestamp { self.established_time } @@ -246,7 +246,7 @@ impl NetworkConnection { // Push mutable timer so we can reset it // Normally we would use an io::timeout here, but WASM won't support that, so we use a mutable sleep future let new_timer = || { - intf::sleep(connection_manager.connection_inactivity_timeout_ms()).then(|_| async { + sleep(connection_manager.connection_inactivity_timeout_ms()).then(|_| async { // timeout log_net!("== Connection timeout on {:?}", descriptor.green()); RecvLoopAction::Timeout @@ -301,7 +301,7 @@ impl NetworkConnection { match res { Ok(v) => { - let message = network_result_value_or_log!(debug v => { + let message = network_result_value_or_log!(v => { return RecvLoopAction::Finish; }); diff --git a/veilid-core/src/network_manager/tasks.rs b/veilid-core/src/network_manager/tasks.rs deleted file mode 100644 index c9834645..00000000 --- a/veilid-core/src/network_manager/tasks.rs +++ /dev/null @@ -1,699 +0,0 @@ -use super::*; - -use crate::crypto::*; -use crate::xx::*; -use futures_util::FutureExt; -use stop_token::future::FutureExt as StopFutureExt; - -impl NetworkManager { - // Bootstrap lookup process - #[instrument(level = "trace", skip(self), ret, err)] - pub(super) async fn resolve_bootstrap( - &self, - bootstrap: Vec, - ) -> EyreResult { - // Resolve from bootstrap root to bootstrap hostnames - let mut bsnames = Vec::::new(); - for bh in bootstrap { - // Get TXT record for bootstrap (bootstrap.veilid.net, or similar) - let records = intf::txt_lookup(&bh).await?; - for record in records { - // Split the bootstrap name record by commas - for rec in record.split(',') { - let rec = rec.trim(); - // If the name specified is fully qualified, go with it - let bsname = if rec.ends_with('.') { - rec.to_string() - } - // If the name is not fully qualified, prepend it to the bootstrap name - else { - format!("{}.{}", rec, bh) - }; - - // Add to the list of bootstrap name to look up - bsnames.push(bsname); - } - } - } - - // Get bootstrap nodes from hostnames concurrently - let mut unord = FuturesUnordered::new(); - for bsname in bsnames { - unord.push( - async move { - // look up boostrap node txt records - let bsnirecords = match intf::txt_lookup(&bsname).await { - Err(e) => { - warn!("bootstrap node txt lookup failed for {}: {}", bsname, e); - return None; - } - Ok(v) => v, - }; - // for each record resolve into key/bootstraprecord pairs - let mut bootstrap_records: Vec<(DHTKey, BootstrapRecord)> = Vec::new(); - for bsnirecord in bsnirecords { - // Bootstrap TXT Record Format Version 0: - // txt_version,min_version,max_version,nodeid,hostname,dialinfoshort* - // - // Split bootstrap node record by commas. Example: - // 0,0,0,7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ,bootstrap-1.dev.veilid.net,T5150,U5150,W5150/ws - let records: Vec = bsnirecord - .trim() - .split(',') - .map(|x| x.trim().to_owned()) - .collect(); - if records.len() < 6 { - warn!("invalid number of fields in bootstrap txt record"); - continue; - } - - // Bootstrap TXT record version - let txt_version: u8 = match records[0].parse::() { - Ok(v) => v, - Err(e) => { - warn!( - "invalid txt_version specified in bootstrap node txt record: {}", - e - ); - continue; - } - }; - if txt_version != BOOTSTRAP_TXT_VERSION { - warn!("unsupported bootstrap txt record version"); - continue; - } - - // Min/Max wire protocol version - let min_version: u8 = match records[1].parse::() { - Ok(v) => v, - Err(e) => { - warn!( - "invalid min_version specified in bootstrap node txt record: {}", - e - ); - continue; - } - }; - let max_version: u8 = match records[2].parse::() { - Ok(v) => v, - Err(e) => { - warn!( - "invalid max_version specified in bootstrap node txt record: {}", - e - ); - continue; - } - }; - - // Node Id - let node_id_str = &records[3]; - let node_id_key = match DHTKey::try_decode(node_id_str) { - Ok(v) => v, - Err(e) => { - warn!( - "Invalid node id in bootstrap node record {}: {}", - node_id_str, e - ); - continue; - } - }; - - // Hostname - let hostname_str = &records[4]; - - // If this is our own node id, then we skip it for bootstrap, in case we are a bootstrap node - if self.routing_table().node_id() == node_id_key { - continue; - } - - // Resolve each record and store in node dial infos list - let mut bootstrap_record = BootstrapRecord { - min_version, - max_version, - dial_info_details: Vec::new(), - }; - for rec in &records[5..] { - let rec = rec.trim(); - let dial_infos = match DialInfo::try_vec_from_short(rec, hostname_str) { - Ok(dis) => dis, - Err(e) => { - warn!( - "Couldn't resolve bootstrap node dial info {}: {}", - rec, e - ); - continue; - } - }; - - for di in dial_infos { - bootstrap_record.dial_info_details.push(DialInfoDetail { - dial_info: di, - class: DialInfoClass::Direct, - }); - } - } - bootstrap_records.push((node_id_key, bootstrap_record)); - } - Some(bootstrap_records) - } - .instrument(Span::current()), - ); - } - - let mut bsmap = BootstrapRecordMap::new(); - while let Some(bootstrap_records) = unord.next().await { - if let Some(bootstrap_records) = bootstrap_records { - for (bskey, mut bsrec) in bootstrap_records { - let rec = bsmap.entry(bskey).or_insert_with(|| BootstrapRecord { - min_version: bsrec.min_version, - max_version: bsrec.max_version, - dial_info_details: Vec::new(), - }); - rec.dial_info_details.append(&mut bsrec.dial_info_details); - } - } - } - - Ok(bsmap) - } - - // 'direct' bootstrap task routine for systems incapable of resolving TXT records, such as browser WASM - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn direct_bootstrap_task_routine( - self, - stop_token: StopToken, - bootstrap_dialinfos: Vec, - ) -> EyreResult<()> { - let mut unord = FuturesUnordered::new(); - let routing_table = self.routing_table(); - - for bootstrap_di in bootstrap_dialinfos { - log_net!(debug "direct bootstrap with: {}", bootstrap_di); - - let peer_info = self.boot_request(bootstrap_di).await?; - - log_net!(debug " direct bootstrap peerinfo: {:?}", peer_info); - - // Got peer info, let's add it to the routing table - for pi in peer_info { - let k = pi.node_id.key; - // Register the node - if let Some(nr) = routing_table.register_node_with_signed_node_info( - RoutingDomain::PublicInternet, - k, - pi.signed_node_info, - false, - ) { - // Add this our futures to process in parallel - let routing_table = routing_table.clone(); - unord.push( - // lets ask bootstrap to find ourselves now - async move { routing_table.reverse_find_node(nr, true).await } - .instrument(Span::current()), - ); - } - } - } - - // Wait for all bootstrap operations to complete before we complete the singlefuture - while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} - - Ok(()) - } - - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn bootstrap_task_routine(self, stop_token: StopToken) -> EyreResult<()> { - let (bootstrap, bootstrap_nodes) = { - let c = self.unlocked_inner.config.get(); - ( - c.network.bootstrap.clone(), - c.network.bootstrap_nodes.clone(), - ) - }; - let routing_table = self.routing_table(); - - log_net!(debug "--- bootstrap_task"); - - // See if we are specifying a direct dialinfo for bootstrap, if so use the direct mechanism - if !bootstrap.is_empty() && bootstrap_nodes.is_empty() { - let mut bootstrap_dialinfos = Vec::::new(); - for b in &bootstrap { - if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) { - for bootstrap_di in bootstrap_di_vec { - bootstrap_dialinfos.push(bootstrap_di); - } - } - } - if bootstrap_dialinfos.len() > 0 { - return self - .direct_bootstrap_task_routine(stop_token, bootstrap_dialinfos) - .await; - } - } - - // If we aren't specifying a bootstrap node list explicitly, then pull from the bootstrap server(s) - let bsmap: BootstrapRecordMap = if !bootstrap_nodes.is_empty() { - let mut bsmap = BootstrapRecordMap::new(); - let mut bootstrap_node_dial_infos = Vec::new(); - for b in bootstrap_nodes { - let (id_str, di_str) = b - .split_once('@') - .ok_or_else(|| eyre!("Invalid node dial info in bootstrap entry"))?; - let node_id = - NodeId::from_str(id_str).wrap_err("Invalid node id in bootstrap entry")?; - let dial_info = - DialInfo::from_str(di_str).wrap_err("Invalid dial info in bootstrap entry")?; - bootstrap_node_dial_infos.push((node_id, dial_info)); - } - for (node_id, dial_info) in bootstrap_node_dial_infos { - bsmap - .entry(node_id.key) - .or_insert_with(|| BootstrapRecord { - min_version: MIN_CRYPTO_VERSION, - max_version: MAX_CRYPTO_VERSION, - dial_info_details: Vec::new(), - }) - .dial_info_details - .push(DialInfoDetail { - dial_info, - class: DialInfoClass::Direct, // Bootstraps are always directly reachable - }); - } - bsmap - } else { - // Resolve bootstrap servers and recurse their TXT entries - self.resolve_bootstrap(bootstrap).await? - }; - - // Map all bootstrap entries to a single key with multiple dialinfo - - // Run all bootstrap operations concurrently - let mut unord = FuturesUnordered::new(); - for (k, mut v) in bsmap { - // Sort dial info so we get the preferred order correct - v.dial_info_details.sort(); - - log_net!("--- bootstrapping {} with {:?}", k.encode(), &v); - - // Make invalid signed node info (no signature) - if let Some(nr) = routing_table.register_node_with_signed_node_info( - RoutingDomain::PublicInternet, - k, - SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo { - network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable - outbound_protocols: ProtocolTypeSet::only(ProtocolType::UDP), // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled - address_types: AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable - min_version: v.min_version, // Minimum crypto version specified in txt record - max_version: v.max_version, // Maximum crypto version specified in txt record - dial_info_detail_list: v.dial_info_details, // Dial info is as specified in the bootstrap list - })), - true, - ) { - // Add this our futures to process in parallel - let routing_table = routing_table.clone(); - unord.push( - async move { - // Need VALID signed peer info, so ask bootstrap to find_node of itself - // which will ensure it has the bootstrap's signed peer info as part of the response - let _ = routing_table.find_target(nr.clone()).await; - - // Ensure we got the signed peer info - if !nr.signed_node_info_has_valid_signature(RoutingDomain::PublicInternet) { - log_net!(warn - "bootstrap at {:?} did not return valid signed node info", - nr - ); - // If this node info is invalid, it will time out after being unpingable - } else { - // otherwise this bootstrap is valid, lets ask it to find ourselves now - routing_table.reverse_find_node(nr, true).await - } - } - .instrument(Span::current()), - ); - } - } - - // Wait for all bootstrap operations to complete before we complete the singlefuture - while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} - Ok(()) - } - - // Ping each node in the routing table if they need to be pinged - // to determine their reliability - #[instrument(level = "trace", skip(self), err)] - fn ping_validator_public_internet( - &self, - cur_ts: u64, - unord: &mut FuturesUnordered< - SendPinBoxFuture>>, RPCError>>, - >, - ) -> EyreResult<()> { - let rpc = self.rpc_processor(); - let routing_table = self.routing_table(); - - // Get all nodes needing pings in the PublicInternet routing domain - let node_refs = routing_table.get_nodes_needing_ping(RoutingDomain::PublicInternet, cur_ts); - - // Look up any NAT mappings we may need to try to preserve with keepalives - let mut mapped_port_info = routing_table.get_low_level_port_info(); - - // Get the PublicInternet relay if we are using one - let opt_relay_nr = routing_table.relay_node(RoutingDomain::PublicInternet); - let opt_relay_id = opt_relay_nr.map(|nr| nr.node_id()); - - // Get our publicinternet dial info - let dids = routing_table.all_filtered_dial_info_details( - RoutingDomain::PublicInternet.into(), - &DialInfoFilter::all(), - ); - - // For all nodes needing pings, figure out how many and over what protocols - for nr in node_refs { - // If this is a relay, let's check for NAT keepalives - let mut did_pings = false; - if Some(nr.node_id()) == opt_relay_id { - // Relay nodes get pinged over all protocols we have inbound dialinfo for - // This is so we can preserve the inbound NAT mappings at our router - for did in &dids { - // Do we need to do this ping? - // Check if we have already pinged over this low-level-protocol/address-type/port combo - // We want to ensure we do the bare minimum required here - let pt = did.dial_info.protocol_type(); - let at = did.dial_info.address_type(); - let needs_ping = if let Some((llpt, port)) = - mapped_port_info.protocol_to_port.get(&(pt, at)) - { - mapped_port_info - .low_level_protocol_ports - .remove(&(*llpt, at, *port)) - } else { - false - }; - if needs_ping { - let rpc = rpc.clone(); - let dif = did.dial_info.make_filter(); - let nr_filtered = - nr.filtered_clone(NodeRefFilter::new().with_dial_info_filter(dif)); - log_net!("--> Keepalive ping to {:?}", nr_filtered); - unord.push( - async move { rpc.rpc_call_status(Destination::direct(nr_filtered)).await } - .instrument(Span::current()) - .boxed(), - ); - did_pings = true; - } - } - } - // Just do a single ping with the best protocol for all the other nodes, - // ensuring that we at least ping a relay with -something- even if we didnt have - // any mapped ports to preserve - if !did_pings { - let rpc = rpc.clone(); - unord.push( - async move { rpc.rpc_call_status(Destination::direct(nr)).await } - .instrument(Span::current()) - .boxed(), - ); - } - } - - Ok(()) - } - - // Ping each node in the LocalNetwork routing domain if they - // need to be pinged to determine their reliability - #[instrument(level = "trace", skip(self), err)] - fn ping_validator_local_network( - &self, - cur_ts: u64, - unord: &mut FuturesUnordered< - SendPinBoxFuture>>, RPCError>>, - >, - ) -> EyreResult<()> { - let rpc = self.rpc_processor(); - let routing_table = self.routing_table(); - - // Get all nodes needing pings in the LocalNetwork routing domain - let node_refs = routing_table.get_nodes_needing_ping(RoutingDomain::LocalNetwork, cur_ts); - - // For all nodes needing pings, figure out how many and over what protocols - for nr in node_refs { - let rpc = rpc.clone(); - - // Just do a single ping with the best protocol for all the nodes - unord.push( - async move { rpc.rpc_call_status(Destination::direct(nr)).await } - .instrument(Span::current()) - .boxed(), - ); - } - - Ok(()) - } - - // Ping each node in the routing table if they need to be pinged - // to determine their reliability - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn ping_validator_task_routine( - self, - stop_token: StopToken, - _last_ts: u64, - cur_ts: u64, - ) -> EyreResult<()> { - let mut unord = FuturesUnordered::new(); - - // PublicInternet - self.ping_validator_public_internet(cur_ts, &mut unord)?; - - // LocalNetwork - self.ping_validator_local_network(cur_ts, &mut unord)?; - - // Wait for ping futures to complete in parallel - while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} - - Ok(()) - } - - // Ask our remaining peers to give us more peers before we go - // back to the bootstrap servers to keep us from bothering them too much - // This only adds PublicInternet routing domain peers. The discovery - // mechanism for LocalNetwork suffices for locating all the local network - // peers that are available. This, however, may query other LocalNetwork - // nodes for their PublicInternet peers, which is a very fast way to get - // a new node online. - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn peer_minimum_refresh_task_routine( - self, - stop_token: StopToken, - ) -> EyreResult<()> { - let routing_table = self.routing_table(); - let mut ord = FuturesOrdered::new(); - let min_peer_count = { - let c = self.unlocked_inner.config.get(); - c.network.dht.min_peer_count as usize - }; - - // For the PublicInternet routing domain, get list of all peers we know about - // even the unreliable ones, and ask them to find nodes close to our node too - let noderefs = routing_table.find_fastest_nodes( - min_peer_count, - VecDeque::new(), - |_rti, k: DHTKey, v: Option>| { - NodeRef::new(routing_table.clone(), k, v.unwrap().clone(), None) - }, - ); - for nr in noderefs { - let routing_table = routing_table.clone(); - ord.push_back( - async move { routing_table.reverse_find_node(nr, false).await } - .instrument(Span::current()), - ); - } - - // do peer minimum search in order from fastest to slowest - while let Ok(Some(_)) = ord.next().timeout_at(stop_token.clone()).await {} - - Ok(()) - } - - // Keep relays assigned and accessible - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn relay_management_task_routine( - self, - _stop_token: StopToken, - _last_ts: u64, - cur_ts: u64, - ) -> EyreResult<()> { - // Get our node's current node info and network class and do the right thing - let routing_table = self.routing_table(); - let own_peer_info = routing_table.get_own_peer_info(RoutingDomain::PublicInternet); - let own_node_info = own_peer_info.signed_node_info.node_info(); - let network_class = routing_table.get_network_class(RoutingDomain::PublicInternet); - - // Get routing domain editor - let mut editor = routing_table.edit_routing_domain(RoutingDomain::PublicInternet); - - // Do we know our network class yet? - if let Some(network_class) = network_class { - // If we already have a relay, see if it is dead, or if we don't need it any more - let has_relay = { - if let Some(relay_node) = routing_table.relay_node(RoutingDomain::PublicInternet) { - let state = relay_node.state(cur_ts); - // Relay node is dead or no longer needed - if matches!(state, BucketEntryState::Dead) { - info!("Relay node died, dropping relay {}", relay_node); - editor.clear_relay_node(); - false - } else if !own_node_info.requires_relay() { - info!( - "Relay node no longer required, dropping relay {}", - relay_node - ); - editor.clear_relay_node(); - false - } else { - true - } - } else { - false - } - }; - - // Do we need a relay? - if !has_relay && own_node_info.requires_relay() { - // Do we want an outbound relay? - let mut got_outbound_relay = false; - if network_class.outbound_wants_relay() { - // The outbound relay is the host of the PWA - if let Some(outbound_relay_peerinfo) = intf::get_outbound_relay_peer().await { - // Register new outbound relay - if let Some(nr) = routing_table.register_node_with_signed_node_info( - RoutingDomain::PublicInternet, - outbound_relay_peerinfo.node_id.key, - outbound_relay_peerinfo.signed_node_info, - false, - ) { - info!("Outbound relay node selected: {}", nr); - editor.set_relay_node(nr); - got_outbound_relay = true; - } - } - } - if !got_outbound_relay { - // Find a node in our routing table that is an acceptable inbound relay - if let Some(nr) = - routing_table.find_inbound_relay(RoutingDomain::PublicInternet, cur_ts) - { - info!("Inbound relay node selected: {}", nr); - editor.set_relay_node(nr); - } - } - } - } - - // Commit the changes - editor.commit().await; - - Ok(()) - } - - // Keep private routes assigned and accessible - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn private_route_management_task_routine( - self, - _stop_token: StopToken, - _last_ts: u64, - cur_ts: u64, - ) -> EyreResult<()> { - // Get our node's current node info and network class and do the right thing - let routing_table = self.routing_table(); - let own_peer_info = routing_table.get_own_peer_info(RoutingDomain::PublicInternet); - let network_class = routing_table.get_network_class(RoutingDomain::PublicInternet); - - // Get routing domain editor - let mut editor = routing_table.edit_routing_domain(RoutingDomain::PublicInternet); - - // Do we know our network class yet? - if let Some(network_class) = network_class { - - // see if we have any routes that need extending - } - - // Commit the changes - editor.commit().await; - - Ok(()) - } - - // Compute transfer statistics for the low level network - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn rolling_transfers_task_routine( - self, - _stop_token: StopToken, - last_ts: u64, - cur_ts: u64, - ) -> EyreResult<()> { - // log_net!("--- network manager rolling_transfers task"); - { - let inner = &mut *self.inner.lock(); - - // Roll the low level network transfer stats for our address - inner - .stats - .self_stats - .transfer_stats_accounting - .roll_transfers(last_ts, cur_ts, &mut inner.stats.self_stats.transfer_stats); - - // Roll all per-address transfers - let mut dead_addrs: HashSet = HashSet::new(); - for (addr, stats) in &mut inner.stats.per_address_stats { - stats.transfer_stats_accounting.roll_transfers( - last_ts, - cur_ts, - &mut stats.transfer_stats, - ); - - // While we're here, lets see if this address has timed out - if cur_ts - stats.last_seen_ts >= IPADDR_MAX_INACTIVE_DURATION_US { - // it's dead, put it in the dead list - dead_addrs.insert(*addr); - } - } - - // Remove the dead addresses from our tables - for da in &dead_addrs { - inner.stats.per_address_stats.remove(da); - } - } - - // Send update - self.send_network_update(); - - Ok(()) - } - - // Clean up the public address check tables, removing entries that have timed out - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn public_address_check_task_routine( - self, - stop_token: StopToken, - _last_ts: u64, - cur_ts: u64, - ) -> EyreResult<()> { - // go through public_address_inconsistencies_table and time out things that have expired - let mut inner = self.inner.lock(); - for (_, pait_v) in &mut inner.public_address_inconsistencies_table { - let mut expired = Vec::new(); - for (addr, exp_ts) in pait_v.iter() { - if *exp_ts <= cur_ts { - expired.push(*addr); - } - } - for exp in expired { - pait_v.remove(&exp); - } - } - Ok(()) - } -} diff --git a/veilid-core/src/network_manager/tasks/mod.rs b/veilid-core/src/network_manager/tasks/mod.rs new file mode 100644 index 00000000..03f76a42 --- /dev/null +++ b/veilid-core/src/network_manager/tasks/mod.rs @@ -0,0 +1,76 @@ +pub mod public_address_check; +pub mod rolling_transfers; + +use super::*; + +impl NetworkManager { + pub(crate) fn start_tasks(&self) { + // Set rolling transfers tick task + { + let this = self.clone(); + self.unlocked_inner + .rolling_transfers_task + .set_routine(move |s, l, t| { + Box::pin( + this.clone() + .rolling_transfers_task_routine(s, Timestamp::new(l), Timestamp::new(t)) + .instrument(trace_span!( + parent: None, + "NetworkManager rolling transfers task routine" + )), + ) + }); + } + + // Set public address check task + { + let this = self.clone(); + self.unlocked_inner + .public_address_check_task + .set_routine(move |s, l, t| { + Box::pin( + this.clone() + .public_address_check_task_routine( + s, + Timestamp::new(l), + Timestamp::new(t), + ) + .instrument(trace_span!( + parent: None, + "public address check task routine" + )), + ) + }); + } + } + + pub async fn tick(&self) -> EyreResult<()> { + let routing_table = self.routing_table(); + let net = self.net(); + let receipt_manager = self.receipt_manager(); + + // Run the rolling transfers task + self.unlocked_inner.rolling_transfers_task.tick().await?; + + // Run the routing table tick + routing_table.tick().await?; + + // Run the low level network tick + net.tick().await?; + + // Run the receipt manager tick + receipt_manager.tick().await?; + + // Purge the client whitelist + self.purge_client_whitelist(); + + Ok(()) + } + + pub(crate) async fn stop_tasks(&self) { + debug!("stopping rolling transfers task"); + if let Err(e) = self.unlocked_inner.rolling_transfers_task.stop().await { + warn!("rolling_transfers_task not stopped: {}", e); + } + } +} diff --git a/veilid-core/src/network_manager/tasks/public_address_check.rs b/veilid-core/src/network_manager/tasks/public_address_check.rs new file mode 100644 index 00000000..91507e37 --- /dev/null +++ b/veilid-core/src/network_manager/tasks/public_address_check.rs @@ -0,0 +1,27 @@ +use super::*; + +impl NetworkManager { + // Clean up the public address check tables, removing entries that have timed out + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn public_address_check_task_routine( + self, + stop_token: StopToken, + _last_ts: Timestamp, + cur_ts: Timestamp, + ) -> EyreResult<()> { + // go through public_address_inconsistencies_table and time out things that have expired + let mut inner = self.inner.lock(); + for (_, pait_v) in &mut inner.public_address_inconsistencies_table { + let mut expired = Vec::new(); + for (addr, exp_ts) in pait_v.iter() { + if *exp_ts <= cur_ts { + expired.push(*addr); + } + } + for exp in expired { + pait_v.remove(&exp); + } + } + Ok(()) + } +} diff --git a/veilid-core/src/network_manager/tasks/rolling_transfers.rs b/veilid-core/src/network_manager/tasks/rolling_transfers.rs new file mode 100644 index 00000000..219790ec --- /dev/null +++ b/veilid-core/src/network_manager/tasks/rolling_transfers.rs @@ -0,0 +1,50 @@ +use super::*; + +impl NetworkManager { + // Compute transfer statistics for the low level network + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn rolling_transfers_task_routine( + self, + _stop_token: StopToken, + last_ts: Timestamp, + cur_ts: Timestamp, + ) -> EyreResult<()> { + // log_net!("--- network manager rolling_transfers task"); + { + let inner = &mut *self.inner.lock(); + + // Roll the low level network transfer stats for our address + inner + .stats + .self_stats + .transfer_stats_accounting + .roll_transfers(last_ts, cur_ts, &mut inner.stats.self_stats.transfer_stats); + + // Roll all per-address transfers + let mut dead_addrs: HashSet = HashSet::new(); + for (addr, stats) in &mut inner.stats.per_address_stats { + stats.transfer_stats_accounting.roll_transfers( + last_ts, + cur_ts, + &mut stats.transfer_stats, + ); + + // While we're here, lets see if this address has timed out + if cur_ts.saturating_sub(stats.last_seen_ts) >= IPADDR_MAX_INACTIVE_DURATION_US { + // it's dead, put it in the dead list + dead_addrs.insert(*addr); + } + } + + // Remove the dead addresses from our tables + for da in &dead_addrs { + inner.stats.per_address_stats.remove(da); + } + } + + // Send update + self.send_network_update(); + + Ok(()) + } +} diff --git a/veilid-core/src/network_manager/tests/test_connection_table.rs b/veilid-core/src/network_manager/tests/test_connection_table.rs index 79306051..8a48c79d 100644 --- a/veilid-core/src/network_manager/tests/test_connection_table.rs +++ b/veilid-core/src/network_manager/tests/test_connection_table.rs @@ -1,8 +1,7 @@ +use super::*; + use super::connection_table::*; -use super::network_connection::*; use crate::tests::common::test_veilid_config::*; -use crate::xx::*; -use crate::*; pub async fn test_add_get_remove() { let config = get_config(); @@ -51,13 +50,13 @@ pub async fn test_add_get_remove() { ))), ); - let c1 = NetworkConnection::dummy(1, a1); - let c1b = NetworkConnection::dummy(10, a1); + let c1 = NetworkConnection::dummy(1.into(), a1); + let c1b = NetworkConnection::dummy(10.into(), a1); let c1h = c1.get_handle(); - let c2 = NetworkConnection::dummy(2, a2); - let c3 = NetworkConnection::dummy(3, a3); - let c4 = NetworkConnection::dummy(4, a4); - let c5 = NetworkConnection::dummy(5, a5); + let c2 = NetworkConnection::dummy(2.into(), a2); + let c3 = NetworkConnection::dummy(3.into(), a3); + let c4 = NetworkConnection::dummy(4.into(), a4); + let c5 = NetworkConnection::dummy(5.into(), a5); assert_eq!(a1, c2.connection_descriptor()); assert_ne!(a3, c4.connection_descriptor()); @@ -69,8 +68,8 @@ pub async fn test_add_get_remove() { assert!(table.add_connection(c1b).is_err()); assert_eq!(table.connection_count(), 1); - assert!(table.remove_connection_by_id(4).is_none()); - assert!(table.remove_connection_by_id(5).is_none()); + assert!(table.remove_connection_by_id(4.into()).is_none()); + assert!(table.remove_connection_by_id(5.into()).is_none()); assert_eq!(table.connection_count(), 1); assert_eq!(table.get_connection_by_descriptor(a1), Some(c1h.clone())); assert_eq!(table.get_connection_by_descriptor(a1), Some(c1h.clone())); @@ -82,41 +81,41 @@ pub async fn test_add_get_remove() { assert_eq!(table.connection_count(), 1); assert_eq!( table - .remove_connection_by_id(1) + .remove_connection_by_id(1.into()) .map(|c| c.connection_descriptor()) .unwrap(), a1 ); assert_eq!(table.connection_count(), 0); - assert!(table.remove_connection_by_id(2).is_none()); + assert!(table.remove_connection_by_id(2.into()).is_none()); assert_eq!(table.connection_count(), 0); assert_eq!(table.get_connection_by_descriptor(a2), None); assert_eq!(table.get_connection_by_descriptor(a1), None); assert_eq!(table.connection_count(), 0); - let c1 = NetworkConnection::dummy(6, a1); + let c1 = NetworkConnection::dummy(6.into(), a1); table.add_connection(c1).unwrap(); - let c2 = NetworkConnection::dummy(7, a2); + let c2 = NetworkConnection::dummy(7.into(), a2); assert_err!(table.add_connection(c2)); table.add_connection(c3).unwrap(); table.add_connection(c4).unwrap(); assert_eq!(table.connection_count(), 3); assert_eq!( table - .remove_connection_by_id(6) + .remove_connection_by_id(6.into()) .map(|c| c.connection_descriptor()) .unwrap(), a2 ); assert_eq!( table - .remove_connection_by_id(3) + .remove_connection_by_id(3.into()) .map(|c| c.connection_descriptor()) .unwrap(), a3 ); assert_eq!( table - .remove_connection_by_id(4) + .remove_connection_by_id(4.into()) .map(|c| c.connection_descriptor()) .unwrap(), a4 diff --git a/veilid-core/src/network_manager/wasm/mod.rs b/veilid-core/src/network_manager/wasm/mod.rs index 2489e191..e5db561e 100644 --- a/veilid-core/src/network_manager/wasm/mod.rs +++ b/veilid-core/src/network_manager/wasm/mod.rs @@ -1,6 +1,7 @@ mod protocol; use super::*; + use crate::routing_table::*; use connection_manager::*; use protocol::ws::WebsocketProtocolHandler; @@ -108,7 +109,7 @@ impl Network { // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), data_len as u64); + .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::Value(())) } @@ -151,7 +152,7 @@ impl Network { network_result_try!(pnc.send(data).await.wrap_err("send failure")?); self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), data_len as u64); + .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); let out = network_result_try!(network_result_try!(timeout(timeout_ms, pnc.recv()) .await @@ -159,7 +160,7 @@ impl Network { .wrap_err("recv failure")?); self.network_manager() - .stats_packet_rcvd(dial_info.to_ip_addr(), out.len() as u64); + .stats_packet_rcvd(dial_info.to_ip_addr(), ByteCount::new(out.len() as u64)); Ok(NetworkResult::Value(out)) } @@ -193,7 +194,7 @@ impl Network { // Network accounting self.network_manager().stats_packet_sent( descriptor.remote().to_socket_addr().ip(), - data_len as u64, + ByteCount::new(data_len as u64), ); // Data was consumed @@ -242,7 +243,7 @@ impl Network { // Network accounting self.network_manager() - .stats_packet_sent(dial_info.to_ip_addr(), data_len as u64); + .stats_packet_sent(dial_info.to_ip_addr(), ByteCount::new(data_len as u64)); Ok(NetworkResult::value(connection_descriptor)) } @@ -251,7 +252,7 @@ impl Network { pub async fn startup(&self) -> EyreResult<()> { // get protocol config - self.inner.lock().protocol_config = { + let protocol_config = { let c = self.config.get(); let inbound = ProtocolTypeSet::new(); let mut outbound = ProtocolTypeSet::new(); @@ -268,12 +269,31 @@ impl Network { let family_local = AddressTypeSet::all(); ProtocolConfig { - inbound, outbound, + inbound, family_global, family_local, } }; + self.inner.lock().protocol_config = protocol_config; + + // Start editing routing table + let mut editor_public_internet = self + .unlocked_inner + .routing_table + .edit_routing_domain(RoutingDomain::PublicInternet); + + // set up the routing table's network config + // if we have static public dialinfo, upgrade our network class + editor_public_internet.setup_network( + protocol_config.outbound, + protocol_config.inbound, + protocol_config.family_global, + ); + editor_public_internet.set_network_class(Some(NetworkClass::WebApp)); + + // commit routing table edits + editor_public_internet.commit().await; self.inner.lock().network_started = true; Ok(()) @@ -299,13 +319,8 @@ impl Network { // Drop all dial info let mut editor = routing_table.edit_routing_domain(RoutingDomain::PublicInternet); - editor.disable_node_info_updates(); - editor.clear_dial_info_details(); - editor.commit().await; - - let mut editor = routing_table.edit_routing_domain(RoutingDomain::LocalNetwork); - editor.disable_node_info_updates(); editor.clear_dial_info_details(); + editor.set_network_class(None); editor.commit().await; // Cancels all async background tasks by dropping join handles @@ -335,15 +350,6 @@ impl Network { false } - pub fn get_network_class(&self, _routing_domain: RoutingDomain) -> Option { - // xxx eventually detect tor browser? - return if self.inner.lock().network_started { - Some(NetworkClass::WebApp) - } else { - None - }; - } - pub fn get_protocol_config(&self) -> ProtocolConfig { self.inner.lock().protocol_config.clone() } diff --git a/veilid-core/src/network_manager/wasm/protocol/mod.rs b/veilid-core/src/network_manager/wasm/protocol/mod.rs index 16edb5ee..bc4966ca 100644 --- a/veilid-core/src/network_manager/wasm/protocol/mod.rs +++ b/veilid-core/src/network_manager/wasm/protocol/mod.rs @@ -2,7 +2,6 @@ pub mod wrtc; pub mod ws; use super::*; -use crate::xx::*; use std::io; #[derive(Debug)] diff --git a/veilid-core/src/network_manager/wasm/protocol/ws.rs b/veilid-core/src/network_manager/wasm/protocol/ws.rs index 0315d3e5..56082a86 100644 --- a/veilid-core/src/network_manager/wasm/protocol/ws.rs +++ b/veilid-core/src/network_manager/wasm/protocol/ws.rs @@ -11,12 +11,20 @@ struct WebsocketNetworkConnectionInner { fn to_io(err: WsErr) -> io::Error { match err { - WsErr::InvalidWsState { supplied: _ } => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()), + WsErr::InvalidWsState { supplied: _ } => { + io::Error::new(io::ErrorKind::InvalidInput, err.to_string()) + } WsErr::ConnectionNotOpen => io::Error::new(io::ErrorKind::NotConnected, err.to_string()), - WsErr::InvalidUrl { supplied: _ } => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()), - WsErr::InvalidCloseCode { supplied: _ } => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()), + WsErr::InvalidUrl { supplied: _ } => { + io::Error::new(io::ErrorKind::InvalidInput, err.to_string()) + } + WsErr::InvalidCloseCode { supplied: _ } => { + io::Error::new(io::ErrorKind::InvalidInput, err.to_string()) + } WsErr::ReasonStringToLong => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()), - WsErr::ConnectionFailed { event: _ } => io::Error::new(io::ErrorKind::ConnectionRefused, err.to_string()), + WsErr::ConnectionFailed { event: _ } => { + io::Error::new(io::ErrorKind::ConnectionRefused, err.to_string()) + } WsErr::InvalidEncoding => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()), WsErr::CantDecodeBlob => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()), WsErr::UnknownDataType => io::Error::new(io::ErrorKind::InvalidInput, err.to_string()), @@ -80,19 +88,19 @@ impl WebsocketNetworkConnection { let out = match SendWrapper::new(self.inner.ws_stream.clone().next()).await { Some(WsMessage::Binary(v)) => { if v.len() > MAX_MESSAGE_SIZE { - return Err(io::Error::new( - io::ErrorKind::InvalidData, - "too large ws message", - )); + return Ok(NetworkResult::invalid_message("too large ws message")); } NetworkResult::Value(v) } - Some(_) => NetworkResult::NoConnection(io::Error::new( + Some(_) => NetworkResult::no_connection_other(io::Error::new( io::ErrorKind::ConnectionReset, "Unexpected WS message type", )), None => { - bail_io_error_other!("WS stream closed"); + return Ok(NetworkResult::no_connection(io::Error::new( + io::ErrorKind::ConnectionReset, + "WS stream closed", + ))); } }; // tracing::Span::current().record("network_result", &tracing::field::display(&out)); @@ -126,7 +134,7 @@ impl WebsocketProtocolHandler { let fut = SendWrapper::new(timeout(timeout_ms, async move { WsMeta::connect(request, None).await.map_err(to_io) })); - + let (wsmeta, wsio) = network_result_try!(network_result_try!(fut .await .into_network_result()) diff --git a/veilid-core/src/receipt_manager.rs b/veilid-core/src/receipt_manager.rs index 3e9971a3..17fcf000 100644 --- a/veilid-core/src/receipt_manager.rs +++ b/veilid-core/src/receipt_manager.rs @@ -5,7 +5,6 @@ use futures_util::stream::{FuturesUnordered, StreamExt}; use network_manager::*; use routing_table::*; use stop_token::future::FutureExt; -use xx::*; #[derive(Clone, Debug)] pub enum ReceiptEvent { @@ -71,7 +70,7 @@ impl fmt::Debug for ReceiptRecordCallbackType { } pub struct ReceiptRecord { - expiration_ts: u64, + expiration_ts: Timestamp, receipt: Receipt, expected_returns: u32, returns_so_far: u32, @@ -93,7 +92,7 @@ impl fmt::Debug for ReceiptRecord { impl ReceiptRecord { pub fn new( receipt: Receipt, - expiration_ts: u64, + expiration_ts: Timestamp, expected_returns: u32, receipt_callback: impl ReceiptCallback, ) -> Self { @@ -108,7 +107,7 @@ impl ReceiptRecord { pub fn new_single_shot( receipt: Receipt, - expiration_ts: u64, + expiration_ts: Timestamp, eventual: ReceiptSingleShotType, ) -> Self { Self { @@ -124,7 +123,7 @@ impl ReceiptRecord { /* XXX: may be useful for O(1) timestamp expiration #[derive(Clone, Debug)] struct ReceiptRecordTimestampSort { - expiration_ts: u64, + expiration_ts: Timestamp, record: Arc>, } @@ -151,7 +150,7 @@ impl PartialOrd for ReceiptRecordTimestampSort { pub struct ReceiptManagerInner { network_manager: NetworkManager, records_by_nonce: BTreeMap>>, - next_oldest_ts: Option, + next_oldest_ts: Option, stop_source: Option, timeout_task: MustJoinSingleFuture<()>, } @@ -220,9 +219,9 @@ impl ReceiptManager { } #[instrument(level = "trace", skip(self))] - pub async fn timeout_task_routine(self, now: u64, stop_token: StopToken) { + pub async fn timeout_task_routine(self, now: Timestamp, stop_token: StopToken) { // Go through all receipts and build a list of expired nonces - let mut new_next_oldest_ts: Option = None; + let mut new_next_oldest_ts: Option = None; let mut expired_records = Vec::new(); { let mut inner = self.inner.lock(); @@ -281,7 +280,7 @@ impl ReceiptManager { }; (inner.next_oldest_ts, inner.timeout_task.clone(), stop_token) }; - let now = intf::get_timestamp(); + let now = get_aligned_timestamp(); // If we have at least one timestamp to expire, lets do it if let Some(next_oldest_ts) = next_oldest_ts { if now >= next_oldest_ts { @@ -319,7 +318,7 @@ impl ReceiptManager { pub fn record_receipt( &self, receipt: Receipt, - expiration: u64, + expiration: Timestamp, expected_returns: u32, callback: impl ReceiptCallback, ) { @@ -340,7 +339,7 @@ impl ReceiptManager { pub fn record_single_shot_receipt( &self, receipt: Receipt, - expiration: u64, + expiration: Timestamp, eventual: ReceiptSingleShotType, ) { let receipt_nonce = receipt.get_nonce(); @@ -357,7 +356,7 @@ impl ReceiptManager { fn update_next_oldest_timestamp(inner: &mut ReceiptManagerInner) { // Update the next oldest timestamp - let mut new_next_oldest_ts: Option = None; + let mut new_next_oldest_ts: Option = None; for v in inner.records_by_nonce.values() { let receipt_inner = v.lock(); if new_next_oldest_ts.is_none() diff --git a/veilid-core/src/routing_table/bucket.rs b/veilid-core/src/routing_table/bucket.rs index b1e3199c..ce0794bf 100644 --- a/veilid-core/src/routing_table/bucket.rs +++ b/veilid-core/src/routing_table/bucket.rs @@ -120,7 +120,7 @@ impl Bucket { .iter() .map(|(k, v)| (k.clone(), v.clone())) .collect(); - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); sorted_entries.sort_by(|a, b| -> core::cmp::Ordering { if a.0 == b.0 { return core::cmp::Ordering::Equal; diff --git a/veilid-core/src/routing_table/bucket_entry.rs b/veilid-core/src/routing_table/bucket_entry.rs index c4cf88c6..3ca93132 100644 --- a/veilid-core/src/routing_table/bucket_entry.rs +++ b/veilid-core/src/routing_table/bucket_entry.rs @@ -50,8 +50,8 @@ pub struct LastConnectionKey(ProtocolType, AddressType); pub struct BucketEntryPublicInternet { /// The PublicInternet node info signed_node_info: Option>, - /// If this node has seen our publicinternet node info - seen_our_node_info: bool, + /// The last node info timestamp of ours that this entry has seen + last_seen_our_node_info_ts: Timestamp, /// Last known node status node_status: Option, } @@ -62,8 +62,8 @@ pub struct BucketEntryPublicInternet { pub struct BucketEntryLocalNetwork { /// The LocalNetwork node info signed_node_info: Option>, - /// If this node has seen our localnetwork node info - seen_our_node_info: bool, + /// The last node info timestamp of ours that this entry has seen + last_seen_our_node_info_ts: Timestamp, /// Last known node status node_status: Option, } @@ -93,7 +93,7 @@ pub struct BucketEntryInner { updated_since_last_network_change: bool, /// The last connection descriptors used to contact this node, per protocol type #[with(Skip)] - last_connections: BTreeMap, + last_connections: BTreeMap, /// The node info for this entry on the publicinternet routing domain public_internet: BucketEntryPublicInternet, /// The node info for this entry on the localnetwork routing domain @@ -148,7 +148,7 @@ impl BucketEntryInner { } // Less is more reliable then faster - pub fn cmp_fastest_reliable(cur_ts: u64, e1: &Self, e2: &Self) -> std::cmp::Ordering { + pub fn cmp_fastest_reliable(cur_ts: Timestamp, e1: &Self, e2: &Self) -> std::cmp::Ordering { // Reverse compare so most reliable is at front let ret = e2.state(cur_ts).cmp(&e1.state(cur_ts)); if ret != std::cmp::Ordering::Equal { @@ -170,7 +170,7 @@ impl BucketEntryInner { } // Less is more reliable then older - pub fn cmp_oldest_reliable(cur_ts: u64, e1: &Self, e2: &Self) -> std::cmp::Ordering { + pub fn cmp_oldest_reliable(cur_ts: Timestamp, e1: &Self, e2: &Self) -> std::cmp::Ordering { // Reverse compare so most reliable is at front let ret = e2.state(cur_ts).cmp(&e1.state(cur_ts)); if ret != std::cmp::Ordering::Equal { @@ -191,7 +191,7 @@ impl BucketEntryInner { } } - pub fn sort_fastest_reliable_fn(cur_ts: u64) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { + pub fn sort_fastest_reliable_fn(cur_ts: Timestamp) -> impl FnMut(&Self, &Self) -> std::cmp::Ordering { move |e1, e2| Self::cmp_fastest_reliable(cur_ts, e1, e2) } @@ -231,7 +231,7 @@ impl BucketEntryInner { // No need to update the signednodeinfo though since the timestamp is the same // Touch the node and let it try to live again self.updated_since_last_network_change = true; - self.touch_last_seen(intf::get_timestamp()); + self.touch_last_seen(get_aligned_timestamp()); } return; } @@ -258,7 +258,7 @@ impl BucketEntryInner { // Update the signed node info *opt_current_sni = Some(Box::new(signed_node_info)); self.updated_since_last_network_change = true; - self.touch_last_seen(intf::get_timestamp()); + self.touch_last_seen(get_aligned_timestamp()); } pub fn has_node_info(&self, routing_domain_set: RoutingDomainSet) -> bool { @@ -275,6 +275,25 @@ impl BucketEntryInner { false } + pub fn exists_in_routing_domain( + &self, + rti: &RoutingTableInner, + routing_domain: RoutingDomain, + ) -> bool { + // Check node info + if self.has_node_info(routing_domain.into()) { + return true; + } + + // Check connections + let last_connections = self.last_connections( + rti, + true, + Some(NodeRefFilter::new().with_routing_domain(routing_domain)), + ); + !last_connections.is_empty() + } + pub fn node_info(&self, routing_domain: RoutingDomain) -> Option<&NodeInfo> { let opt_current_sni = match routing_domain { RoutingDomain::LocalNetwork => &self.local_network.signed_node_info, @@ -304,8 +323,10 @@ impl BucketEntryInner { pub fn best_routing_domain( &self, + rti: &RoutingTableInner, routing_domain_set: RoutingDomainSet, ) -> Option { + // Check node info for routing_domain in routing_domain_set { let opt_current_sni = match routing_domain { RoutingDomain::LocalNetwork => &self.local_network.signed_node_info, @@ -315,7 +336,27 @@ impl BucketEntryInner { return Some(routing_domain); } } - None + // Check connections + let mut best_routing_domain: Option = None; + let last_connections = self.last_connections( + rti, + true, + Some(NodeRefFilter::new().with_routing_domain_set(routing_domain_set)), + ); + for lc in last_connections { + if let Some(rd) = + rti.routing_domain_for_address(lc.0.remote_address().address()) + { + if let Some(brd) = best_routing_domain { + if rd < brd { + best_routing_domain = Some(rd); + } + } else { + best_routing_domain = Some(rd); + } + } + } + best_routing_domain } fn descriptor_to_key(&self, last_connection: ConnectionDescriptor) -> LastConnectionKey { @@ -326,7 +367,7 @@ impl BucketEntryInner { } // Stores a connection descriptor in this entry's table of last connections - pub fn set_last_connection(&mut self, last_connection: ConnectionDescriptor, timestamp: u64) { + pub fn set_last_connection(&mut self, last_connection: ConnectionDescriptor, timestamp: Timestamp) { let key = self.descriptor_to_key(last_connection); self.last_connections .insert(key, (last_connection, timestamp)); @@ -337,13 +378,17 @@ impl BucketEntryInner { self.last_connections.clear(); } - // Gets all the 'last connections' that match a particular filter + // Gets all the 'last connections' that match a particular filter, and their accompanying timestamps of last use pub(super) fn last_connections( &self, rti: &RoutingTableInner, + only_live: bool, filter: Option, - ) -> Vec<(ConnectionDescriptor, u64)> { - let mut out: Vec<(ConnectionDescriptor, u64)> = self + ) -> Vec<(ConnectionDescriptor, Timestamp)> { + let connection_manager = + rti.unlocked_inner.network_manager.connection_manager(); + + let mut out: Vec<(ConnectionDescriptor, Timestamp)> = self .last_connections .iter() .filter_map(|(k, v)| { @@ -368,7 +413,29 @@ impl BucketEntryInner { // no filter true }; - if include { + + if !include { + return None; + } + + if !only_live { + return Some(v.clone()); + } + + // Check if the connection is still considered live + let alive = + // Should we check the connection table? + if v.0.protocol_type().is_connection_oriented() { + // Look the connection up in the connection manager and see if it's still there + connection_manager.get_connection(v.0).is_some() + } else { + // If this is not connection oriented, then we check our last seen time + // to see if this mapping has expired (beyond our timeout) + let cur_ts = get_aligned_timestamp(); + (v.1 + TimestampDuration::new(CONNECTIONLESS_TIMEOUT_SECS as u64 * 1_000_000u64)) >= cur_ts + }; + + if alive { Some(v.clone()) } else { None @@ -388,7 +455,7 @@ impl BucketEntryInner { self.min_max_version } - pub fn state(&self, cur_ts: u64) -> BucketEntryState { + pub fn state(&self, cur_ts: Timestamp) -> BucketEntryState { if self.check_reliable(cur_ts) { BucketEntryState::Reliable } else if self.check_dead(cur_ts) { @@ -427,21 +494,29 @@ impl BucketEntryInner { } } - pub fn set_seen_our_node_info(&mut self, routing_domain: RoutingDomain, seen: bool) { + pub fn set_our_node_info_ts(&mut self, routing_domain: RoutingDomain, seen_ts: Timestamp) { match routing_domain { RoutingDomain::LocalNetwork => { - self.local_network.seen_our_node_info = seen; + self.local_network.last_seen_our_node_info_ts = seen_ts; } RoutingDomain::PublicInternet => { - self.public_internet.seen_our_node_info = seen; + self.public_internet.last_seen_our_node_info_ts = seen_ts; } } } - pub fn has_seen_our_node_info(&self, routing_domain: RoutingDomain) -> bool { + pub fn has_seen_our_node_info_ts( + &self, + routing_domain: RoutingDomain, + our_node_info_ts: Timestamp, + ) -> bool { match routing_domain { - RoutingDomain::LocalNetwork => self.local_network.seen_our_node_info, - RoutingDomain::PublicInternet => self.public_internet.seen_our_node_info, + RoutingDomain::LocalNetwork => { + our_node_info_ts == self.local_network.last_seen_our_node_info_ts + } + RoutingDomain::PublicInternet => { + our_node_info_ts == self.public_internet.last_seen_our_node_info_ts + } } } @@ -455,7 +530,7 @@ impl BucketEntryInner { ///// stats methods // called every ROLLING_TRANSFERS_INTERVAL_SECS seconds - pub(super) fn roll_transfers(&mut self, last_ts: u64, cur_ts: u64) { + pub(super) fn roll_transfers(&mut self, last_ts: Timestamp, cur_ts: Timestamp) { self.transfer_stats_accounting.roll_transfers( last_ts, cur_ts, @@ -464,12 +539,12 @@ impl BucketEntryInner { } // Called for every round trip packet we receive - fn record_latency(&mut self, latency: u64) { + fn record_latency(&mut self, latency: TimestampDuration) { self.peer_stats.latency = Some(self.latency_stats_accounting.record_latency(latency)); } ///// state machine handling - pub(super) fn check_reliable(&self, cur_ts: u64) -> bool { + pub(super) fn check_reliable(&self, cur_ts: Timestamp) -> bool { // If we have had any failures to send, this is not reliable if self.peer_stats.rpc_stats.failed_to_send > 0 { return false; @@ -479,11 +554,11 @@ impl BucketEntryInner { match self.peer_stats.rpc_stats.first_consecutive_seen_ts { None => false, Some(ts) => { - cur_ts.saturating_sub(ts) >= (UNRELIABLE_PING_SPAN_SECS as u64 * 1000000u64) + cur_ts.saturating_sub(ts) >= TimestampDuration::new(UNRELIABLE_PING_SPAN_SECS as u64 * 1000000u64) } } } - pub(super) fn check_dead(&self, cur_ts: u64) -> bool { + pub(super) fn check_dead(&self, cur_ts: Timestamp) -> bool { // If we have failured to send NEVER_REACHED_PING_COUNT times in a row, the node is dead if self.peer_stats.rpc_stats.failed_to_send >= NEVER_REACHED_PING_COUNT { return true; @@ -494,20 +569,20 @@ impl BucketEntryInner { match self.peer_stats.rpc_stats.last_seen_ts { None => self.peer_stats.rpc_stats.recent_lost_answers < NEVER_REACHED_PING_COUNT, Some(ts) => { - cur_ts.saturating_sub(ts) >= (UNRELIABLE_PING_SPAN_SECS as u64 * 1000000u64) + cur_ts.saturating_sub(ts) >= TimestampDuration::new(UNRELIABLE_PING_SPAN_SECS as u64 * 1000000u64) } } } /// Return the last time we either saw a node, or asked it a question - fn latest_contact_time(&self) -> Option { + fn latest_contact_time(&self) -> Option { self.peer_stats .rpc_stats .last_seen_ts - .max(self.peer_stats.rpc_stats.last_question) + .max(self.peer_stats.rpc_stats.last_question_ts) } - fn needs_constant_ping(&self, cur_ts: u64, interval: u64) -> bool { + fn needs_constant_ping(&self, cur_ts: Timestamp, interval_us: TimestampDuration) -> bool { // If we have not either seen the node in the last 'interval' then we should ping it let latest_contact_time = self.latest_contact_time(); @@ -515,20 +590,20 @@ impl BucketEntryInner { None => true, Some(latest_contact_time) => { // If we haven't done anything with this node in 'interval' seconds - cur_ts.saturating_sub(latest_contact_time) >= (interval * 1000000u64) + cur_ts.saturating_sub(latest_contact_time) >= interval_us } } } // Check if this node needs a ping right now to validate it is still reachable - pub(super) fn needs_ping(&self, cur_ts: u64, needs_keepalive: bool) -> bool { + pub(super) fn needs_ping(&self, cur_ts: Timestamp, needs_keepalive: bool) -> bool { // See which ping pattern we are to use let state = self.state(cur_ts); // If this entry needs a keepalive (like a relay node), // then we should ping it regularly to keep our association alive if needs_keepalive { - return self.needs_constant_ping(cur_ts, KEEPALIVE_PING_INTERVAL_SECS as u64); + return self.needs_constant_ping(cur_ts, TimestampDuration::new(KEEPALIVE_PING_INTERVAL_SECS as u64 * 1000000u64)); } // If we don't have node status for this node, then we should ping it to get some node status @@ -561,8 +636,8 @@ impl BucketEntryInner { latest_contact_time.saturating_sub(start_of_reliable_time); retry_falloff_log( - reliable_last, - reliable_cur, + reliable_last.as_u64(), + reliable_cur.as_u64(), RELIABLE_PING_INTERVAL_START_SECS as u64 * 1_000_000u64, RELIABLE_PING_INTERVAL_MAX_SECS as u64 * 1_000_000u64, RELIABLE_PING_INTERVAL_MULTIPLIER, @@ -572,13 +647,13 @@ impl BucketEntryInner { } BucketEntryState::Unreliable => { // If we are in an unreliable state, we need a ping every UNRELIABLE_PING_INTERVAL_SECS seconds - self.needs_constant_ping(cur_ts, UNRELIABLE_PING_INTERVAL_SECS as u64) + self.needs_constant_ping(cur_ts, TimestampDuration::new(UNRELIABLE_PING_INTERVAL_SECS as u64 * 1000000u64)) } BucketEntryState::Dead => false, } } - pub(super) fn touch_last_seen(&mut self, ts: u64) { + pub(super) fn touch_last_seen(&mut self, ts: Timestamp) { // Mark the node as seen if self .peer_stats @@ -592,13 +667,13 @@ impl BucketEntryInner { self.peer_stats.rpc_stats.last_seen_ts = Some(ts); } - pub(super) fn _state_debug_info(&self, cur_ts: u64) -> String { + pub(super) fn _state_debug_info(&self, cur_ts: Timestamp) -> String { let first_consecutive_seen_ts = if let Some(first_consecutive_seen_ts) = self.peer_stats.rpc_stats.first_consecutive_seen_ts { format!( "{}s ago", - timestamp_to_secs(cur_ts.saturating_sub(first_consecutive_seen_ts)) + timestamp_to_secs(cur_ts.saturating_sub(first_consecutive_seen_ts).as_u64()) ) } else { "never".to_owned() @@ -606,7 +681,7 @@ impl BucketEntryInner { let last_seen_ts_str = if let Some(last_seen_ts) = self.peer_stats.rpc_stats.last_seen_ts { format!( "{}s ago", - timestamp_to_secs(cur_ts.saturating_sub(last_seen_ts)) + timestamp_to_secs(cur_ts.saturating_sub(last_seen_ts).as_u64()) ) } else { "never".to_owned() @@ -623,30 +698,30 @@ impl BucketEntryInner { //////////////////////////////////////////////////////////////// /// Called when rpc processor things happen - pub(super) fn question_sent(&mut self, ts: u64, bytes: u64, expects_answer: bool) { + pub(super) fn question_sent(&mut self, ts: Timestamp, bytes: ByteCount, expects_answer: bool) { self.transfer_stats_accounting.add_up(bytes); self.peer_stats.rpc_stats.messages_sent += 1; self.peer_stats.rpc_stats.failed_to_send = 0; if expects_answer { self.peer_stats.rpc_stats.questions_in_flight += 1; - self.peer_stats.rpc_stats.last_question = Some(ts); + self.peer_stats.rpc_stats.last_question_ts = Some(ts); } } - pub(super) fn question_rcvd(&mut self, ts: u64, bytes: u64) { + pub(super) fn question_rcvd(&mut self, ts: Timestamp, bytes: ByteCount) { self.transfer_stats_accounting.add_down(bytes); self.peer_stats.rpc_stats.messages_rcvd += 1; self.touch_last_seen(ts); } - pub(super) fn answer_sent(&mut self, bytes: u64) { + pub(super) fn answer_sent(&mut self, bytes: ByteCount) { self.transfer_stats_accounting.add_up(bytes); self.peer_stats.rpc_stats.messages_sent += 1; self.peer_stats.rpc_stats.failed_to_send = 0; } - pub(super) fn answer_rcvd(&mut self, send_ts: u64, recv_ts: u64, bytes: u64) { + pub(super) fn answer_rcvd(&mut self, send_ts: Timestamp, recv_ts: Timestamp, bytes: ByteCount) { self.transfer_stats_accounting.add_down(bytes); self.peer_stats.rpc_stats.messages_rcvd += 1; self.peer_stats.rpc_stats.questions_in_flight -= 1; - self.record_latency(recv_ts - send_ts); + self.record_latency(recv_ts.saturating_sub(send_ts)); self.touch_last_seen(recv_ts); self.peer_stats.rpc_stats.recent_lost_answers = 0; } @@ -655,9 +730,9 @@ impl BucketEntryInner { self.peer_stats.rpc_stats.questions_in_flight -= 1; self.peer_stats.rpc_stats.recent_lost_answers += 1; } - pub(super) fn failed_to_send(&mut self, ts: u64, expects_answer: bool) { + pub(super) fn failed_to_send(&mut self, ts: Timestamp, expects_answer: bool) { if expects_answer { - self.peer_stats.rpc_stats.last_question = Some(ts); + self.peer_stats.rpc_stats.last_question_ts = Some(ts); } self.peer_stats.rpc_stats.failed_to_send += 1; self.peer_stats.rpc_stats.first_consecutive_seen_ts = None; @@ -672,7 +747,7 @@ pub struct BucketEntry { impl BucketEntry { pub(super) fn new() -> Self { - let now = intf::get_timestamp(); + let now = get_aligned_timestamp(); Self { ref_count: AtomicU32::new(0), inner: RwLock::new(BucketEntryInner { @@ -680,12 +755,12 @@ impl BucketEntry { updated_since_last_network_change: false, last_connections: BTreeMap::new(), local_network: BucketEntryLocalNetwork { - seen_our_node_info: false, + last_seen_our_node_info_ts: Timestamp::new(0u64), signed_node_info: None, node_status: None, }, public_internet: BucketEntryPublicInternet { - seen_our_node_info: false, + last_seen_our_node_info_ts: Timestamp::new(0u64), signed_node_info: None, node_status: None, }, diff --git a/veilid-core/src/routing_table/debug.rs b/veilid-core/src/routing_table/debug.rs index 3170fae6..64299c78 100644 --- a/veilid-core/src/routing_table/debug.rs +++ b/veilid-core/src/routing_table/debug.rs @@ -1,4 +1,5 @@ use super::*; +use routing_table::tasks::bootstrap::BOOTSTRAP_TXT_VERSION; impl RoutingTable { pub(crate) fn debug_info_nodeinfo(&self) -> String { @@ -103,7 +104,7 @@ impl RoutingTable { pub(crate) fn debug_info_entries(&self, limit: usize, min_state: BucketEntryState) -> String { let inner = self.inner.read(); let inner = &*inner; - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); let mut out = String::new(); @@ -163,7 +164,7 @@ impl RoutingTable { pub(crate) fn debug_info_buckets(&self, min_state: BucketEntryState) -> String { let inner = self.inner.read(); let inner = &*inner; - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); let mut out = String::new(); const COLS: usize = 16; diff --git a/veilid-core/src/routing_table/mod.rs b/veilid-core/src/routing_table/mod.rs index 89370fc4..8ddd5c5a 100644 --- a/veilid-core/src/routing_table/mod.rs +++ b/veilid-core/src/routing_table/mod.rs @@ -11,11 +11,11 @@ mod routing_table_inner; mod stats_accounting; mod tasks; +use crate::*; + use crate::crypto::*; use crate::network_manager::*; use crate::rpc_processor::*; -use crate::xx::*; -use crate::*; use bucket::*; pub use bucket_entry::*; pub use debug::*; @@ -31,6 +31,16 @@ pub use stats_accounting::*; ////////////////////////////////////////////////////////////////////////// +/// How frequently we tick the relay management routine +pub const RELAY_MANAGEMENT_INTERVAL_SECS: u32 = 1; + +/// How frequently we tick the private route management routine +pub const PRIVATE_ROUTE_MANAGEMENT_INTERVAL_SECS: u32 = 1; + +// Connectionless protocols like UDP are dependent on a NAT translation timeout +// We should ping them with some frequency and 30 seconds is typical timeout +pub const CONNECTIONLESS_TIMEOUT_SECS: u32 = 29; + pub type LowLevelProtocolPorts = BTreeSet<(LowLevelProtocolType, AddressType, u16)>; pub type ProtocolToPortMapping = BTreeMap<(ProtocolType, AddressType), (LowLevelProtocolType, u16)>; #[derive(Clone, Debug)] @@ -41,7 +51,7 @@ pub struct LowLevelPortInfo { pub type RoutingTableEntryFilter<'t> = Box>) -> bool + Send + 't>; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct RoutingTableHealth { /// Number of reliable (responsive) entries in the routing table pub reliable_entry_count: usize, @@ -49,6 +59,10 @@ pub struct RoutingTableHealth { pub unreliable_entry_count: usize, /// Number of dead (always unresponsive) entries in the routing table pub dead_entry_count: usize, + /// If PublicInternet network class is valid yet + pub public_internet_ready: bool, + /// If LocalNetwork network class is valid yet + pub local_network_ready: bool, } pub(super) struct RoutingTableUnlockedInner { @@ -64,8 +78,18 @@ pub(super) struct RoutingTableUnlockedInner { kick_queue: Mutex>, /// Background process for computing statistics rolling_transfers_task: TickTask, - /// Backgroup process to purge dead routing table entries when necessary + /// Background process to purge dead routing table entries when necessary kick_buckets_task: TickTask, + /// Background process to get our initial routing table + bootstrap_task: TickTask, + /// Background process to ensure we have enough nodes in our routing table + peer_minimum_refresh_task: TickTask, + /// Background process to check nodes to see if they are still alive and for reliability + ping_validator_task: TickTask, + /// Background process to keep relays up + relay_management_task: TickTask, + /// Background process to keep private routes up + private_route_management_task: TickTask, } #[derive(Clone)] @@ -88,6 +112,11 @@ impl RoutingTable { kick_queue: Mutex::new(BTreeSet::default()), rolling_transfers_task: TickTask::new(ROLLING_TRANSFERS_INTERVAL_SECS), kick_buckets_task: TickTask::new(1), + bootstrap_task: TickTask::new(1), + peer_minimum_refresh_task: TickTask::new_ms(c.network.dht.min_peer_refresh_time_ms), + ping_validator_task: TickTask::new(1), + relay_management_task: TickTask::new(RELAY_MANAGEMENT_INTERVAL_SECS), + private_route_management_task: TickTask::new(PRIVATE_ROUTE_MANAGEMENT_INTERVAL_SECS), } } pub fn new(network_manager: NetworkManager) -> Self { @@ -99,38 +128,8 @@ impl RoutingTable { unlocked_inner, }; - // Set rolling transfers tick task - { - let this2 = this.clone(); - this.unlocked_inner - .rolling_transfers_task - .set_routine(move |s, l, t| { - Box::pin( - this2 - .clone() - .rolling_transfers_task_routine(s, l, t) - .instrument(trace_span!( - parent: None, - "RoutingTable rolling transfers task routine" - )), - ) - }); - } + this.start_tasks(); - // Set kick buckets tick task - { - let this2 = this.clone(); - this.unlocked_inner - .kick_buckets_task - .set_routine(move |s, l, t| { - Box::pin( - this2 - .clone() - .kick_buckets_task_routine(s, l, t) - .instrument(trace_span!(parent: None, "kick buckets task routine")), - ) - }); - } this } @@ -140,6 +139,15 @@ impl RoutingTable { pub fn rpc_processor(&self) -> RPCProcessor { self.network_manager().rpc_processor() } + pub fn update_callback(&self) -> UpdateCallback { + self.network_manager().update_callback() + } + pub fn with_config(&self, f: F) -> R + where + F: FnOnce(&VeilidConfigInner) -> R, + { + f(&*self.unlocked_inner.config.get()) + } pub fn node_id(&self) -> DHTKey { self.unlocked_inner.node_id @@ -194,15 +202,8 @@ impl RoutingTable { pub async fn terminate(&self) { debug!("starting routing table terminate"); - // Cancel all tasks being ticked - debug!("stopping rolling transfers task"); - if let Err(e) = self.unlocked_inner.rolling_transfers_task.stop().await { - error!("rolling_transfers_task not stopped: {}", e); - } - debug!("stopping kick buckets task"); - if let Err(e) = self.unlocked_inner.kick_buckets_task.stop().await { - error!("kick_buckets_task not stopped: {}", e); - } + // Stop tasks + self.stop_tasks().await; // Load bucket entries from table db if possible debug!("saving routing table entries"); @@ -240,7 +241,7 @@ impl RoutingTable { let table_store = self.network_manager().table_store(); let tdb = table_store.open("routing_table", 1).await?; let bucket_count = bucketvec.len(); - let mut dbx = tdb.transact(); + let dbx = tdb.transact(); if let Err(e) = dbx.store_rkyv(0, b"bucket_count", &bucket_count) { dbx.rollback(); return Err(e); @@ -249,7 +250,7 @@ impl RoutingTable { for (n, b) in bucketvec.iter().enumerate() { dbx.store(0, format!("bucket_{}", n).as_bytes(), b) } - dbx.commit()?; + dbx.commit().await?; Ok(()) } @@ -385,15 +386,30 @@ impl RoutingTable { } /// Return a copy of our node's peerinfo - pub fn get_own_peer_info(&self, routing_domain: RoutingDomain) -> PeerInfo { + pub fn get_own_peer_info(&self, routing_domain: RoutingDomain) -> Option { self.inner.read().get_own_peer_info(routing_domain) } + /// Return the best effort copy of our node's peerinfo + /// This may be invalid and should not be passed to other nodes, + /// but may be used for contact method calculation + pub fn get_best_effort_own_peer_info(&self, routing_domain: RoutingDomain) -> PeerInfo { + self.inner + .read() + .get_best_effort_own_peer_info(routing_domain) + } + /// If we have a valid network class in this routing domain, then our 'NodeInfo' is valid + /// If this is true, we can get our final peer info, otherwise we only have a 'best effort' peer info pub fn has_valid_own_node_info(&self, routing_domain: RoutingDomain) -> bool { self.inner.read().has_valid_own_node_info(routing_domain) } + /// Return our current node info timestamp + pub fn get_own_node_info_ts(&self, routing_domain: RoutingDomain) -> Option { + self.inner.read().get_own_node_info_ts(routing_domain) + } + /// Return the domain's currently registered network class pub fn get_network_class(&self, routing_domain: RoutingDomain) -> Option { self.inner.read().get_network_class(routing_domain) @@ -453,28 +469,17 @@ impl RoutingTable { .get_entry_count(routing_domain_set, min_state) } - pub fn get_nodes_needing_updates( - &self, - routing_domain: RoutingDomain, - cur_ts: u64, - all: bool, - ) -> Vec { - self.inner - .read() - .get_nodes_needing_updates(self.clone(), routing_domain, cur_ts, all) - } - pub fn get_nodes_needing_ping( &self, routing_domain: RoutingDomain, - cur_ts: u64, + cur_ts: Timestamp, ) -> Vec { self.inner .read() .get_nodes_needing_ping(self.clone(), routing_domain, cur_ts) } - pub fn get_all_nodes(&self, cur_ts: u64) -> Vec { + pub fn get_all_nodes(&self, cur_ts: Timestamp) -> Vec { let inner = self.inner.read(); inner.get_all_nodes(self.clone(), cur_ts) } @@ -541,7 +546,7 @@ impl RoutingTable { &self, node_id: DHTKey, descriptor: ConnectionDescriptor, - timestamp: u64, + timestamp: Timestamp, ) -> Option { self.inner.write().register_node_with_existing_connection( self.clone(), @@ -551,21 +556,6 @@ impl RoutingTable { ) } - /// Ticks about once per second - /// to run tick tasks which may run at slower tick rates as configured - pub async fn tick(&self) -> EyreResult<()> { - // Do rolling transfers every ROLLING_TRANSFERS_INTERVAL_SECS secs - self.unlocked_inner.rolling_transfers_task.tick().await?; - - // Kick buckets task - let kick_bucket_queue_count = self.unlocked_inner.kick_queue.lock().len(); - if kick_bucket_queue_count > 0 { - self.unlocked_inner.kick_buckets_task.tick().await?; - } - - Ok(()) - } - ////////////////////////////////////////////////////////////////////// // Routing Table Health Metrics @@ -788,7 +778,7 @@ impl RoutingTable { pub fn find_peers_with_sort_and_filter( &self, node_count: usize, - cur_ts: u64, + cur_ts: Timestamp, filters: VecDeque, compare: C, transform: T, @@ -905,7 +895,7 @@ impl RoutingTable { // and then contact those nodes to inform -them- that we exist // Ask bootstrap server for nodes closest to our own node - let closest_nodes = network_result_value_or_log!(debug match self.find_self(node_ref.clone()).await { + let closest_nodes = network_result_value_or_log!(match self.find_self(node_ref.clone()).await { Err(e) => { log_rtab!(error "find_self failed for {:?}: {:?}", @@ -921,7 +911,7 @@ impl RoutingTable { // Ask each node near us to find us as well if wide { for closest_nr in closest_nodes { - network_result_value_or_log!(debug match self.find_self(closest_nr.clone()).await { + network_result_value_or_log!(match self.find_self(closest_nr.clone()).await { Err(e) => { log_rtab!(error "find_self failed for {:?}: {:?}", @@ -983,7 +973,7 @@ impl RoutingTable { pub fn find_inbound_relay( &self, routing_domain: RoutingDomain, - cur_ts: u64, + cur_ts: Timestamp, ) -> Option { // Get relay filter function let relay_node_filter = match routing_domain { diff --git a/veilid-core/src/routing_table/node_ref.rs b/veilid-core/src/routing_table/node_ref.rs index ae77c4df..7e53effe 100644 --- a/veilid-core/src/routing_table/node_ref.rs +++ b/veilid-core/src/routing_table/node_ref.rs @@ -2,10 +2,6 @@ use super::*; use crate::crypto::*; use alloc::fmt; -// Connectionless protocols like UDP are dependent on a NAT translation timeout -// We should ping them with some frequency and 30 seconds is typical timeout -const CONNECTIONLESS_TIMEOUT_SECS: u32 = 29; - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// pub struct NodeRefBaseCommon { @@ -87,8 +83,9 @@ pub trait NodeRefBase: Sized { } fn best_routing_domain(&self) -> Option { - self.operate(|_rti, e| { + self.operate(|rti, e| { e.best_routing_domain( + rti, self.common() .filter .as_ref() @@ -122,7 +119,7 @@ pub trait NodeRefBase: Sized { fn set_min_max_version(&self, min_max_version: VersionRange) { self.operate_mut(|_rti, e| e.set_min_max_version(min_max_version)) } - fn state(&self, cur_ts: u64) -> BucketEntryState { + fn state(&self, cur_ts: Timestamp) -> BucketEntryState { self.operate(|_rti, e| e.state(cur_ts)) } fn peer_stats(&self) -> PeerStats { @@ -143,11 +140,22 @@ pub trait NodeRefBase: Sized { .unwrap_or(false) }) } - fn has_seen_our_node_info(&self, routing_domain: RoutingDomain) -> bool { - self.operate(|_rti, e| e.has_seen_our_node_info(routing_domain)) + fn node_info_ts(&self, routing_domain: RoutingDomain) -> Timestamp { + self.operate(|_rti, e| { + e.signed_node_info(routing_domain) + .map(|sni| sni.timestamp()) + .unwrap_or(0u64.into()) + }) } - fn set_seen_our_node_info(&self, routing_domain: RoutingDomain) { - self.operate_mut(|_rti, e| e.set_seen_our_node_info(routing_domain, true)); + fn has_seen_our_node_info_ts( + &self, + routing_domain: RoutingDomain, + our_node_info_ts: Timestamp, + ) -> bool { + self.operate(|_rti, e| e.has_seen_our_node_info_ts(routing_domain, our_node_info_ts)) + } + fn set_our_node_info_ts(&self, routing_domain: RoutingDomain, seen_ts: Timestamp) { + self.operate_mut(|_rti, e| e.set_our_node_info_ts(routing_domain, seen_ts)); } fn network_class(&self, routing_domain: RoutingDomain) -> Option { self.operate(|_rt, e| e.node_info(routing_domain).map(|n| n.network_class)) @@ -260,28 +268,8 @@ pub trait NodeRefBase: Sized { // Get the last connections and the last time we saw anything with this connection // Filtered first and then sorted by most recent self.operate(|rti, e| { - let last_connections = e.last_connections(rti, self.common().filter.clone()); - - // Do some checks to ensure these are possibly still 'live' - for (last_connection, last_seen) in last_connections { - // Should we check the connection table? - if last_connection.protocol_type().is_connection_oriented() { - // Look the connection up in the connection manager and see if it's still there - let connection_manager = - rti.unlocked_inner.network_manager.connection_manager(); - if connection_manager.get_connection(last_connection).is_some() { - return Some(last_connection); - } - } else { - // If this is not connection oriented, then we check our last seen time - // to see if this mapping has expired (beyond our timeout) - let cur_ts = intf::get_timestamp(); - if (last_seen + (CONNECTIONLESS_TIMEOUT_SECS as u64 * 1_000_000u64)) >= cur_ts { - return Some(last_connection); - } - } - } - None + let last_connections = e.last_connections(rti, true, self.common().filter.clone()); + last_connections.first().map(|x| x.0) }) } @@ -289,7 +277,7 @@ pub trait NodeRefBase: Sized { self.operate_mut(|_rti, e| e.clear_last_connections()) } - fn set_last_connection(&self, connection_descriptor: ConnectionDescriptor, ts: u64) { + fn set_last_connection(&self, connection_descriptor: ConnectionDescriptor, ts: Timestamp) { self.operate_mut(|rti, e| { e.set_last_connection(connection_descriptor, ts); rti.touch_recent_peer(self.common().node_id, connection_descriptor); @@ -309,29 +297,29 @@ pub trait NodeRefBase: Sized { }) } - fn stats_question_sent(&self, ts: u64, bytes: u64, expects_answer: bool) { + fn stats_question_sent(&self, ts: Timestamp, bytes: Timestamp, expects_answer: bool) { self.operate_mut(|rti, e| { rti.transfer_stats_accounting().add_up(bytes); e.question_sent(ts, bytes, expects_answer); }) } - fn stats_question_rcvd(&self, ts: u64, bytes: u64) { + fn stats_question_rcvd(&self, ts: Timestamp, bytes: ByteCount) { self.operate_mut(|rti, e| { rti.transfer_stats_accounting().add_down(bytes); e.question_rcvd(ts, bytes); }) } - fn stats_answer_sent(&self, bytes: u64) { + fn stats_answer_sent(&self, bytes: ByteCount) { self.operate_mut(|rti, e| { rti.transfer_stats_accounting().add_up(bytes); e.answer_sent(bytes); }) } - fn stats_answer_rcvd(&self, send_ts: u64, recv_ts: u64, bytes: u64) { + fn stats_answer_rcvd(&self, send_ts: Timestamp, recv_ts: Timestamp, bytes: ByteCount) { self.operate_mut(|rti, e| { rti.transfer_stats_accounting().add_down(bytes); rti.latency_stats_accounting() - .record_latency(recv_ts - send_ts); + .record_latency(recv_ts.saturating_sub(send_ts)); e.answer_rcvd(send_ts, recv_ts, bytes); }) } @@ -340,7 +328,7 @@ pub trait NodeRefBase: Sized { e.question_lost(); }) } - fn stats_failed_to_send(&self, ts: u64, expects_answer: bool) { + fn stats_failed_to_send(&self, ts: Timestamp, expects_answer: bool) { self.operate_mut(|_rti, e| { e.failed_to_send(ts, expects_answer); }) diff --git a/veilid-core/src/routing_table/privacy.rs b/veilid-core/src/routing_table/privacy.rs index 352b716b..e902622f 100644 --- a/veilid-core/src/routing_table/privacy.rs +++ b/veilid-core/src/routing_table/privacy.rs @@ -83,6 +83,14 @@ impl PrivateRoute { } } + /// Check if this is a stub route + pub fn is_stub(&self) -> bool { + if let PrivateRouteHops::FirstHop(first_hop) = &self.hops { + return first_hop.next_hop.is_none(); + } + false + } + /// Remove the first unencrypted hop if possible pub fn pop_first_hop(&mut self) -> Option { match &mut self.hops { @@ -108,6 +116,18 @@ impl PrivateRoute { PrivateRouteHops::Empty => return None, } } + + pub fn first_hop_node_id(&self) -> Option { + let PrivateRouteHops::FirstHop(pr_first_hop) = &self.hops else { + return None; + }; + + // Get the safety route to use from the spec + Some(match &pr_first_hop.node { + RouteNode::NodeId(n) => n.key, + RouteNode::PeerInfo(p) => p.node_id.key, + }) + } } impl fmt::Display for PrivateRoute { @@ -149,6 +169,8 @@ pub struct SafetyRoute { impl SafetyRoute { pub fn new_stub(public_key: DHTKey, private_route: PrivateRoute) -> Self { + // First hop should have already been popped off for stubbed safety routes since + // we are sending directly to the first hop assert!(matches!(private_route.hops, PrivateRouteHops::Data(_))); Self { public_key, @@ -156,6 +178,9 @@ impl SafetyRoute { hops: SafetyRouteHops::Private(private_route), } } + pub fn is_stub(&self) -> bool { + matches!(self.hops, SafetyRouteHops::Private(_)) + } } impl fmt::Display for SafetyRoute { diff --git a/veilid-core/src/routing_table/route_spec_store.rs b/veilid-core/src/routing_table/route_spec_store.rs index 66d80cf6..4c7d8d5b 100644 --- a/veilid-core/src/routing_table/route_spec_store.rs +++ b/veilid-core/src/routing_table/route_spec_store.rs @@ -7,7 +7,19 @@ use rkyv::{ /// The size of the remote private route cache const REMOTE_PRIVATE_ROUTE_CACHE_SIZE: usize = 1024; /// Remote private route cache entries expire in 5 minutes if they haven't been used -const REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY: u64 = 300_000_000u64; +const REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY: TimestampDuration = TimestampDuration::new(300_000_000u64); +/// Amount of time a route can remain idle before it gets tested +const ROUTE_MIN_IDLE_TIME_MS: u32 = 30_000; +/// The size of the compiled route cache +const COMPILED_ROUTE_CACHE_SIZE: usize = 256; + + +// Compiled route key for caching +#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] +struct CompiledRouteCacheKey { + sr_pubkey: DHTKey, + pr_pubkey: DHTKey, +} /// Compiled route (safety route + private route) #[derive(Clone, Debug)] @@ -27,52 +39,193 @@ pub struct KeyPair { secret: DHTKeySecret, } -#[derive(Clone, Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[derive(Clone, Debug, Default, RkyvArchive, RkyvSerialize, RkyvDeserialize)] #[archive_attr(repr(C), derive(CheckBytes))] -pub struct RouteSpecDetail { - /// Secret key +pub struct RouteStats { + /// Consecutive failed to send count #[with(Skip)] - pub secret_key: DHTKeySecret, - /// Route hops - pub hops: Vec, - /// Route noderefs + pub failed_to_send: u32, + /// Questions lost #[with(Skip)] - hop_node_refs: Vec, + pub questions_lost: u32, + /// Timestamp of when the route was created + pub created_ts: Timestamp, + /// Timestamp of when the route was last checked for validity + #[with(Skip)] + pub last_tested_ts: Option, + /// Timestamp of when the route was last sent to + #[with(Skip)] + pub last_sent_ts: Option, + /// Timestamp of when the route was last received over + #[with(Skip)] + pub last_received_ts: Option, /// Transfers up and down - transfer_stats_down_up: TransferStatsDownUp, + pub transfer_stats_down_up: TransferStatsDownUp, /// Latency stats - latency_stats: LatencyStats, + pub latency_stats: LatencyStats, /// Accounting mechanism for this route's RPC latency #[with(Skip)] latency_stats_accounting: LatencyStatsAccounting, /// Accounting mechanism for the bandwidth across this route #[with(Skip)] transfer_stats_accounting: TransferStatsAccounting, +} + +impl RouteStats { + /// Make new route stats + pub fn new(created_ts: Timestamp) -> Self { + Self { + created_ts, + ..Default::default() + } + } + /// Mark a route as having failed to send + pub fn record_send_failed(&mut self) { + self.failed_to_send += 1; + } + + /// Mark a route as having lost a question + pub fn record_question_lost(&mut self) { + self.questions_lost += 1; + } + + /// Mark a route as having received something + pub fn record_received(&mut self, cur_ts: Timestamp, bytes: ByteCount) { + self.last_received_ts = Some(cur_ts); + self.last_tested_ts = Some(cur_ts); + self.transfer_stats_accounting.add_down(bytes); + } + + /// Mark a route as having been sent to + pub fn record_sent(&mut self, cur_ts: Timestamp, bytes: ByteCount) { + self.last_sent_ts = Some(cur_ts); + self.transfer_stats_accounting.add_up(bytes); + } + + /// Mark a route as having been sent to + pub fn record_latency(&mut self, latency: TimestampDuration) { + self.latency_stats = self.latency_stats_accounting.record_latency(latency); + } + + /// Mark a route as having been tested + pub fn record_tested(&mut self, cur_ts: Timestamp) { + self.last_tested_ts = Some(cur_ts); + + // Reset question_lost and failed_to_send if we test clean + self.failed_to_send = 0; + self.questions_lost = 0; + } + + /// Roll transfers for these route stats + pub fn roll_transfers(&mut self, last_ts: Timestamp, cur_ts: Timestamp) { + self.transfer_stats_accounting.roll_transfers( + last_ts, + cur_ts, + &mut self.transfer_stats_down_up, + ) + } + + /// Get the latency stats + pub fn latency_stats(&self) -> &LatencyStats { + &self.latency_stats + } + + /// Get the transfer stats + pub fn transfer_stats(&self) -> &TransferStatsDownUp { + &self.transfer_stats_down_up + } + + /// Reset stats when network restarts + pub fn reset(&mut self) { + self.last_tested_ts = None; + self.last_sent_ts = None; + self.last_received_ts = None; + } + + /// Check if a route needs testing + pub fn needs_testing(&self, cur_ts: Timestamp) -> bool { + // Has the route had any failures lately? + if self.questions_lost > 0 || self.failed_to_send > 0 { + // If so, always test + return true; + } + + // Has the route been tested within the idle time we'd want to check things? + // (also if we've received successfully over the route, this will get set) + if let Some(last_tested_ts) = self.last_tested_ts { + if cur_ts.saturating_sub(last_tested_ts) + > TimestampDuration::new(ROUTE_MIN_IDLE_TIME_MS as u64 * 1000u64) + { + return true; + } + } else { + // If this route has never been tested, it needs to be + return true; + } + + false + } +} + +#[derive(Clone, Debug, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct RouteSpecDetail { + /// Secret key + #[with(Skip)] + secret_key: DHTKeySecret, + /// Route hops + hops: Vec, + /// Route noderefs + #[with(Skip)] + hop_node_refs: Vec, /// Published private route, do not reuse for ephemeral routes /// Not serialized because all routes should be re-published when restarting #[with(Skip)] published: bool, - // Can optimize the rendering of this route, using node ids only instead of full peer info - #[with(Skip)] - reachable: bool, - /// Timestamp of when the route was created - created_ts: u64, - /// Timestamp of when the route was last checked for validity - last_checked_ts: Option, - /// Timestamp of when the route was last used for anything - last_used_ts: Option, /// Directions this route is guaranteed to work in #[with(RkyvEnumSet)] directions: DirectionSet, /// Stability preference (prefer reliable nodes over faster) - pub stability: Stability, - /// Sequencing preference (connection oriented protocols vs datagram) - pub sequencing: Sequencing, + stability: Stability, + /// Sequencing capability (connection oriented protocols vs datagram) + can_do_sequenced: bool, + /// Stats + stats: RouteStats, +} + +impl RouteSpecDetail { + pub fn get_stats(&self) -> &RouteStats { + &self.stats + } + pub fn get_stats_mut(&mut self) -> &mut RouteStats { + &mut self.stats + } + pub fn is_published(&self) -> bool { + self.published + } + pub fn hop_count(&self) -> usize { + self.hops.len() + } + pub fn get_secret_key(&self) -> DHTKeySecret { + self.secret_key + } + pub fn get_stability(&self) -> Stability { + self.stability + } + pub fn is_sequencing_match(&self, sequencing: Sequencing) -> bool { + match sequencing { + Sequencing::NoPreference => true, + Sequencing::PreferOrdered => true, + Sequencing::EnsureOrdered => { + self.can_do_sequenced + } + } + } } /// The core representation of the RouteSpecStore that can be serialized #[derive(Debug, Clone, Default, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] +#[archive_attr(repr(C, align(8)), derive(CheckBytes))] pub struct RouteSpecStoreContent { /// All of the routes we have allocated so far details: HashMap, @@ -80,15 +233,24 @@ pub struct RouteSpecStoreContent { /// What remote private routes have seen #[derive(Debug, Clone, Default)] -struct RemotePrivateRouteInfo { +pub struct RemotePrivateRouteInfo { // The private route itself private_route: Option, - /// Timestamp of when the route was last used for anything - last_used_ts: u64, - /// The time this remote private route last responded - last_replied_ts: Option, /// Did this remote private route see our node info due to no safety route in use - seen_our_node_info: bool, + last_seen_our_node_info_ts: Timestamp, + /// Last time this remote private route was requested for any reason (cache expiration) + last_touched_ts: Timestamp, + /// Stats + stats: RouteStats, +} + +impl RemotePrivateRouteInfo { + pub fn get_stats(&self) -> &RouteStats { + &self.stats + } + pub fn get_stats_mut(&mut self) -> &mut RouteStats { + &mut self.stats + } } /// Ephemeral data used to help the RouteSpecStore operate efficiently @@ -102,6 +264,12 @@ pub struct RouteSpecStoreCache { hop_cache: HashSet>, /// Has a remote private route responded to a question and when remote_private_route_cache: LruCache, + /// Compiled route cache + compiled_route_cache: LruCache, + /// List of dead allocated routes + dead_routes: Vec, + /// List of dead remote routes + dead_remote_routes: Vec, } impl Default for RouteSpecStoreCache { @@ -111,6 +279,9 @@ impl Default for RouteSpecStoreCache { used_end_nodes: Default::default(), hop_cache: Default::default(), remote_private_route_cache: LruCache::new(REMOTE_PRIVATE_ROUTE_CACHE_SIZE), + compiled_route_cache: LruCache::new(COMPILED_ROUTE_CACHE_SIZE), + dead_routes: Default::default(), + dead_remote_routes: Default::default(), } } } @@ -180,8 +351,8 @@ fn _get_route_permutation_count(hop_count: usize) -> usize { // hop_count = 4 -> 3! -> 6 (3..hop_count).into_iter().fold(2usize, |acc, x| acc * x) } - -type PermFunc<'t> = Box Option<(Vec, Vec)> + Send + 't>; +type PermReturnType = (Vec, Vec, bool); +type PermFunc<'t> = Box Option + Send + 't>; /// get the route permutation at particular 'perm' index, starting at the 'start' index /// for a set of 'hop_count' nodes. the first node is always fixed, and the maximum @@ -191,7 +362,7 @@ fn with_route_permutations( hop_count: usize, start: usize, f: &PermFunc, -) -> Option<(Vec, Vec)> { +) -> Option { if hop_count == 0 { unreachable!(); } @@ -210,7 +381,7 @@ fn with_route_permutations( permutation: &mut [usize], size: usize, f: &PermFunc, - ) -> Option<(Vec, Vec)> { + ) -> Option { if size == 1 { return f(&permutation); } @@ -254,6 +425,7 @@ impl RouteSpecStore { } } + #[instrument(level = "trace", skip(routing_table), err)] pub async fn load(routing_table: RoutingTable) -> EyreResult { let (max_route_hop_count, default_route_hop_count) = { let config = routing_table.network_manager().config(); @@ -326,6 +498,7 @@ impl RouteSpecStore { Ok(rss) } + #[instrument(level = "trace", skip(self), err)] pub async fn save(&self) -> EyreResult<()> { let content = { let inner = self.inner.lock(); @@ -339,7 +512,7 @@ impl RouteSpecStore { .network_manager() .table_store(); let rsstdb = table_store.open("RouteSpecStore", 1).await?; - rsstdb.store_rkyv(0, b"content", &content)?; + rsstdb.store_rkyv(0, b"content", &content).await?; // // Keep secrets in protected store as well let pstore = self @@ -361,6 +534,29 @@ impl RouteSpecStore { Ok(()) } + #[instrument(level = "trace", skip(self))] + pub fn send_route_update(&self) { + let update_callback = self.unlocked_inner.routing_table.update_callback(); + + let (dead_routes, dead_remote_routes) = { + let mut inner = self.inner.lock(); + if inner.cache.dead_routes.is_empty() && inner.cache.dead_remote_routes.is_empty() { + // Nothing to do + return; + } + let dead_routes = core::mem::take(&mut inner.cache.dead_routes); + let dead_remote_routes = core::mem::take(&mut inner.cache.dead_remote_routes); + (dead_routes, dead_remote_routes) + }; + + let update = VeilidUpdate::Route(VeilidStateRoute { + dead_routes, + dead_remote_routes, + }); + + update_callback(update); + } + fn add_to_cache(cache: &mut RouteSpecStoreCache, cache_key: Vec, rsd: &RouteSpecDetail) { if !cache.hop_cache.insert(cache_key) { panic!("route should never be inserted twice"); @@ -413,6 +609,7 @@ impl RouteSpecStore { /// Prefers nodes that are not currently in use by another route /// The route is not yet tested for its reachability /// Returns None if no route could be allocated at this time + #[instrument(level = "trace", skip(self), ret, err)] pub fn allocate_route( &self, stability: Stability, @@ -436,6 +633,7 @@ impl RouteSpecStore { ) } + #[instrument(level = "trace", skip(self, inner, rti), ret, err)] fn allocate_route_inner( &self, inner: &mut RouteSpecStoreInner, @@ -456,13 +654,17 @@ impl RouteSpecStore { bail!("Not allocating route longer than max route hop count"); } + let Some(our_peer_info) = rti.get_own_peer_info(RoutingDomain::PublicInternet) else { + bail!("Can't allocate route until we have our own peer info"); + }; + // Get relay node id if we have one let opt_relay_id = rti .relay_node(RoutingDomain::PublicInternet) .map(|nr| nr.node_id()); // Get list of all nodes, and sort them for selection - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); let filter = Box::new( move |rti: &RoutingTableInner, k: DHTKey, v: Option>| -> bool { // Exclude our own node from routes @@ -472,22 +674,43 @@ impl RouteSpecStore { let v = v.unwrap(); // Exclude our relay if we have one - if let Some(relay_id) = opt_relay_id { - if k == relay_id { + if let Some(own_relay_id) = opt_relay_id { + if k == own_relay_id { return false; } } - // Exclude nodes on our local network - let on_local_network = v.with(rti, |_rti, e| { - e.node_info(RoutingDomain::LocalNetwork).is_some() - }); - if on_local_network { + // Exclude nodes we have specifically chosen to avoid + if avoid_node_ids.contains(&k) { return false; } - // Exclude nodes we have specifically chosen to avoid - if avoid_node_ids.contains(&k) { + // Process node info exclusions + let keep = v.with(rti, |_rti, e| { + // Exclude nodes on our local network + if e.node_info(RoutingDomain::LocalNetwork).is_some() { + return false; + } + // Exclude nodes that have no publicinternet signednodeinfo + let Some(sni) = e.signed_node_info(RoutingDomain::PublicInternet) else { + return false; + }; + // Relay check + if let Some(relay_id) = sni.relay_id() { + // Exclude nodes whose relays we have chosen to avoid + if avoid_node_ids.contains(&relay_id.key) { + return false; + } + // Exclude nodes whose relay is our own relay if we have one + if let Some(own_relay_id) = opt_relay_id { + if own_relay_id == relay_id.key { + return false; + } + } + } + return true; + }); + if !keep { return false; } @@ -551,6 +774,24 @@ impl RouteSpecStore { return cmp_used; } + // apply sequencing preference + // ensureordered will be taken care of by filter + // and nopreference doesn't care + if matches!(sequencing, Sequencing::PreferOrdered) { + let cmp_seq = v1.1.as_ref().unwrap().with(rti, |rti, e1| { + v2.1.as_ref() + .unwrap() + .with(rti, |_rti, e2| { + let e1_can_do_ordered = e1.signed_node_info(RoutingDomain::PublicInternet).map(|sni| sni.has_sequencing_matched_dial_info(sequencing)).unwrap_or(false); + let e2_can_do_ordered = e2.signed_node_info(RoutingDomain::PublicInternet).map(|sni| sni.has_sequencing_matched_dial_info(sequencing)).unwrap_or(false); + e2_can_do_ordered.cmp(&e1_can_do_ordered) + }) + }); + if !matches!(cmp_seq, Ordering::Equal) { + return cmp_seq; + } + } + // always prioritize reliable nodes, but sort by oldest or fastest let cmpout = v1.1.as_ref().unwrap().with(rti, |rti, e1| { v2.1.as_ref() @@ -600,9 +841,25 @@ impl RouteSpecStore { return None; } + // Ensure the route doesn't contain both a node and its relay + let mut seen_nodes: HashSet = HashSet::new(); + for n in permutation { + let node = nodes.get(*n).unwrap(); + if !seen_nodes.insert(node.node_id.key) { + // Already seen this node, should not be in the route twice + return None; + } + if let Some(relay_id) = node.signed_node_info.relay_id() { + if !seen_nodes.insert(relay_id.key) { + // Already seen this node, should not be in the route twice + return None; + } + } + } + // Ensure this route is viable by checking that each node can contact the next one + let mut can_do_sequenced = true; if directions.contains(Direction::Outbound) { - let our_peer_info = rti.get_own_peer_info(RoutingDomain::PublicInternet); let mut previous_node = &our_peer_info; let mut reachable = true; for n in permutation { @@ -618,6 +875,21 @@ impl RouteSpecStore { reachable = false; break; } + + // Check if we can do sequenced specifically + if can_do_sequenced { + let cm = rti.get_contact_method( + RoutingDomain::PublicInternet, + previous_node, + current_node, + DialInfoFilter::all(), + Sequencing::EnsureOrdered, + ); + if matches!(cm, ContactMethod::Unreachable) { + can_do_sequenced = false; + } + } + previous_node = current_node; } if !reachable { @@ -625,7 +897,6 @@ impl RouteSpecStore { } } if directions.contains(Direction::Inbound) { - let our_peer_info = rti.get_own_peer_info(RoutingDomain::PublicInternet); let mut next_node = &our_peer_info; let mut reachable = true; for n in permutation.iter().rev() { @@ -641,6 +912,20 @@ impl RouteSpecStore { reachable = false; break; } + + // Check if we can do sequenced specifically + if can_do_sequenced { + let cm = rti.get_contact_method( + RoutingDomain::PublicInternet, + next_node, + current_node, + DialInfoFilter::all(), + Sequencing::EnsureOrdered, + ); + if matches!(cm, ContactMethod::Unreachable) { + can_do_sequenced = false; + } + } next_node = current_node; } if !reachable { @@ -649,17 +934,19 @@ impl RouteSpecStore { } // Keep this route let route_nodes = permutation.to_vec(); - Some((route_nodes, cache_key)) + Some((route_nodes, cache_key, can_do_sequenced)) }) as PermFunc; let mut route_nodes: Vec = Vec::new(); let mut cache_key: Vec = Vec::new(); + let mut can_do_sequenced: bool = true; for start in 0..(nodes.len() - hop_count) { // Try the permutations available starting with 'start' - if let Some((rn, ck)) = with_route_permutations(hop_count, start, &perm_func) { + if let Some((rn, ck, cds)) = with_route_permutations(hop_count, start, &perm_func) { route_nodes = rn; cache_key = ck; + can_do_sequenced = cds; break; } } @@ -680,22 +967,17 @@ impl RouteSpecStore { let (public_key, secret_key) = generate_secret(); + + let rsd = RouteSpecDetail { secret_key, hops, hop_node_refs, - transfer_stats_down_up: Default::default(), - latency_stats: Default::default(), - latency_stats_accounting: Default::default(), - transfer_stats_accounting: Default::default(), published: false, - reachable: false, - created_ts: cur_ts, - last_checked_ts: None, - last_used_ts: None, directions, stability, - sequencing, + can_do_sequenced, + stats: RouteStats::new(cur_ts), }; drop(perm_func); @@ -709,21 +991,29 @@ impl RouteSpecStore { Ok(Some(public_key)) } - pub fn validate_signatures( + #[instrument(level = "trace", skip(self, data, callback), ret)] + pub fn with_signature_validated_route( &self, public_key: &DHTKey, signatures: &[DHTSignature], data: &[u8], last_hop_id: DHTKey, - ) -> EyreResult> { + callback: F, + ) -> Option + where F: FnOnce(&RouteSpecDetail) -> R, + R: fmt::Debug, + { let inner = &*self.inner.lock(); - let rsd = Self::detail(inner, &public_key).ok_or_else(|| eyre!("route does not exist"))?; + let Some(rsd) = Self::detail(inner, &public_key) else { + log_rpc!(debug "route does not exist: {:?}", public_key); + return None; + }; // Ensure we have the right number of signatures if signatures.len() != rsd.hops.len() - 1 { // Wrong number of signatures log_rpc!(debug "wrong number of signatures ({} should be {}) for routed operation on private route {}", signatures.len(), rsd.hops.len() - 1, public_key); - return Ok(None); + return None; } // Validate signatures to ensure the route was handled by the nodes and not messed with // This is in private route (reverse) order as we are receiving over the route @@ -733,53 +1023,53 @@ impl RouteSpecStore { // Verify the node we received the routed operation from is the last hop in our route if *hop_public_key != last_hop_id { log_rpc!(debug "received routed operation from the wrong hop ({} should be {}) on private route {}", hop_public_key.encode(), last_hop_id.encode(), public_key); - return Ok(None); + return None; } } else { // Verify a signature for a hop node along the route if let Err(e) = verify(hop_public_key, data, &signatures[hop_n]) { log_rpc!(debug "failed to verify signature for hop {} at {} on private route {}: {}", hop_n, hop_public_key, public_key, e); - return Ok(None); + return None; } } } - // We got the correct signatures, return a key and response safety spec - Ok(Some(( - rsd.secret_key, - SafetySpec { - preferred_route: Some(*public_key), - hop_count: rsd.hops.len(), - stability: rsd.stability, - sequencing: rsd.sequencing, - }, - ))) + // We got the correct signatures, return a key and response safety spec + Some(callback(rsd)) } - /// Test an allocated route for continuity - pub async fn test_route(&self, key: &DHTKey) -> EyreResult { - let inner = &mut *self.inner.lock(); - let rsd = Self::detail(inner, &key).ok_or_else(|| eyre!("route does not exist"))?; + #[instrument(level = "trace", skip(self), ret, err)] + async fn test_allocated_route(&self, key: &DHTKey) -> EyreResult { + // Make loopback route to test with + let dest = { + let private_route = self.assemble_private_route(key, None)?; + + let inner = &mut *self.inner.lock(); + let rsd = Self::detail(inner, &key).ok_or_else(|| eyre!("route does not exist"))?; + + // Match the private route's hop length for safety route length + let hop_count = rsd.hops.len(); + // Always test routes with safety routes that are more likely to succeed + let stability = Stability::Reliable; + // Routes can test with whatever sequencing they were allocated with + let sequencing = Sequencing::NoPreference; + + let safety_spec = SafetySpec { + preferred_route: Some(key.clone()), + hop_count, + stability, + sequencing, + }; + let safety_selection = SafetySelection::Safe(safety_spec); + + Destination::PrivateRoute { + private_route, + safety_selection, + } + }; + + // Test with double-round trip ping to self let rpc_processor = self.unlocked_inner.routing_table.rpc_processor(); - - // Target is the last hop - let target = rsd.hop_node_refs.last().unwrap().clone(); - let hop_count = rsd.hops.len(); - let stability = rsd.stability; - let sequencing = rsd.sequencing; - - // Test with ping to end - let res = match rpc_processor - .rpc_call_status(Destination::Direct { - target, - safety_selection: SafetySelection::Safe(SafetySpec { - preferred_route: Some(key.clone()), - hop_count, - stability, - sequencing, - }), - }) - .await? - { + let _res = match rpc_processor.rpc_call_status(dest).await? { NetworkResult::Value(v) => v, _ => { // Did not error, but did not come back, just return false @@ -790,13 +1080,71 @@ impl RouteSpecStore { Ok(true) } - /// Release an allocated route that is no longer in use - pub fn release_route(&self, public_key: DHTKey) -> EyreResult<()> { - let mut inner = self.inner.lock(); - let Some(detail) = inner.content.details.remove(&public_key) else { - bail!("can't release route that was never allocated"); + #[instrument(level = "trace", skip(self), ret, err)] + async fn test_remote_route(&self, key: &DHTKey) -> EyreResult { + // Make private route test + let dest = { + // Get the route to test + let private_route = match self.peek_remote_private_route(key) { + Some(pr) => pr, + None => return Ok(false), + }; + + // Get a safety route that is good enough + let safety_spec = SafetySpec { + preferred_route: None, + hop_count: self.unlocked_inner.default_route_hop_count, + stability: Stability::default(), + sequencing: Sequencing::default(), + }; + + let safety_selection = SafetySelection::Safe(safety_spec); + + Destination::PrivateRoute { + private_route, + safety_selection, + } }; + // Test with double-round trip ping to self + let rpc_processor = self.unlocked_inner.routing_table.rpc_processor(); + let _res = match rpc_processor.rpc_call_status(dest).await? { + NetworkResult::Value(v) => v, + _ => { + // Did not error, but did not come back, just return false + return Ok(false); + } + }; + + Ok(true) + } + + /// Test an allocated route for continuity + #[instrument(level = "trace", skip(self), ret, err)] + pub async fn test_route(&self, key: &DHTKey) -> EyreResult { + let is_remote = { + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + Self::with_peek_remote_private_route(inner, cur_ts, key, |_| {}).is_some() + }; + if is_remote { + self.test_remote_route(key).await + } else { + self.test_allocated_route(key).await + } + } + + /// Release an allocated route that is no longer in use + #[instrument(level = "trace", skip(self), ret)] + fn release_allocated_route(&self, public_key: &DHTKey) -> bool { + let mut inner = self.inner.lock(); + let Some(detail) = inner.content.details.remove(public_key) else { + return false; + }; + + // Mark it as dead for the update + inner.cache.dead_routes.push(*public_key); + // Remove from hop cache let cache_key = route_hops_to_hop_cache(&detail.hops); if !inner.cache.hop_cache.remove(&cache_key) { @@ -832,11 +1180,34 @@ impl RouteSpecStore { panic!("used_end_nodes cache should have contained hop"); } } - Ok(()) + true + } + + /// Release an allocated or remote route that is no longer in use + #[instrument(level = "trace", skip(self), ret)] + pub fn release_route(&self, key: &DHTKey) -> bool { + + let is_remote = { + let inner = &mut *self.inner.lock(); + + // Release from compiled route cache if it's used there + self.invalidate_compiled_route_cache(inner, key); + + // Check to see if this is a remote route + let cur_ts = get_aligned_timestamp(); + Self::with_peek_remote_private_route(inner, cur_ts, key, |_| {}).is_some() + }; + + if is_remote { + self.release_remote_private_route(key) + } else { + self.release_allocated_route(key) + } } /// Find first matching unpublished route that fits into the selection criteria - fn first_unpublished_route_inner<'a>( + /// Don't pick any routes that have failed and haven't been tested yet + fn first_available_route_inner<'a>( inner: &'a RouteSpecStoreInner, min_hop_count: usize, max_hop_count: usize, @@ -845,12 +1216,18 @@ impl RouteSpecStore { directions: DirectionSet, avoid_node_ids: &[DHTKey], ) -> Option { + let cur_ts = get_aligned_timestamp(); + + let mut routes = Vec::new(); + + // Get all valid routes, allow routes that need testing + // but definitely prefer routes that have been recently tested for detail in &inner.content.details { if detail.1.stability >= stability - && detail.1.sequencing >= sequencing + && detail.1.is_sequencing_match(sequencing) && detail.1.hops.len() >= min_hop_count && detail.1.hops.len() <= max_hop_count - && detail.1.directions.is_subset(directions) + && detail.1.directions.is_superset(directions) && !detail.1.published { let mut avoid = false; @@ -861,31 +1238,112 @@ impl RouteSpecStore { } } if !avoid { - return Some(*detail.0); + routes.push(detail); } } } - None + + // Sort the routes by preference + routes.sort_by(|a, b| { + let a_needs_testing = a.1.stats.needs_testing(cur_ts); + let b_needs_testing = b.1.stats.needs_testing(cur_ts); + if !a_needs_testing && b_needs_testing { + return cmp::Ordering::Less; + } + if !b_needs_testing && a_needs_testing { + return cmp::Ordering::Greater; + } + let a_latency = a.1.stats.latency_stats().average; + let b_latency = b.1.stats.latency_stats().average; + + a_latency.cmp(&b_latency) + }); + + // Return the best one if we got one + routes.first().map(|r| *r.0) } - /// List all routes - pub fn list_routes(&self) -> Vec { + /// List all allocated routes + pub fn list_allocated_routes(&self, mut filter: F) -> Vec + where + F: FnMut(&DHTKey, &RouteSpecDetail) -> Option, + { let inner = self.inner.lock(); let mut out = Vec::with_capacity(inner.content.details.len()); for detail in &inner.content.details { - out.push(*detail.0); + if let Some(x) = filter(detail.0, detail.1) { + out.push(x); + } + } + out + } + + /// List all allocated routes + pub fn list_remote_routes(&self, mut filter: F) -> Vec + where + F: FnMut(&DHTKey, &RemotePrivateRouteInfo) -> Option, + { + let inner = self.inner.lock(); + let mut out = Vec::with_capacity(inner.cache.remote_private_route_cache.len()); + for info in &inner.cache.remote_private_route_cache { + if let Some(x) = filter(info.0, info.1) { + out.push(x); + } } out } /// Get the debug description of a route pub fn debug_route(&self, key: &DHTKey) -> Option { - let inner = &*self.inner.lock(); + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + // If this is a remote route, print it + if let Some(s) = + Self::with_peek_remote_private_route(inner, cur_ts, key, |rpi| format!("{:#?}", rpi)) + { + return Some(s); + } + // Otherwise check allocated routes Self::detail(inner, key).map(|rsd| format!("{:#?}", rsd)) } ////////////////////////////////////////////////////////////////////// + // Route cache + fn add_to_compiled_route_cache(&self, inner: &mut RouteSpecStoreInner, pr_pubkey: DHTKey, safety_route: SafetyRoute) + { + let key = CompiledRouteCacheKey { + sr_pubkey: safety_route.public_key, + pr_pubkey, + }; + + if let Some(v) = inner.cache.compiled_route_cache.insert(key, safety_route) { + log_rtab!(error "route cache already contained key: sr_pubkey={:?}, pr_pubkey={:?}", v.public_key, pr_pubkey); + } + } + + fn lookup_compiled_route_cache(&self, inner: &mut RouteSpecStoreInner, sr_pubkey: DHTKey, pr_pubkey: DHTKey) -> Option { + + let key = CompiledRouteCacheKey { + sr_pubkey, + pr_pubkey, + }; + + inner.cache.compiled_route_cache.get(&key).cloned() + } + + fn invalidate_compiled_route_cache(&self, inner: &mut RouteSpecStoreInner, dead_key: &DHTKey) { + let mut dead_entries = Vec::new(); + for (k, _v) in inner.cache.compiled_route_cache.iter() { + if k.sr_pubkey == *dead_key || k.pr_pubkey == *dead_key { + dead_entries.push(k.clone()); + } + } + for d in dead_entries { + inner.cache.compiled_route_cache.remove(&d); + } + } + /// Compiles a safety route to the private route, with caching /// Returns an Err() if the parameters are wrong /// Returns Ok(None) if no allocation could happen at this time (not an error) @@ -894,13 +1352,17 @@ impl RouteSpecStore { safety_selection: SafetySelection, mut private_route: PrivateRoute, ) -> EyreResult> { + // let profile_start_ts = get_timestamp(); + let inner = &mut *self.inner.lock(); let routing_table = self.unlocked_inner.routing_table.clone(); let rti = &mut *routing_table.inner.write(); + let pr_pubkey = private_route.public_key; let pr_hopcount = private_route.hop_count as usize; let max_route_hop_count = self.unlocked_inner.max_route_hop_count; - if pr_hopcount > max_route_hop_count { + // Check private route hop count isn't larger than the max route hop count plus one for the 'first hop' header + if pr_hopcount > (max_route_hop_count + 1) { bail!("private route hop count too long"); } // See if we are using a safety route, if not, short circuit this operation @@ -934,6 +1396,7 @@ impl RouteSpecStore { first_hop.set_sequencing(sequencing); // Return the compiled safety route + //println!("compile_safety_route profile (stub): {} us", (get_timestamp() - profile_start_ts)); return Ok(Some(CompiledRoute { safety_route: SafetyRoute::new_stub(routing_table.node_id(), private_route), secret: routing_table.node_id_secret(), @@ -942,29 +1405,60 @@ impl RouteSpecStore { } }; - let PrivateRouteHops::FirstHop(pr_first_hop) = &private_route.hops else { - bail!("compiled private route should have first hop"); + // If the safety route requested is also the private route, this is a loopback test, just accept it + let sr_pubkey = if safety_spec.preferred_route == Some(private_route.public_key) { + // Private route is also safety route during loopback test + private_route.public_key + } else { + let Some(avoid_node_id) = private_route.first_hop_node_id() else { + bail!("compiled private route should have first hop"); + }; + let Some(sr_pubkey) = self.get_route_for_safety_spec_inner(inner, rti, &safety_spec, Direction::Outbound.into(), &[avoid_node_id])? else { + // No safety route could be found for this spec + return Ok(None); + }; + sr_pubkey + }; + + // Look up a few things from the safety route detail we want for the compiled route and don't borrow inner + let (optimize, first_hop, secret) = { + let safety_rsd = Self::detail(inner, &sr_pubkey).ok_or_else(|| eyre!("route missing"))?; + + // We can optimize the peer info in this safety route if it has been successfully + // communicated over either via an outbound test, or used as a private route inbound + // and we are replying over the same route as our safety route outbound + let optimize = safety_rsd.stats.last_tested_ts.is_some() || safety_rsd.stats.last_received_ts.is_some(); + + // Get the first hop noderef of the safety route + let mut first_hop = safety_rsd.hop_node_refs.first().unwrap().clone(); + // Ensure sequencing requirement is set on first hop + first_hop.set_sequencing(safety_spec.sequencing); + + // Get the safety route secret key + let secret = safety_rsd.secret_key; + + (optimize, first_hop, secret) }; - // Get the safety route to use from the spec - let avoid_node_id = match &pr_first_hop.node { - RouteNode::NodeId(n) => n.key, - RouteNode::PeerInfo(p) => p.node_id.key, - }; - let Some(sr_pubkey) = self.get_route_for_safety_spec_inner(inner, rti, &safety_spec, Direction::Outbound.into(), &[avoid_node_id])? else { - // No safety route could be found for this spec - return Ok(None); - }; - let safety_rsd = Self::detail_mut(inner, &sr_pubkey).unwrap(); - - // See if we can optimize this compilation yet - // We don't want to include full nodeinfo if we don't have to - let optimize = safety_rsd.reachable; - - // xxx implement caching here! + // See if we have a cached route we can use + if optimize { + if let Some(safety_route) = self.lookup_compiled_route_cache(inner, sr_pubkey, pr_pubkey) { + // Build compiled route + let compiled_route = CompiledRoute { + safety_route, + secret, + first_hop, + }; + // Return compiled route + //println!("compile_safety_route profile (cached): {} us", (get_timestamp() - profile_start_ts)); + return Ok(Some(compiled_route)); + } + } // Create hops let hops = { + let safety_rsd = Self::detail(inner, &sr_pubkey).ok_or_else(|| eyre!("route missing"))?; + // start last blob-to-encrypt data off as private route let mut blob_data = { let mut pr_message = ::capnp::message::Builder::new_default(); @@ -1062,25 +1556,25 @@ impl RouteSpecStore { hops, }; - let mut first_hop = safety_rsd.hop_node_refs.first().unwrap().clone(); - - // Ensure sequencing requirement is set on first hop - first_hop.set_sequencing(safety_spec.sequencing); + // Add to cache but only if we have an optimized route + if optimize { + self.add_to_compiled_route_cache(inner, pr_pubkey, safety_route.clone()); + } // Build compiled route let compiled_route = CompiledRoute { safety_route, - secret: safety_rsd.secret_key, + secret, first_hop, }; - // xxx: add cache here - // Return compiled route + //println!("compile_safety_route profile (uncached): {} us", (get_timestamp() - profile_start_ts)); Ok(Some(compiled_route)) } /// Get a route that matches a particular safety spec + #[instrument(level = "trace", skip(self, inner, rti), ret, err)] fn get_route_for_safety_spec_inner( &self, inner: &mut RouteSpecStoreInner, @@ -1100,13 +1594,16 @@ impl RouteSpecStore { // See if the preferred route is here if let Some(preferred_route) = safety_spec.preferred_route { - if inner.content.details.contains_key(&preferred_route) { - return Ok(Some(preferred_route)); + if let Some(preferred_rsd) = inner.content.details.get(&preferred_route) { + // Only use the preferred route if it doesn't end with the avoid nodes + if !avoid_node_ids.contains(preferred_rsd.hops.last().unwrap()) { + return Ok(Some(preferred_route)); + } } } // Select a safety route from the pool or make one if we don't have one that matches - let sr_pubkey = if let Some(sr_pubkey) = Self::first_unpublished_route_inner( + let sr_pubkey = if let Some(sr_pubkey) = Self::first_available_route_inner( inner, safety_spec.hop_count, safety_spec.hop_count, @@ -1140,6 +1637,7 @@ impl RouteSpecStore { } /// Get a private sroute to use for the answer to question + #[instrument(level = "trace", skip(self), ret, err)] pub fn get_private_route_for_safety_spec( &self, safety_spec: &SafetySpec, @@ -1159,10 +1657,11 @@ impl RouteSpecStore { } /// Assemble private route for publication + #[instrument(level = "trace", skip(self), err)] pub fn assemble_private_route( &self, key: &DHTKey, - optimize: Option, + optimized: Option, ) -> EyreResult { let inner = &*self.inner.lock(); let routing_table = self.unlocked_inner.routing_table.clone(); @@ -1172,14 +1671,21 @@ impl RouteSpecStore { // See if we can optimize this compilation yet // We don't want to include full nodeinfo if we don't have to - let optimize = optimize.unwrap_or(rsd.reachable); + let optimized = optimized + .unwrap_or(rsd.stats.last_tested_ts.is_some() || rsd.stats.last_received_ts.is_some()); // Make innermost route hop to our own node let mut route_hop = RouteHop { - node: if optimize { + node: if optimized { + if !rti.has_valid_own_node_info(RoutingDomain::PublicInternet) { + bail!("can't make private routes until our node info is valid"); + } RouteNode::NodeId(NodeId::new(routing_table.node_id())) } else { - RouteNode::PeerInfo(rti.get_own_peer_info(RoutingDomain::PublicInternet)) + let Some(pi) = rti.get_own_peer_info(RoutingDomain::PublicInternet) else { + bail!("can't make private routes until our node info is valid"); + }; + RouteNode::PeerInfo(pi) }, next_hop: None, }; @@ -1210,7 +1716,7 @@ impl RouteSpecStore { }; route_hop = RouteHop { - node: if optimize { + node: if optimized { // Optimized, no peer info, just the dht key RouteNode::NodeId(NodeId::new(rsd.hops[h])) } else { @@ -1242,36 +1748,65 @@ impl RouteSpecStore { } /// Import a remote private route for compilation + #[instrument(level = "trace", skip(self, blob), ret, err)] pub fn import_remote_private_route(&self, blob: Vec) -> EyreResult { // decode the pr blob let private_route = RouteSpecStore::blob_to_private_route(blob)?; - // store the private route in our cache - let inner = &mut *self.inner.lock(); - let cur_ts = intf::get_timestamp(); + // ensure private route has first hop + if !matches!(private_route.hops, PrivateRouteHops::FirstHop(_)) { + bail!("private route must have first hop"); + } + // ensure this isn't also an allocated route + let inner = &mut *self.inner.lock(); + if Self::detail(inner, &private_route.public_key).is_some() { + bail!("should not import allocated route"); + } + + // store the private route in our cache + let cur_ts = get_aligned_timestamp(); let key = Self::with_create_remote_private_route(inner, cur_ts, private_route, |r| { r.private_route.as_ref().unwrap().public_key.clone() }); Ok(key) } - /// Retrieve an imported remote private route by its public key - pub fn get_remote_private_route(&self, key: &DHTKey) -> EyreResult { + /// Release a remote private route that is no longer in use + #[instrument(level = "trace", skip(self), ret)] + fn release_remote_private_route(&self, key: &DHTKey) -> bool { let inner = &mut *self.inner.lock(); - let cur_ts = intf::get_timestamp(); - let Some(pr) = Self::with_get_remote_private_route(inner, cur_ts, key, |r| { + if inner.cache.remote_private_route_cache.remove(key).is_some() { + // Mark it as dead for the update + inner.cache.dead_remote_routes.push(*key); + true + } else { + false + } + } + + /// Retrieve an imported remote private route by its public key + pub fn get_remote_private_route(&self, key: &DHTKey) -> Option { + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + Self::with_get_remote_private_route(inner, cur_ts, key, |r| { r.private_route.as_ref().unwrap().clone() - }) else { - bail!("remote private route not found"); - }; - Ok(pr) + }) + } + + /// Retrieve an imported remote private route by its public key but don't 'touch' it + pub fn peek_remote_private_route(&self, key: &DHTKey) -> Option { + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + Self::with_peek_remote_private_route(inner, cur_ts, key, |r| { + r.private_route.as_ref().unwrap().clone() + }) } // get or create a remote private route cache entry fn with_create_remote_private_route( inner: &mut RouteSpecStoreInner, - cur_ts: u64, + cur_ts: Timestamp, private_route: PrivateRoute, f: F, ) -> R @@ -1285,30 +1820,42 @@ impl RouteSpecStore { .remote_private_route_cache .entry(pr_pubkey) .and_modify(|rpr| { - if cur_ts - rpr.last_used_ts >= REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY { + if cur_ts.saturating_sub(rpr.last_touched_ts) >= REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY { // Start fresh if this had expired - rpr.last_used_ts = cur_ts; - rpr.last_replied_ts = None; - rpr.seen_our_node_info = false; + rpr.last_seen_our_node_info_ts = Timestamp::new(0); + rpr.last_touched_ts = cur_ts; + rpr.stats = RouteStats::new(cur_ts); } else { // If not expired, just mark as being used - rpr.last_used_ts = cur_ts; + rpr.last_touched_ts = cur_ts; } }) .or_insert_with(|| RemotePrivateRouteInfo { // New remote private route cache entry private_route: Some(private_route), - last_used_ts: cur_ts, - last_replied_ts: None, - seen_our_node_info: false, + last_seen_our_node_info_ts: Timestamp::new(0), + last_touched_ts: cur_ts, + stats: RouteStats::new(cur_ts), }); - f(rpr) + + let out = f(rpr); + + // Ensure we LRU out items + if inner.cache.remote_private_route_cache.len() + > inner.cache.remote_private_route_cache.capacity() + { + let (dead_k, _) = inner.cache.remote_private_route_cache.remove_lru().unwrap(); + // Mark it as dead for the update + inner.cache.dead_remote_routes.push(dead_k); + } + + out } // get a remote private route cache entry fn with_get_remote_private_route( inner: &mut RouteSpecStoreInner, - cur_ts: u64, + cur_ts: Timestamp, key: &DHTKey, f: F, ) -> Option @@ -1316,32 +1863,94 @@ impl RouteSpecStore { F: FnOnce(&mut RemotePrivateRouteInfo) -> R, { let rpr = inner.cache.remote_private_route_cache.get_mut(key)?; - if cur_ts - rpr.last_used_ts < REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY { + if cur_ts.saturating_sub(rpr.last_touched_ts) < REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY { + rpr.last_touched_ts = cur_ts; return Some(f(rpr)); } inner.cache.remote_private_route_cache.remove(key); + inner.cache.dead_remote_routes.push(*key); None } - /// Check to see if this remote (not ours) private route has seen our node info yet - /// This returns true if we have sent non-safety-route node info to the - /// private route and gotten a response before - pub fn has_remote_private_route_seen_our_node_info(&self, key: &DHTKey) -> bool { - let inner = &mut *self.inner.lock(); - let cur_ts = intf::get_timestamp(); - Self::with_get_remote_private_route(inner, cur_ts, key, |rpr| rpr.seen_our_node_info) - .unwrap_or_default() + // peek a remote private route cache entry + fn with_peek_remote_private_route( + inner: &mut RouteSpecStoreInner, + cur_ts: Timestamp, + key: &DHTKey, + f: F, + ) -> Option + where + F: FnOnce(&mut RemotePrivateRouteInfo) -> R, + { + match inner.cache.remote_private_route_cache.entry(*key) { + hashlink::lru_cache::Entry::Occupied(mut o) => { + let rpr = o.get_mut(); + if cur_ts.saturating_sub(rpr.last_touched_ts) < REMOTE_PRIVATE_ROUTE_CACHE_EXPIRY { + return Some(f(rpr)); + } + o.remove(); + inner.cache.dead_remote_routes.push(*key); + None + } + hashlink::lru_cache::Entry::Vacant(_) => None, + } } - /// Mark a remote private route as having seen our node info + /// Check to see if this remote (not ours) private route has seen our current node info yet + /// This happens when you communicate with a private route without a safety route + pub fn has_remote_private_route_seen_our_node_info(&self, key: &DHTKey) -> bool { + let our_node_info_ts = { + let rti = &*self.unlocked_inner.routing_table.inner.read(); + let Some(ts) = rti.get_own_node_info_ts(RoutingDomain::PublicInternet) else { + return false; + }; + ts + }; + + let opt_rpr_node_info_ts = { + let inner = &mut *self.inner.lock(); + let cur_ts = get_aligned_timestamp(); + Self::with_peek_remote_private_route(inner, cur_ts, key, |rpr| { + rpr.last_seen_our_node_info_ts + }) + }; + + let Some(rpr_node_info_ts) = opt_rpr_node_info_ts else { + return false; + }; + + our_node_info_ts == rpr_node_info_ts + } + + /// Mark a remote private route as having seen our current node info + /// PRIVACY: + /// We do not accept node info timestamps from remote private routes because this would + /// enable a deanonymization attack, whereby a node could be 'pinged' with a doctored node_info with a + /// special 'timestamp', which then may be sent back over a private route, identifying that it + /// was that node that had the private route. pub fn mark_remote_private_route_seen_our_node_info( &self, key: &DHTKey, - cur_ts: u64, + cur_ts: Timestamp, ) -> EyreResult<()> { + let our_node_info_ts = { + let rti = &*self.unlocked_inner.routing_table.inner.read(); + let Some(ts) = rti.get_own_node_info_ts(RoutingDomain::PublicInternet) else { + // Node info is invalid, skipping this + return Ok(()); + }; + ts + }; + let inner = &mut *self.inner.lock(); + // Check for local route. If this is not a remote private route + // then we just skip the recording. We may be running a test and using + // our own local route as the destination private route. + if let Some(_) = Self::detail_mut(inner, key) { + return Ok(()); + } if Self::with_get_remote_private_route(inner, cur_ts, key, |rpr| { - rpr.seen_our_node_info = true; + rpr.last_seen_our_node_info_ts = our_node_info_ts; }) .is_none() { @@ -1350,33 +1959,33 @@ impl RouteSpecStore { Ok(()) } - /// Mark a remote private route as having replied to a question { - pub fn mark_remote_private_route_replied(&self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { + /// Get the route statistics for any route we know about, local or remote + pub fn with_route_stats(&self, cur_ts: Timestamp, key: &DHTKey, f: F) -> Option + where + F: FnOnce(&mut RouteStats) -> R, + { let inner = &mut *self.inner.lock(); - if Self::with_get_remote_private_route(inner, cur_ts, key, |rpr| { - rpr.last_replied_ts = Some(cur_ts); - }) - .is_none() - { - bail!("private route is missing from store: {}", key); - } - Ok(()) - } - /// Mark a remote private route as having beed used { - pub fn mark_remote_private_route_used(&self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - if Self::with_get_remote_private_route(inner, cur_ts, key, |rpr| { - rpr.last_used_ts = cur_ts; - }) - .is_none() - { - bail!("private route is missing from store: {}", key); + // Check for stub route + if *key == self.unlocked_inner.routing_table.node_id() { + return None; } - Ok(()) + // Check for local route + if let Some(rsd) = Self::detail_mut(inner, key) { + return Some(f(&mut rsd.stats)); + } + // Check for remote route + if let Some(res) = + Self::with_peek_remote_private_route(inner, cur_ts, key, |rpr| f(&mut rpr.stats)) + { + return Some(res); + } + + None } /// Clear caches when local our local node info changes + #[instrument(level = "trace", skip(self))] pub fn reset(&self) { let inner = &mut *self.inner.lock(); @@ -1384,16 +1993,14 @@ impl RouteSpecStore { for (_k, v) in &mut inner.content.details { // Must republish route now v.published = false; - // Route is not known reachable now - v.reachable = false; - // We have yet to check it since local node info changed - v.last_checked_ts = None; + // Restart stats for routes so we test the route again + v.stats.reset(); } // Reset private route cache for (_k, v) in &mut inner.cache.remote_private_route_cache { - v.last_replied_ts = None; - v.seen_our_node_info = false; + // Restart stats for routes so we test the route again + v.stats.reset(); } } @@ -1408,78 +2015,17 @@ impl RouteSpecStore { Ok(()) } - /// Mark route as reachable - /// When first deserialized, routes must be re-tested for reachability - /// This can be used to determine if routes need to be sent with full peerinfo or can just use a node id - pub fn mark_route_reachable(&self, key: &DHTKey, reachable: bool) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - Self::detail_mut(inner, key) - .ok_or_else(|| eyre!("route does not exist"))? - .reachable = reachable; - Ok(()) - } - - /// Mark route as checked - pub fn touch_route_checked(&self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - Self::detail_mut(inner, key) - .ok_or_else(|| eyre!("route does not exist"))? - .last_checked_ts = Some(cur_ts); - Ok(()) - } - - /// Mark route as used - pub fn touch_route_used(&self, key: &DHTKey, cur_ts: u64) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - Self::detail_mut(inner, key) - .ok_or_else(|| eyre!("route does not exist"))? - .last_used_ts = Some(cur_ts); - Ok(()) - } - - /// Record latency on the route - pub fn record_latency(&self, key: &DHTKey, latency: u64) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - - let rsd = Self::detail_mut(inner, key).ok_or_else(|| eyre!("route does not exist"))?; - rsd.latency_stats = rsd.latency_stats_accounting.record_latency(latency); - Ok(()) - } - - /// Get the calculated latency stats - pub fn latency_stats(&self, key: &DHTKey) -> EyreResult { - let inner = &mut *self.inner.lock(); - Ok(Self::detail_mut(inner, key) - .ok_or_else(|| eyre!("route does not exist"))? - .latency_stats - .clone()) - } - - /// Add download transfers to route - pub fn add_down(&self, key: &DHTKey, bytes: u64) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - let rsd = Self::detail_mut(inner, key).ok_or_else(|| eyre!("route does not exist"))?; - rsd.transfer_stats_accounting.add_down(bytes); - Ok(()) - } - - /// Add upload transfers to route - pub fn add_up(&self, key: &DHTKey, bytes: u64) -> EyreResult<()> { - let inner = &mut *self.inner.lock(); - let rsd = Self::detail_mut(inner, key).ok_or_else(|| eyre!("route does not exist"))?; - rsd.transfer_stats_accounting.add_up(bytes); - Ok(()) - } - /// Process transfer statistics to get averages - pub fn roll_transfers(&self, last_ts: u64, cur_ts: u64) { + pub fn roll_transfers(&self, last_ts: Timestamp, cur_ts: Timestamp) { let inner = &mut *self.inner.lock(); + + // Roll transfers for locally allocated routes for rsd in inner.content.details.values_mut() { - rsd.transfer_stats_accounting.roll_transfers( - last_ts, - cur_ts, - &mut rsd.transfer_stats_down_up, - ); + rsd.stats.roll_transfers(last_ts, cur_ts); + } + // Roll transfers for remote private routes + for (_k, v) in inner.cache.remote_private_route_cache.iter_mut() { + v.stats.roll_transfers(last_ts, cur_ts); } } diff --git a/veilid-core/src/routing_table/routing_domain_editor.rs b/veilid-core/src/routing_table/routing_domain_editor.rs index 08aba320..28785c12 100644 --- a/veilid-core/src/routing_table/routing_domain_editor.rs +++ b/veilid-core/src/routing_table/routing_domain_editor.rs @@ -23,7 +23,6 @@ pub struct RoutingDomainEditor { routing_table: RoutingTable, routing_domain: RoutingDomain, changes: Vec, - send_node_info_updates: bool, } impl RoutingDomainEditor { @@ -32,13 +31,8 @@ impl RoutingDomainEditor { routing_table, routing_domain, changes: Vec::new(), - send_node_info_updates: true, } } - #[instrument(level = "debug", skip(self))] - pub fn disable_node_info_updates(&mut self) { - self.send_node_info_updates = false; - } #[instrument(level = "debug", skip(self))] pub fn clear_dial_info_details(&mut self) { @@ -199,9 +193,7 @@ impl RoutingDomainEditor { } }); if changed { - // Mark that nothing in the routing table has seen our new node info - inner.reset_all_seen_our_node_info(self.routing_domain); - // + // Allow signed node info updates at same timestamp for otherwise dead nodes if our network has changed inner.reset_all_updated_since_last_network_change(); } } @@ -212,12 +204,5 @@ impl RoutingDomainEditor { rss.reset(); } } - // Send our updated node info to all the nodes in the routing table - if changed && self.send_node_info_updates { - let network_manager = self.routing_table.unlocked_inner.network_manager.clone(); - network_manager - .send_node_info_updates(self.routing_domain, true) - .await; - } } } diff --git a/veilid-core/src/routing_table/routing_domains.rs b/veilid-core/src/routing_table/routing_domains.rs index d52365c8..21fcfefe 100644 --- a/veilid-core/src/routing_table/routing_domains.rs +++ b/veilid-core/src/routing_table/routing_domains.rs @@ -294,8 +294,10 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { // Get the target's inbound relay, it must have one or it is not reachable if let Some(node_b_relay) = peer_b.signed_node_info.relay_info() { let node_b_relay_id = peer_b.signed_node_info.relay_id().unwrap(); + // Note that relay_peer_info could be node_a, in which case a connection already exists - // and we shouldn't have even gotten here + // and we only get here if the connection had dropped, in which case node_a is unreachable until + // it gets a new relay connection up if node_b_relay_id.key == peer_a.node_id.key { return ContactMethod::Existing; } @@ -375,6 +377,13 @@ impl RoutingDomainDetail for PublicInternetRoutingDomainDetail { // If the node B has no direct dial info, it needs to have an inbound relay else if let Some(node_b_relay) = peer_b.signed_node_info.relay_info() { let node_b_relay_id = peer_b.signed_node_info.relay_id().unwrap(); + + // Note that relay_peer_info could be node_a, in which case a connection already exists + // and we only get here if the connection had dropped, in which case node_a is unreachable until + // it gets a new relay connection up + if node_b_relay_id.key == peer_a.node_id.key { + return ContactMethod::Existing; + } // Can we reach the full relay? if first_filtered_dial_info_detail( diff --git a/veilid-core/src/routing_table/routing_table_inner.rs b/veilid-core/src/routing_table/routing_table_inner.rs index f37672fe..50913789 100644 --- a/veilid-core/src/routing_table/routing_table_inner.rs +++ b/veilid-core/src/routing_table/routing_table_inner.rs @@ -226,18 +226,8 @@ impl RoutingTableInner { }) } - pub fn reset_all_seen_our_node_info(&mut self, routing_domain: RoutingDomain) { - let cur_ts = intf::get_timestamp(); - self.with_entries_mut(cur_ts, BucketEntryState::Dead, |rti, _, v| { - v.with_mut(rti, |_rti, e| { - e.set_seen_our_node_info(routing_domain, false); - }); - Option::<()>::None - }); - } - pub fn reset_all_updated_since_last_network_change(&mut self) { - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); self.with_entries_mut(cur_ts, BucketEntryState::Dead, |rti, _, v| { v.with_mut(rti, |_rti, e| { e.set_updated_since_last_network_change(false) @@ -246,16 +236,43 @@ impl RoutingTableInner { }); } + /// Return if our node info is valid yet, which is only true if we have a valid network class + pub fn has_valid_own_node_info(&self, routing_domain: RoutingDomain) -> bool { + self.with_routing_domain(routing_domain, |rdd| rdd.common().has_valid_own_node_info()) + } + /// Return a copy of our node's peerinfo - pub fn get_own_peer_info(&self, routing_domain: RoutingDomain) -> PeerInfo { + pub fn get_own_peer_info(&self, routing_domain: RoutingDomain) -> Option { + self.with_routing_domain(routing_domain, |rdd| { + if !rdd.common().has_valid_own_node_info() { + None + } else { + Some(rdd.common().with_peer_info(self, |pi| pi.clone())) + } + }) + } + + /// Return the best effort copy of our node's peerinfo + /// This may be invalid and should not be passed to other nodes, + /// but may be used for contact method calculation + pub fn get_best_effort_own_peer_info(&self, routing_domain: RoutingDomain) -> PeerInfo { self.with_routing_domain(routing_domain, |rdd| { rdd.common().with_peer_info(self, |pi| pi.clone()) }) } - /// Return our currently registered network class - pub fn has_valid_own_node_info(&self, routing_domain: RoutingDomain) -> bool { - self.with_routing_domain(routing_domain, |rdd| rdd.common().has_valid_own_node_info()) + /// Return our current node info timestamp + pub fn get_own_node_info_ts(&self, routing_domain: RoutingDomain) -> Option { + self.with_routing_domain(routing_domain, |rdd| { + if !rdd.common().has_valid_own_node_info() { + None + } else { + Some( + rdd.common() + .with_peer_info(self, |pi| pi.signed_node_info.timestamp()), + ) + } + }) } /// Return the domain's currently registered network class @@ -301,10 +318,8 @@ impl RoutingTableInner { 4 => 16, 5 => 8, 6 => 4, - 7 => 4, - 8 => 4, - 9 => 4, - _ => 4, + 7 => 2, + _ => 1, } } @@ -330,11 +345,10 @@ impl RoutingTableInner { // If the local network topology has changed, nuke the existing local node info and let new local discovery happen if changed { - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); self.with_entries_mut(cur_ts, BucketEntryState::Dead, |rti, _, e| { e.with_mut(rti, |_rti, e| { e.clear_signed_node_info(RoutingDomain::LocalNetwork); - e.set_seen_our_node_info(RoutingDomain::LocalNetwork, false); e.set_updated_since_last_network_change(false); }); Option::<()>::None @@ -410,9 +424,9 @@ impl RoutingTableInner { min_state: BucketEntryState, ) -> usize { let mut count = 0usize; - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); self.with_entries(cur_ts, min_state, |rti, _, e| { - if e.with(rti, |_rti, e| e.best_routing_domain(routing_domain_set)) + if e.with(rti, |rti, e| e.best_routing_domain(rti, routing_domain_set)) .is_some() { count += 1; @@ -424,7 +438,7 @@ impl RoutingTableInner { pub fn with_entries) -> Option>( &self, - cur_ts: u64, + cur_ts: Timestamp, min_state: BucketEntryState, mut f: F, ) -> Option { @@ -450,7 +464,7 @@ impl RoutingTableInner { F: FnMut(&mut RoutingTableInner, DHTKey, Arc) -> Option, >( &mut self, - cur_ts: u64, + cur_ts: Timestamp, min_state: BucketEntryState, mut f: F, ) -> Option { @@ -471,46 +485,37 @@ impl RoutingTableInner { None } - pub fn get_nodes_needing_updates( - &self, - outer_self: RoutingTable, - routing_domain: RoutingDomain, - cur_ts: u64, - all: bool, - ) -> Vec { - let mut node_refs = Vec::::with_capacity(self.bucket_entry_count); - self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, k, v| { - // Only update nodes that haven't seen our node info yet - if all || !v.with(rti, |_rti, e| e.has_seen_our_node_info(routing_domain)) { - node_refs.push(NodeRef::new( - outer_self.clone(), - k, - v, - Some(NodeRefFilter::new().with_routing_domain(routing_domain)), - )); - } - Option::<()>::None - }); - node_refs - } - pub fn get_nodes_needing_ping( &self, outer_self: RoutingTable, routing_domain: RoutingDomain, - cur_ts: u64, + cur_ts: Timestamp, ) -> Vec { // Collect relay nodes let opt_relay_id = self.with_routing_domain(routing_domain, |rd| { rd.common().relay_node().map(|rn| rn.node_id()) }); + let own_node_info_ts = self.get_own_node_info_ts(routing_domain); // Collect all entries that are 'needs_ping' and have some node info making them reachable somehow let mut node_refs = Vec::::with_capacity(self.bucket_entry_count); self.with_entries(cur_ts, BucketEntryState::Unreliable, |rti, k, v| { - if v.with(rti, |_rti, e| { - e.has_node_info(routing_domain.into()) - && e.needs_ping(cur_ts, opt_relay_id == Some(k)) + if v.with(rti, |rti, e| { + // If this isn't in the routing domain we are checking, don't include it + if !e.exists_in_routing_domain(rti, routing_domain) { + return false; + } + // If we need a ping via the normal timing mechanism, then do it + if e.needs_ping(cur_ts, opt_relay_id == Some(k)) { + return true; + } + // If we need a ping because this node hasn't seen our latest node info, then do it + if let Some(own_node_info_ts) = own_node_info_ts { + if !e.has_seen_our_node_info_ts(routing_domain, own_node_info_ts) { + return true; + } + } + false }) { node_refs.push(NodeRef::new( outer_self.clone(), @@ -524,7 +529,7 @@ impl RoutingTableInner { node_refs } - pub fn get_all_nodes(&self, outer_self: RoutingTable, cur_ts: u64) -> Vec { + pub fn get_all_nodes(&self, outer_self: RoutingTable, cur_ts: Timestamp) -> Vec { let mut node_refs = Vec::::with_capacity(self.bucket_entry_count); self.with_entries(cur_ts, BucketEntryState::Unreliable, |_rti, k, v| { node_refs.push(NodeRef::new(outer_self.clone(), k, v, None)); @@ -693,7 +698,7 @@ impl RoutingTableInner { outer_self: RoutingTable, node_id: DHTKey, descriptor: ConnectionDescriptor, - timestamp: u64, + timestamp: Timestamp, ) -> Option { let out = self.create_node_ref(outer_self, node_id, |_rti, e| { // this node is live because it literally just connected to us @@ -711,24 +716,45 @@ impl RoutingTableInner { // Routing Table Health Metrics pub fn get_routing_table_health(&self) -> RoutingTableHealth { - let mut health = RoutingTableHealth::default(); - let cur_ts = intf::get_timestamp(); + let mut reliable_entry_count: usize = 0; + let mut unreliable_entry_count: usize = 0; + let mut dead_entry_count: usize = 0; + + let cur_ts = get_aligned_timestamp(); for bucket in &self.buckets { for (_, v) in bucket.entries() { match v.with(self, |_rti, e| e.state(cur_ts)) { BucketEntryState::Reliable => { - health.reliable_entry_count += 1; + reliable_entry_count += 1; } BucketEntryState::Unreliable => { - health.unreliable_entry_count += 1; + unreliable_entry_count += 1; } BucketEntryState::Dead => { - health.dead_entry_count += 1; + dead_entry_count += 1; } } } } - health + + let public_internet_ready = !matches!( + self.get_network_class(RoutingDomain::PublicInternet) + .unwrap_or_default(), + NetworkClass::Invalid + ); + let local_network_ready = !matches!( + self.get_network_class(RoutingDomain::LocalNetwork) + .unwrap_or_default(), + NetworkClass::Invalid + ); + + RoutingTableHealth { + reliable_entry_count, + unreliable_entry_count, + dead_entry_count, + public_internet_ready, + local_network_ready, + } } pub fn touch_recent_peer(&mut self, node_id: DHTKey, last_connection: ConnectionDescriptor) { @@ -792,12 +818,12 @@ impl RoutingTableInner { pub fn transform_to_peer_info( &self, routing_domain: RoutingDomain, - own_peer_info: PeerInfo, + own_peer_info: &PeerInfo, k: DHTKey, v: Option>, ) -> PeerInfo { match v { - None => own_peer_info, + None => own_peer_info.clone(), Some(entry) => entry.with(self, |_rti, e| e.make_peer_info(k, routing_domain).unwrap()), } } @@ -805,7 +831,7 @@ impl RoutingTableInner { pub fn find_peers_with_sort_and_filter( &self, node_count: usize, - cur_ts: u64, + cur_ts: Timestamp, mut filters: VecDeque, mut compare: C, mut transform: T, @@ -869,7 +895,7 @@ impl RoutingTableInner { where T: for<'r> FnMut(&'r RoutingTableInner, DHTKey, Option>) -> O, { - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); // Add filter to remove dead nodes always let filter_dead = Box::new( @@ -954,7 +980,7 @@ impl RoutingTableInner { where T: for<'r> FnMut(&'r RoutingTableInner, DHTKey, Option>) -> O, { - let cur_ts = intf::get_timestamp(); + let cur_ts = get_aligned_timestamp(); let node_count = { let config = self.config(); let c = config.get(); diff --git a/veilid-core/src/routing_table/stats_accounting.rs b/veilid-core/src/routing_table/stats_accounting.rs index 7ef69bd6..5f0c8960 100644 --- a/veilid-core/src/routing_table/stats_accounting.rs +++ b/veilid-core/src/routing_table/stats_accounting.rs @@ -1,4 +1,3 @@ -use crate::xx::*; use crate::*; use alloc::collections::VecDeque; @@ -14,8 +13,8 @@ pub const ROLLING_TRANSFERS_INTERVAL_SECS: u32 = 1; #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct TransferCount { - down: u64, - up: u64, + down: ByteCount, + up: ByteCount, } #[derive(Debug, Clone, Default)] @@ -32,21 +31,21 @@ impl TransferStatsAccounting { } } - pub fn add_down(&mut self, bytes: u64) { + pub fn add_down(&mut self, bytes: ByteCount) { self.current_transfer.down += bytes; } - pub fn add_up(&mut self, bytes: u64) { + pub fn add_up(&mut self, bytes: ByteCount) { self.current_transfer.up += bytes; } pub fn roll_transfers( &mut self, - last_ts: u64, - cur_ts: u64, + last_ts: Timestamp, + cur_ts: Timestamp, transfer_stats: &mut TransferStatsDownUp, ) { - let dur_ms = (cur_ts - last_ts) / 1000u64; + let dur_ms = cur_ts.saturating_sub(last_ts) / 1000u64; while self.rolling_transfers.len() >= ROLLING_TRANSFERS_SIZE { self.rolling_transfers.pop_front(); } @@ -57,12 +56,12 @@ impl TransferStatsAccounting { self.current_transfer = TransferCount::default(); - transfer_stats.down.maximum = 0; - transfer_stats.up.maximum = 0; - transfer_stats.down.minimum = u64::MAX; - transfer_stats.up.minimum = u64::MAX; - transfer_stats.down.average = 0; - transfer_stats.up.average = 0; + transfer_stats.down.maximum = 0.into(); + transfer_stats.up.maximum = 0.into(); + transfer_stats.down.minimum = u64::MAX.into(); + transfer_stats.up.minimum = u64::MAX.into(); + transfer_stats.down.average = 0.into(); + transfer_stats.up.average = 0.into(); for xfer in &self.rolling_transfers { let bpsd = xfer.down * 1000u64 / dur_ms; let bpsu = xfer.up * 1000u64 / dur_ms; @@ -81,7 +80,7 @@ impl TransferStatsAccounting { #[derive(Debug, Clone, Default)] pub struct LatencyStatsAccounting { - rolling_latencies: VecDeque, + rolling_latencies: VecDeque, } impl LatencyStatsAccounting { @@ -91,16 +90,16 @@ impl LatencyStatsAccounting { } } - pub fn record_latency(&mut self, latency: u64) -> veilid_api::LatencyStats { + pub fn record_latency(&mut self, latency: TimestampDuration) -> veilid_api::LatencyStats { while self.rolling_latencies.len() >= ROLLING_LATENCIES_SIZE { self.rolling_latencies.pop_front(); } self.rolling_latencies.push_back(latency); let mut ls = LatencyStats { - fastest: u64::MAX, - average: 0, - slowest: 0, + fastest: u64::MAX.into(), + average: 0.into(), + slowest: 0.into(), }; for rl in &self.rolling_latencies { ls.fastest.min_assign(*rl); diff --git a/veilid-core/src/routing_table/tasks.rs b/veilid-core/src/routing_table/tasks.rs deleted file mode 100644 index f651d30e..00000000 --- a/veilid-core/src/routing_table/tasks.rs +++ /dev/null @@ -1,54 +0,0 @@ -use super::*; -use crate::xx::*; - -impl RoutingTable { - // Compute transfer statistics to determine how 'fast' a node is - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn rolling_transfers_task_routine( - self, - _stop_token: StopToken, - last_ts: u64, - cur_ts: u64, - ) -> EyreResult<()> { - // log_rtab!("--- rolling_transfers task"); - let mut inner = self.inner.write(); - let inner = &mut *inner; - - // Roll our own node's transfers - inner.self_transfer_stats_accounting.roll_transfers( - last_ts, - cur_ts, - &mut inner.self_transfer_stats, - ); - - // Roll all bucket entry transfers - let entries: Vec> = inner - .buckets - .iter() - .flat_map(|b| b.entries().map(|(_k, v)| v.clone())) - .collect(); - for v in entries { - v.with_mut(inner, |_rti, e| e.roll_transfers(last_ts, cur_ts)); - } - Ok(()) - } - - // Kick the queued buckets in the routing table to free dead nodes if necessary - // Attempts to keep the size of the routing table down to the bucket depth - #[instrument(level = "trace", skip(self), err)] - pub(super) async fn kick_buckets_task_routine( - self, - _stop_token: StopToken, - _last_ts: u64, - cur_ts: u64, - ) -> EyreResult<()> { - let kick_queue: Vec = core::mem::take(&mut *self.unlocked_inner.kick_queue.lock()) - .into_iter() - .collect(); - let mut inner = self.inner.write(); - for idx in kick_queue { - inner.kick_bucket(idx) - } - Ok(()) - } -} diff --git a/veilid-core/src/routing_table/tasks/bootstrap.rs b/veilid-core/src/routing_table/tasks/bootstrap.rs new file mode 100644 index 00000000..6b16f84a --- /dev/null +++ b/veilid-core/src/routing_table/tasks/bootstrap.rs @@ -0,0 +1,346 @@ +use super::*; + +use futures_util::stream::{FuturesUnordered, StreamExt}; +use stop_token::future::FutureExt as StopFutureExt; + +pub const BOOTSTRAP_TXT_VERSION: u8 = 0; + +#[derive(Clone, Debug)] +pub struct BootstrapRecord { + min_version: u8, + max_version: u8, + dial_info_details: Vec, +} +pub type BootstrapRecordMap = BTreeMap; + +impl RoutingTable { + // Bootstrap lookup process + #[instrument(level = "trace", skip(self), ret, err)] + pub(crate) async fn resolve_bootstrap( + &self, + bootstrap: Vec, + ) -> EyreResult { + // Resolve from bootstrap root to bootstrap hostnames + let mut bsnames = Vec::::new(); + for bh in bootstrap { + // Get TXT record for bootstrap (bootstrap.veilid.net, or similar) + let records = intf::txt_lookup(&bh).await?; + for record in records { + // Split the bootstrap name record by commas + for rec in record.split(',') { + let rec = rec.trim(); + // If the name specified is fully qualified, go with it + let bsname = if rec.ends_with('.') { + rec.to_string() + } + // If the name is not fully qualified, prepend it to the bootstrap name + else { + format!("{}.{}", rec, bh) + }; + + // Add to the list of bootstrap name to look up + bsnames.push(bsname); + } + } + } + + // Get bootstrap nodes from hostnames concurrently + let mut unord = FuturesUnordered::new(); + for bsname in bsnames { + unord.push( + async move { + // look up boostrap node txt records + let bsnirecords = match intf::txt_lookup(&bsname).await { + Err(e) => { + warn!("bootstrap node txt lookup failed for {}: {}", bsname, e); + return None; + } + Ok(v) => v, + }; + // for each record resolve into key/bootstraprecord pairs + let mut bootstrap_records: Vec<(DHTKey, BootstrapRecord)> = Vec::new(); + for bsnirecord in bsnirecords { + // Bootstrap TXT Record Format Version 0: + // txt_version,min_version,max_version,nodeid,hostname,dialinfoshort* + // + // Split bootstrap node record by commas. Example: + // 0,0,0,7lxDEabK_qgjbe38RtBa3IZLrud84P6NhGP-pRTZzdQ,bootstrap-1.dev.veilid.net,T5150,U5150,W5150/ws + let records: Vec = bsnirecord + .trim() + .split(',') + .map(|x| x.trim().to_owned()) + .collect(); + if records.len() < 6 { + warn!("invalid number of fields in bootstrap txt record"); + continue; + } + + // Bootstrap TXT record version + let txt_version: u8 = match records[0].parse::() { + Ok(v) => v, + Err(e) => { + warn!( + "invalid txt_version specified in bootstrap node txt record: {}", + e + ); + continue; + } + }; + if txt_version != BOOTSTRAP_TXT_VERSION { + warn!("unsupported bootstrap txt record version"); + continue; + } + + // Min/Max wire protocol version + let min_version: u8 = match records[1].parse::() { + Ok(v) => v, + Err(e) => { + warn!( + "invalid min_version specified in bootstrap node txt record: {}", + e + ); + continue; + } + }; + let max_version: u8 = match records[2].parse::() { + Ok(v) => v, + Err(e) => { + warn!( + "invalid max_version specified in bootstrap node txt record: {}", + e + ); + continue; + } + }; + + // Node Id + let node_id_str = &records[3]; + let node_id_key = match DHTKey::try_decode(node_id_str) { + Ok(v) => v, + Err(e) => { + warn!( + "Invalid node id in bootstrap node record {}: {}", + node_id_str, e + ); + continue; + } + }; + + // Hostname + let hostname_str = &records[4]; + + // If this is our own node id, then we skip it for bootstrap, in case we are a bootstrap node + if self.node_id() == node_id_key { + continue; + } + + // Resolve each record and store in node dial infos list + let mut bootstrap_record = BootstrapRecord { + min_version, + max_version, + dial_info_details: Vec::new(), + }; + for rec in &records[5..] { + let rec = rec.trim(); + let dial_infos = match DialInfo::try_vec_from_short(rec, hostname_str) { + Ok(dis) => dis, + Err(e) => { + warn!( + "Couldn't resolve bootstrap node dial info {}: {}", + rec, e + ); + continue; + } + }; + + for di in dial_infos { + bootstrap_record.dial_info_details.push(DialInfoDetail { + dial_info: di, + class: DialInfoClass::Direct, + }); + } + } + bootstrap_records.push((node_id_key, bootstrap_record)); + } + Some(bootstrap_records) + } + .instrument(Span::current()), + ); + } + + let mut bsmap = BootstrapRecordMap::new(); + while let Some(bootstrap_records) = unord.next().await { + if let Some(bootstrap_records) = bootstrap_records { + for (bskey, mut bsrec) in bootstrap_records { + let rec = bsmap.entry(bskey).or_insert_with(|| BootstrapRecord { + min_version: bsrec.min_version, + max_version: bsrec.max_version, + dial_info_details: Vec::new(), + }); + rec.dial_info_details.append(&mut bsrec.dial_info_details); + } + } + } + + Ok(bsmap) + } + + // 'direct' bootstrap task routine for systems incapable of resolving TXT records, such as browser WASM + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn direct_bootstrap_task_routine( + self, + stop_token: StopToken, + bootstrap_dialinfos: Vec, + ) -> EyreResult<()> { + let mut unord = FuturesUnordered::new(); + let network_manager = self.network_manager(); + + for bootstrap_di in bootstrap_dialinfos { + log_rtab!(debug "direct bootstrap with: {}", bootstrap_di); + let peer_info = network_manager.boot_request(bootstrap_di).await?; + + log_rtab!(debug " direct bootstrap peerinfo: {:?}", peer_info); + + // Got peer info, let's add it to the routing table + for pi in peer_info { + let k = pi.node_id.key; + // Register the node + if let Some(nr) = self.register_node_with_signed_node_info( + RoutingDomain::PublicInternet, + k, + pi.signed_node_info, + false, + ) { + // Add this our futures to process in parallel + let routing_table = self.clone(); + unord.push( + // lets ask bootstrap to find ourselves now + async move { routing_table.reverse_find_node(nr, true).await } + .instrument(Span::current()), + ); + } + } + } + + // Wait for all bootstrap operations to complete before we complete the singlefuture + while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} + + Ok(()) + } + + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn bootstrap_task_routine(self, stop_token: StopToken) -> EyreResult<()> { + let (bootstrap, bootstrap_nodes) = self.with_config(|c| { + ( + c.network.bootstrap.clone(), + c.network.bootstrap_nodes.clone(), + ) + }); + + log_rtab!(debug "--- bootstrap_task"); + + // See if we are specifying a direct dialinfo for bootstrap, if so use the direct mechanism + if !bootstrap.is_empty() && bootstrap_nodes.is_empty() { + let mut bootstrap_dialinfos = Vec::::new(); + for b in &bootstrap { + if let Ok(bootstrap_di_vec) = DialInfo::try_vec_from_url(&b) { + for bootstrap_di in bootstrap_di_vec { + bootstrap_dialinfos.push(bootstrap_di); + } + } + } + if bootstrap_dialinfos.len() > 0 { + return self + .direct_bootstrap_task_routine(stop_token, bootstrap_dialinfos) + .await; + } + } + + // If we aren't specifying a bootstrap node list explicitly, then pull from the bootstrap server(s) + let bsmap: BootstrapRecordMap = if !bootstrap_nodes.is_empty() { + let mut bsmap = BootstrapRecordMap::new(); + let mut bootstrap_node_dial_infos = Vec::new(); + for b in bootstrap_nodes { + let (id_str, di_str) = b + .split_once('@') + .ok_or_else(|| eyre!("Invalid node dial info in bootstrap entry"))?; + let node_id = + NodeId::from_str(id_str).wrap_err("Invalid node id in bootstrap entry")?; + let dial_info = + DialInfo::from_str(di_str).wrap_err("Invalid dial info in bootstrap entry")?; + bootstrap_node_dial_infos.push((node_id, dial_info)); + } + for (node_id, dial_info) in bootstrap_node_dial_infos { + bsmap + .entry(node_id.key) + .or_insert_with(|| BootstrapRecord { + min_version: MIN_CRYPTO_VERSION, + max_version: MAX_CRYPTO_VERSION, + dial_info_details: Vec::new(), + }) + .dial_info_details + .push(DialInfoDetail { + dial_info, + class: DialInfoClass::Direct, // Bootstraps are always directly reachable + }); + } + bsmap + } else { + // Resolve bootstrap servers and recurse their TXT entries + self.resolve_bootstrap(bootstrap).await? + }; + + // Map all bootstrap entries to a single key with multiple dialinfo + + // Run all bootstrap operations concurrently + let mut unord = FuturesUnordered::new(); + for (k, mut v) in bsmap { + // Sort dial info so we get the preferred order correct + v.dial_info_details.sort(); + + log_rtab!("--- bootstrapping {} with {:?}", k.encode(), &v); + + // Make invalid signed node info (no signature) + if let Some(nr) = self.register_node_with_signed_node_info( + RoutingDomain::PublicInternet, + k, + SignedNodeInfo::Direct(SignedDirectNodeInfo::with_no_signature(NodeInfo { + network_class: NetworkClass::InboundCapable, // Bootstraps are always inbound capable + outbound_protocols: ProtocolTypeSet::only(ProtocolType::UDP), // Bootstraps do not participate in relaying and will not make outbound requests, but will have UDP enabled + address_types: AddressTypeSet::all(), // Bootstraps are always IPV4 and IPV6 capable + min_version: v.min_version, // Minimum crypto version specified in txt record + max_version: v.max_version, // Maximum crypto version specified in txt record + dial_info_detail_list: v.dial_info_details, // Dial info is as specified in the bootstrap list + })), + true, + ) { + // Add this our futures to process in parallel + let routing_table = self.clone(); + unord.push( + async move { + // Need VALID signed peer info, so ask bootstrap to find_node of itself + // which will ensure it has the bootstrap's signed peer info as part of the response + let _ = routing_table.find_target(nr.clone()).await; + + // Ensure we got the signed peer info + if !nr.signed_node_info_has_valid_signature(RoutingDomain::PublicInternet) { + log_rtab!(warn + "bootstrap at {:?} did not return valid signed node info", + nr + ); + // If this node info is invalid, it will time out after being unpingable + } else { + // otherwise this bootstrap is valid, lets ask it to find ourselves now + routing_table.reverse_find_node(nr, true).await + } + } + .instrument(Span::current()), + ); + } + } + + // Wait for all bootstrap operations to complete before we complete the singlefuture + while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} + Ok(()) + } +} diff --git a/veilid-core/src/routing_table/tasks/kick_buckets.rs b/veilid-core/src/routing_table/tasks/kick_buckets.rs new file mode 100644 index 00000000..318f1915 --- /dev/null +++ b/veilid-core/src/routing_table/tasks/kick_buckets.rs @@ -0,0 +1,22 @@ +use super::*; + +impl RoutingTable { + // Kick the queued buckets in the routing table to free dead nodes if necessary + // Attempts to keep the size of the routing table down to the bucket depth + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn kick_buckets_task_routine( + self, + _stop_token: StopToken, + _last_ts: Timestamp, + cur_ts: Timestamp, + ) -> EyreResult<()> { + let kick_queue: Vec = core::mem::take(&mut *self.unlocked_inner.kick_queue.lock()) + .into_iter() + .collect(); + let mut inner = self.inner.write(); + for idx in kick_queue { + inner.kick_bucket(idx) + } + Ok(()) + } +} diff --git a/veilid-core/src/routing_table/tasks/mod.rs b/veilid-core/src/routing_table/tasks/mod.rs new file mode 100644 index 00000000..1cc3e317 --- /dev/null +++ b/veilid-core/src/routing_table/tasks/mod.rs @@ -0,0 +1,206 @@ +pub mod bootstrap; +pub mod kick_buckets; +pub mod peer_minimum_refresh; +pub mod ping_validator; +pub mod private_route_management; +pub mod relay_management; +pub mod rolling_transfers; + +use super::*; + +impl RoutingTable { + pub(crate) fn start_tasks(&self) { + // Set rolling transfers tick task + { + let this = self.clone(); + self.unlocked_inner + .rolling_transfers_task + .set_routine(move |s, l, t| { + Box::pin( + this.clone() + .rolling_transfers_task_routine(s, Timestamp::new(l), Timestamp::new(t)) + .instrument(trace_span!( + parent: None, + "RoutingTable rolling transfers task routine" + )), + ) + }); + } + + // Set kick buckets tick task + { + let this = self.clone(); + self.unlocked_inner + .kick_buckets_task + .set_routine(move |s, l, t| { + Box::pin( + this.clone() + .kick_buckets_task_routine(s, Timestamp::new(l), Timestamp::new(t)) + .instrument(trace_span!(parent: None, "kick buckets task routine")), + ) + }); + } + + // Set bootstrap tick task + { + let this = self.clone(); + self.unlocked_inner + .bootstrap_task + .set_routine(move |s, _l, _t| { + Box::pin( + this.clone() + .bootstrap_task_routine(s) + .instrument(trace_span!(parent: None, "bootstrap task routine")), + ) + }); + } + + // Set peer minimum refresh tick task + { + let this = self.clone(); + self.unlocked_inner + .peer_minimum_refresh_task + .set_routine(move |s, _l, _t| { + Box::pin( + this.clone() + .peer_minimum_refresh_task_routine(s) + .instrument(trace_span!( + parent: None, + "peer minimum refresh task routine" + )), + ) + }); + } + + // Set ping validator tick task + { + let this = self.clone(); + self.unlocked_inner + .ping_validator_task + .set_routine(move |s, l, t| { + Box::pin( + this.clone() + .ping_validator_task_routine(s, Timestamp::new(l), Timestamp::new(t)) + .instrument(trace_span!(parent: None, "ping validator task routine")), + ) + }); + } + + // Set relay management tick task + { + let this = self.clone(); + self.unlocked_inner + .relay_management_task + .set_routine(move |s, l, t| { + Box::pin( + this.clone() + .relay_management_task_routine(s, Timestamp::new(l), Timestamp::new(t)) + .instrument(trace_span!(parent: None, "relay management task routine")), + ) + }); + } + + // Set private route management tick task + { + let this = self.clone(); + self.unlocked_inner + .private_route_management_task + .set_routine(move |s, l, t| { + Box::pin( + this.clone() + .private_route_management_task_routine( + s, + Timestamp::new(l), + Timestamp::new(t), + ) + .instrument(trace_span!( + parent: None, + "private route management task routine" + )), + ) + }); + } + } + + /// Ticks about once per second + /// to run tick tasks which may run at slower tick rates as configured + pub async fn tick(&self) -> EyreResult<()> { + // Do rolling transfers every ROLLING_TRANSFERS_INTERVAL_SECS secs + self.unlocked_inner.rolling_transfers_task.tick().await?; + + // Kick buckets task + let kick_bucket_queue_count = self.unlocked_inner.kick_queue.lock().len(); + if kick_bucket_queue_count > 0 { + self.unlocked_inner.kick_buckets_task.tick().await?; + } + + // See how many live PublicInternet entries we have + let live_public_internet_entry_count = self.get_entry_count( + RoutingDomain::PublicInternet.into(), + BucketEntryState::Unreliable, + ); + let min_peer_count = self.with_config(|c| c.network.dht.min_peer_count as usize); + + // If none, then add the bootstrap nodes to it + if live_public_internet_entry_count == 0 { + self.unlocked_inner.bootstrap_task.tick().await?; + } + // If we still don't have enough peers, find nodes until we do + else if !self.unlocked_inner.bootstrap_task.is_running() + && live_public_internet_entry_count < min_peer_count + { + self.unlocked_inner.peer_minimum_refresh_task.tick().await?; + } + + // Ping validate some nodes to groom the table + self.unlocked_inner.ping_validator_task.tick().await?; + + // Run the relay management task + self.unlocked_inner.relay_management_task.tick().await?; + + // Run the private route management task + self.unlocked_inner + .private_route_management_task + .tick() + .await?; + + Ok(()) + } + + pub(crate) async fn stop_tasks(&self) { + // Cancel all tasks being ticked + debug!("stopping rolling transfers task"); + if let Err(e) = self.unlocked_inner.rolling_transfers_task.stop().await { + error!("rolling_transfers_task not stopped: {}", e); + } + debug!("stopping kick buckets task"); + if let Err(e) = self.unlocked_inner.kick_buckets_task.stop().await { + error!("kick_buckets_task not stopped: {}", e); + } + debug!("stopping bootstrap task"); + if let Err(e) = self.unlocked_inner.bootstrap_task.stop().await { + error!("bootstrap_task not stopped: {}", e); + } + debug!("stopping peer minimum refresh task"); + if let Err(e) = self.unlocked_inner.peer_minimum_refresh_task.stop().await { + error!("peer_minimum_refresh_task not stopped: {}", e); + } + debug!("stopping ping_validator task"); + if let Err(e) = self.unlocked_inner.ping_validator_task.stop().await { + error!("ping_validator_task not stopped: {}", e); + } + debug!("stopping relay management task"); + if let Err(e) = self.unlocked_inner.relay_management_task.stop().await { + warn!("relay_management_task not stopped: {}", e); + } + debug!("stopping private route management task"); + if let Err(e) = self + .unlocked_inner + .private_route_management_task + .stop() + .await + { + warn!("private_route_management_task not stopped: {}", e); + } + } +} diff --git a/veilid-core/src/routing_table/tasks/peer_minimum_refresh.rs b/veilid-core/src/routing_table/tasks/peer_minimum_refresh.rs new file mode 100644 index 00000000..157e6030 --- /dev/null +++ b/veilid-core/src/routing_table/tasks/peer_minimum_refresh.rs @@ -0,0 +1,46 @@ +use super::*; + +use futures_util::stream::{FuturesOrdered, StreamExt}; +use stop_token::future::FutureExt as StopFutureExt; + +impl RoutingTable { + // Ask our remaining peers to give us more peers before we go + // back to the bootstrap servers to keep us from bothering them too much + // This only adds PublicInternet routing domain peers. The discovery + // mechanism for LocalNetwork suffices for locating all the local network + // peers that are available. This, however, may query other LocalNetwork + // nodes for their PublicInternet peers, which is a very fast way to get + // a new node online. + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn peer_minimum_refresh_task_routine( + self, + stop_token: StopToken, + ) -> EyreResult<()> { + let min_peer_count = self.with_config(|c| c.network.dht.min_peer_count as usize); + + // For the PublicInternet routing domain, get list of all peers we know about + // even the unreliable ones, and ask them to find nodes close to our node too + let routing_table = self.clone(); + let noderefs = routing_table.find_fastest_nodes( + min_peer_count, + VecDeque::new(), + |_rti, k: DHTKey, v: Option>| { + NodeRef::new(routing_table.clone(), k, v.unwrap().clone(), None) + }, + ); + + let mut ord = FuturesOrdered::new(); + for nr in noderefs { + let routing_table = self.clone(); + ord.push_back( + async move { routing_table.reverse_find_node(nr, false).await } + .instrument(Span::current()), + ); + } + + // do peer minimum search in order from fastest to slowest + while let Ok(Some(_)) = ord.next().timeout_at(stop_token.clone()).await {} + + Ok(()) + } +} diff --git a/veilid-core/src/routing_table/tasks/ping_validator.rs b/veilid-core/src/routing_table/tasks/ping_validator.rs new file mode 100644 index 00000000..f94f603c --- /dev/null +++ b/veilid-core/src/routing_table/tasks/ping_validator.rs @@ -0,0 +1,141 @@ +use super::*; + +use futures_util::stream::{FuturesUnordered, StreamExt}; +use futures_util::FutureExt; +use stop_token::future::FutureExt as StopFutureExt; + +impl RoutingTable { + // Ping each node in the routing table if they need to be pinged + // to determine their reliability + #[instrument(level = "trace", skip(self), err)] + fn ping_validator_public_internet( + &self, + cur_ts: Timestamp, + unord: &mut FuturesUnordered< + SendPinBoxFuture>>, RPCError>>, + >, + ) -> EyreResult<()> { + let rpc = self.rpc_processor(); + + // Get all nodes needing pings in the PublicInternet routing domain + let node_refs = self.get_nodes_needing_ping(RoutingDomain::PublicInternet, cur_ts); + + // Look up any NAT mappings we may need to try to preserve with keepalives + let mut mapped_port_info = self.get_low_level_port_info(); + + // Get the PublicInternet relay if we are using one + let opt_relay_nr = self.relay_node(RoutingDomain::PublicInternet); + let opt_relay_id = opt_relay_nr.map(|nr| nr.node_id()); + + // Get our publicinternet dial info + let dids = self.all_filtered_dial_info_details( + RoutingDomain::PublicInternet.into(), + &DialInfoFilter::all(), + ); + + // For all nodes needing pings, figure out how many and over what protocols + for nr in node_refs { + // If this is a relay, let's check for NAT keepalives + let mut did_pings = false; + if Some(nr.node_id()) == opt_relay_id { + // Relay nodes get pinged over all protocols we have inbound dialinfo for + // This is so we can preserve the inbound NAT mappings at our router + for did in &dids { + // Do we need to do this ping? + // Check if we have already pinged over this low-level-protocol/address-type/port combo + // We want to ensure we do the bare minimum required here + let pt = did.dial_info.protocol_type(); + let at = did.dial_info.address_type(); + let needs_ping = if let Some((llpt, port)) = + mapped_port_info.protocol_to_port.get(&(pt, at)) + { + mapped_port_info + .low_level_protocol_ports + .remove(&(*llpt, at, *port)) + } else { + false + }; + if needs_ping { + let rpc = rpc.clone(); + let dif = did.dial_info.make_filter(); + let nr_filtered = + nr.filtered_clone(NodeRefFilter::new().with_dial_info_filter(dif)); + log_net!("--> Keepalive ping to {:?}", nr_filtered); + unord.push( + async move { rpc.rpc_call_status(Destination::direct(nr_filtered)).await } + .instrument(Span::current()) + .boxed(), + ); + did_pings = true; + } + } + } + // Just do a single ping with the best protocol for all the other nodes, + // ensuring that we at least ping a relay with -something- even if we didnt have + // any mapped ports to preserve + if !did_pings { + let rpc = rpc.clone(); + unord.push( + async move { rpc.rpc_call_status(Destination::direct(nr)).await } + .instrument(Span::current()) + .boxed(), + ); + } + } + + Ok(()) + } + + // Ping each node in the LocalNetwork routing domain if they + // need to be pinged to determine their reliability + #[instrument(level = "trace", skip(self), err)] + fn ping_validator_local_network( + &self, + cur_ts: Timestamp, + unord: &mut FuturesUnordered< + SendPinBoxFuture>>, RPCError>>, + >, + ) -> EyreResult<()> { + let rpc = self.rpc_processor(); + + // Get all nodes needing pings in the LocalNetwork routing domain + let node_refs = self.get_nodes_needing_ping(RoutingDomain::LocalNetwork, cur_ts); + + // For all nodes needing pings, figure out how many and over what protocols + for nr in node_refs { + let rpc = rpc.clone(); + + // Just do a single ping with the best protocol for all the nodes + unord.push( + async move { rpc.rpc_call_status(Destination::direct(nr)).await } + .instrument(Span::current()) + .boxed(), + ); + } + + Ok(()) + } + + // Ping each node in the routing table if they need to be pinged + // to determine their reliability + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn ping_validator_task_routine( + self, + stop_token: StopToken, + _last_ts: Timestamp, + cur_ts: Timestamp, + ) -> EyreResult<()> { + let mut unord = FuturesUnordered::new(); + + // PublicInternet + self.ping_validator_public_internet(cur_ts, &mut unord)?; + + // LocalNetwork + self.ping_validator_local_network(cur_ts, &mut unord)?; + + // Wait for ping futures to complete in parallel + while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} + + Ok(()) + } +} diff --git a/veilid-core/src/routing_table/tasks/private_route_management.rs b/veilid-core/src/routing_table/tasks/private_route_management.rs new file mode 100644 index 00000000..b54b73ae --- /dev/null +++ b/veilid-core/src/routing_table/tasks/private_route_management.rs @@ -0,0 +1,235 @@ +use super::*; + +use futures_util::stream::{FuturesUnordered, StreamExt}; +use futures_util::FutureExt; +use stop_token::future::FutureExt as StopFutureExt; + +const BACKGROUND_SAFETY_ROUTE_COUNT: usize = 2; + +impl RoutingTable { + /// Fastest routes sort + fn route_sort_latency_fn(a: &(DHTKey, u64), b: &(DHTKey, u64)) -> cmp::Ordering { + let mut al = a.1; + let mut bl = b.1; + // Treat zero latency as uncalculated + if al == 0 { + al = u64::MAX; + } + if bl == 0 { + bl = u64::MAX; + } + // Less is better + al.cmp(&bl) + } + + /// Get the list of routes to test and drop + /// + /// Allocated routes to test: + /// * if a route 'needs_testing' + /// . all published allocated routes + /// . the fastest 0..N default length routes + /// Routes to drop: + /// * if a route 'needs_testing' + /// . the N.. default routes + /// . the rest of the allocated unpublished routes + /// + /// If a route doesn't 'need_testing', then we neither test nor drop it + #[instrument(level = "trace", skip(self))] + fn get_allocated_routes_to_test(&self, cur_ts: Timestamp) -> Vec { + let default_route_hop_count = + self.with_config(|c| c.network.rpc.default_route_hop_count as usize); + + let rss = self.route_spec_store(); + let mut must_test_routes = Vec::::new(); + let mut unpublished_routes = Vec::<(DHTKey, u64)>::new(); + let mut expired_routes = Vec::::new(); + rss.list_allocated_routes(|k, v| { + let stats = v.get_stats(); + // Ignore nodes that don't need testing + if !stats.needs_testing(cur_ts) { + return Option::<()>::None; + } + // If this has been published, always test if we need it + // Also if the route has never been tested, test it at least once + if v.is_published() || stats.last_tested_ts.is_none() { + must_test_routes.push(*k); + } + // If this is a default route hop length, include it in routes to keep alive + else if v.hop_count() == default_route_hop_count { + unpublished_routes.push((*k, stats.latency_stats.average.as_u64())); + } + // Else this is a route that hasnt been used recently enough and we can tear it down + else { + expired_routes.push(*k); + } + Option::<()>::None + }); + + // Sort unpublished routes by speed if we know the speed + unpublished_routes.sort_by(Self::route_sort_latency_fn); + + // Save up to N unpublished routes and test them + for x in 0..(usize::min(BACKGROUND_SAFETY_ROUTE_COUNT, unpublished_routes.len())) { + must_test_routes.push(unpublished_routes[x].0); + } + + // Kill off all but N unpublished routes rather than testing them + if unpublished_routes.len() > BACKGROUND_SAFETY_ROUTE_COUNT { + for x in &unpublished_routes[BACKGROUND_SAFETY_ROUTE_COUNT..] { + expired_routes.push(x.0); + } + } + + // Process dead routes + for r in &expired_routes { + log_rtab!(debug "Expired route: {}", r); + rss.release_route(r); + } + + // return routes to test + must_test_routes + } + + /// Test set of routes and remove the ones that don't test clean + #[instrument(level = "trace", skip(self, stop_token), err)] + async fn test_route_set( + &self, + stop_token: StopToken, + routes_needing_testing: Vec, + ) -> EyreResult<()> { + if routes_needing_testing.is_empty() { + return Ok(()); + } + log_rtab!("Testing routes: {:?}", routes_needing_testing); + + // Test all the routes that need testing at the same time + let rss = self.route_spec_store(); + #[derive(Default, Debug)] + struct TestRouteContext { + failed: bool, + dead_routes: Vec, + } + + let mut unord = FuturesUnordered::new(); + let ctx = Arc::new(Mutex::new(TestRouteContext::default())); + for r in routes_needing_testing { + let rss = rss.clone(); + let ctx = ctx.clone(); + unord.push( + async move { + let success = match rss.test_route(&r).await { + Ok(v) => v, + Err(e) => { + log_rtab!(error "Test route failed: {}", e); + ctx.lock().failed = true; + return; + } + }; + if success { + // Route is okay, leave it alone + return; + } + // Route test failed + ctx.lock().dead_routes.push(r); + } + .instrument(Span::current()) + .boxed(), + ); + } + + // Wait for test_route futures to complete in parallel + while let Ok(Some(_)) = unord.next().timeout_at(stop_token.clone()).await {} + + // Process failed routes + let ctx = &mut *ctx.lock(); + for r in &ctx.dead_routes { + log_rtab!(debug "Dead route failed to test: {}", &r); + rss.release_route(r); + } + + Ok(()) + } + + /// Keep private routes assigned and accessible + #[instrument(level = "trace", skip(self, stop_token), err)] + pub(crate) async fn private_route_management_task_routine( + self, + stop_token: StopToken, + _last_ts: Timestamp, + cur_ts: Timestamp, + ) -> EyreResult<()> { + // Get our node's current node info and network class and do the right thing + let network_class = self + .get_network_class(RoutingDomain::PublicInternet) + .unwrap_or(NetworkClass::Invalid); + + // If we don't know our network class then don't do this yet + if network_class == NetworkClass::Invalid { + return Ok(()); + } + + // Test locally allocated routes first + // This may remove dead routes + let routes_needing_testing = self.get_allocated_routes_to_test(cur_ts); + if !routes_needing_testing.is_empty() { + self.test_route_set(stop_token.clone(), routes_needing_testing) + .await?; + } + + // Ensure we have a minimum of N allocated local, unpublished routes with the default number of hops + let default_route_hop_count = + self.with_config(|c| c.network.rpc.default_route_hop_count as usize); + let mut local_unpublished_route_count = 0usize; + let rss = self.route_spec_store(); + rss.list_allocated_routes(|_k, v| { + if !v.is_published() && v.hop_count() == default_route_hop_count { + local_unpublished_route_count += 1; + } + Option::<()>::None + }); + if local_unpublished_route_count < BACKGROUND_SAFETY_ROUTE_COUNT { + let routes_to_allocate = BACKGROUND_SAFETY_ROUTE_COUNT - local_unpublished_route_count; + + // Newly allocated routes + let mut newly_allocated_routes = Vec::new(); + for _n in 0..routes_to_allocate { + // Parameters here must be the default safety route spec + // These will be used by test_remote_route as well + if let Some(k) = rss.allocate_route( + Stability::default(), + Sequencing::default(), + default_route_hop_count, + DirectionSet::all(), + &[], + )? { + newly_allocated_routes.push(k); + } + } + + // Immediately test them + if !newly_allocated_routes.is_empty() { + self.test_route_set(stop_token.clone(), newly_allocated_routes) + .await?; + } + } + + // Test remote routes next + let remote_routes_needing_testing = rss.list_remote_routes(|k, v| { + let stats = v.get_stats(); + if stats.needs_testing(cur_ts) { + return Some(*k); + } else { + return None; + } + }); + if !remote_routes_needing_testing.is_empty() { + self.test_route_set(stop_token.clone(), remote_routes_needing_testing) + .await?; + } + + // Send update (also may send updates for released routes done by other parts of the program) + rss.send_route_update(); + + Ok(()) + } +} diff --git a/veilid-core/src/routing_table/tasks/relay_management.rs b/veilid-core/src/routing_table/tasks/relay_management.rs new file mode 100644 index 00000000..4e685c02 --- /dev/null +++ b/veilid-core/src/routing_table/tasks/relay_management.rs @@ -0,0 +1,80 @@ +use super::*; + +impl RoutingTable { + // Keep relays assigned and accessible + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn relay_management_task_routine( + self, + _stop_token: StopToken, + _last_ts: Timestamp, + cur_ts: Timestamp, + ) -> EyreResult<()> { + // Get our node's current node info and network class and do the right thing + let Some(own_peer_info) = self.get_own_peer_info(RoutingDomain::PublicInternet) else { + return Ok(()); + }; + let own_node_info = own_peer_info.signed_node_info.node_info(); + let network_class = own_node_info.network_class; + + // Get routing domain editor + let mut editor = self.edit_routing_domain(RoutingDomain::PublicInternet); + + // If we already have a relay, see if it is dead, or if we don't need it any more + let has_relay = { + if let Some(relay_node) = self.relay_node(RoutingDomain::PublicInternet) { + let state = relay_node.state(cur_ts); + // Relay node is dead or no longer needed + if matches!(state, BucketEntryState::Dead) { + info!("Relay node died, dropping relay {}", relay_node); + editor.clear_relay_node(); + false + } else if !own_node_info.requires_relay() { + info!( + "Relay node no longer required, dropping relay {}", + relay_node + ); + editor.clear_relay_node(); + false + } else { + true + } + } else { + false + } + }; + + // Do we need a relay? + if !has_relay && own_node_info.requires_relay() { + // Do we want an outbound relay? + let mut got_outbound_relay = false; + if network_class.outbound_wants_relay() { + // The outbound relay is the host of the PWA + if let Some(outbound_relay_peerinfo) = intf::get_outbound_relay_peer().await { + // Register new outbound relay + if let Some(nr) = self.register_node_with_signed_node_info( + RoutingDomain::PublicInternet, + outbound_relay_peerinfo.node_id.key, + outbound_relay_peerinfo.signed_node_info, + false, + ) { + info!("Outbound relay node selected: {}", nr); + editor.set_relay_node(nr); + got_outbound_relay = true; + } + } + } + if !got_outbound_relay { + // Find a node in our routing table that is an acceptable inbound relay + if let Some(nr) = self.find_inbound_relay(RoutingDomain::PublicInternet, cur_ts) { + info!("Inbound relay node selected: {}", nr); + editor.set_relay_node(nr); + } + } + } + + // Commit the changes + editor.commit().await; + + Ok(()) + } +} diff --git a/veilid-core/src/routing_table/tasks/rolling_transfers.rs b/veilid-core/src/routing_table/tasks/rolling_transfers.rs new file mode 100644 index 00000000..436381ec --- /dev/null +++ b/veilid-core/src/routing_table/tasks/rolling_transfers.rs @@ -0,0 +1,40 @@ +use super::*; + +impl RoutingTable { + // Compute transfer statistics to determine how 'fast' a node is + #[instrument(level = "trace", skip(self), err)] + pub(crate) async fn rolling_transfers_task_routine( + self, + _stop_token: StopToken, + last_ts: Timestamp, + cur_ts: Timestamp, + ) -> EyreResult<()> { + // log_rtab!("--- rolling_transfers task"); + { + let inner = &mut *self.inner.write(); + + // Roll our own node's transfers + inner.self_transfer_stats_accounting.roll_transfers( + last_ts, + cur_ts, + &mut inner.self_transfer_stats, + ); + + // Roll all bucket entry transfers + let entries: Vec> = inner + .buckets + .iter() + .flat_map(|b| b.entries().map(|(_k, v)| v.clone())) + .collect(); + for v in entries { + v.with_mut(inner, |_rti, e| e.roll_transfers(last_ts, cur_ts)); + } + } + + // Roll all route transfers + let rss = self.route_spec_store(); + rss.roll_transfers(last_ts, cur_ts); + + Ok(()) + } +} diff --git a/veilid-core/src/rpc_processor/coders/address.rs b/veilid-core/src/rpc_processor/coders/address.rs index ff3aabea..7e18ac02 100644 --- a/veilid-core/src/rpc_processor/coders/address.rs +++ b/veilid-core/src/rpc_processor/coders/address.rs @@ -1,7 +1,5 @@ -use crate::xx::*; -use crate::*; +use super::*; use core::convert::TryInto; -use rpc_processor::*; pub fn encode_address( address: &Address, diff --git a/veilid-core/src/rpc_processor/coders/address_type_set.rs b/veilid-core/src/rpc_processor/coders/address_type_set.rs index 67a40159..f341d980 100644 --- a/veilid-core/src/rpc_processor/coders/address_type_set.rs +++ b/veilid-core/src/rpc_processor/coders/address_type_set.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_address_type_set( address_type_set: &AddressTypeSet, diff --git a/veilid-core/src/rpc_processor/coders/dht_key.rs b/veilid-core/src/rpc_processor/coders/dht_key.rs index dd7a8909..bc1e3f22 100644 --- a/veilid-core/src/rpc_processor/coders/dht_key.rs +++ b/veilid-core/src/rpc_processor/coders/dht_key.rs @@ -1,7 +1,5 @@ -use crate::crypto::*; -use crate::*; +use super::*; use core::convert::TryInto; -use rpc_processor::*; pub fn decode_dht_key(public_key: &veilid_capnp::key256::Reader) -> DHTKey { let u0 = public_key.get_u0().to_be_bytes(); diff --git a/veilid-core/src/rpc_processor/coders/dht_signature.rs b/veilid-core/src/rpc_processor/coders/dht_signature.rs index 9e008faa..5b7427b2 100644 --- a/veilid-core/src/rpc_processor/coders/dht_signature.rs +++ b/veilid-core/src/rpc_processor/coders/dht_signature.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_signature(sig: &DHTSignature, builder: &mut veilid_capnp::signature512::Builder) { let sig = &sig.bytes; diff --git a/veilid-core/src/rpc_processor/coders/dial_info.rs b/veilid-core/src/rpc_processor/coders/dial_info.rs index c178bb0c..400075b8 100644 --- a/veilid-core/src/rpc_processor/coders/dial_info.rs +++ b/veilid-core/src/rpc_processor/coders/dial_info.rs @@ -1,7 +1,5 @@ -use crate::xx::*; -use crate::*; +use super::*; use core::convert::TryInto; -use rpc_processor::*; pub fn decode_dial_info(reader: &veilid_capnp::dial_info::Reader) -> Result { match reader diff --git a/veilid-core/src/rpc_processor/coders/dial_info_class.rs b/veilid-core/src/rpc_processor/coders/dial_info_class.rs index 71197171..835f060e 100644 --- a/veilid-core/src/rpc_processor/coders/dial_info_class.rs +++ b/veilid-core/src/rpc_processor/coders/dial_info_class.rs @@ -1,4 +1,4 @@ -use crate::*; +use super::*; pub fn encode_dial_info_class(dial_info_class: DialInfoClass) -> veilid_capnp::DialInfoClass { match dial_info_class { diff --git a/veilid-core/src/rpc_processor/coders/dial_info_detail.rs b/veilid-core/src/rpc_processor/coders/dial_info_detail.rs index c3dc1b27..7b012fe3 100644 --- a/veilid-core/src/rpc_processor/coders/dial_info_detail.rs +++ b/veilid-core/src/rpc_processor/coders/dial_info_detail.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_dial_info_detail( dial_info_detail: &DialInfoDetail, diff --git a/veilid-core/src/rpc_processor/coders/mod.rs b/veilid-core/src/rpc_processor/coders/mod.rs index b5c3c709..dafc01f2 100644 --- a/veilid-core/src/rpc_processor/coders/mod.rs +++ b/veilid-core/src/rpc_processor/coders/mod.rs @@ -14,6 +14,7 @@ mod peer_info; mod private_safety_route; mod protocol_type_set; mod sender_info; +mod sequencing; mod signal_info; mod signed_direct_node_info; mod signed_node_info; @@ -39,6 +40,7 @@ pub use peer_info::*; pub use private_safety_route::*; pub use protocol_type_set::*; pub use sender_info::*; +pub use sequencing::*; pub use signal_info::*; pub use signed_direct_node_info::*; pub use signed_node_info::*; diff --git a/veilid-core/src/rpc_processor/coders/network_class.rs b/veilid-core/src/rpc_processor/coders/network_class.rs index 88d17fce..65bb9e74 100644 --- a/veilid-core/src/rpc_processor/coders/network_class.rs +++ b/veilid-core/src/rpc_processor/coders/network_class.rs @@ -1,4 +1,4 @@ -use crate::*; +use super::*; pub fn encode_network_class(network_class: NetworkClass) -> veilid_capnp::NetworkClass { match network_class { diff --git a/veilid-core/src/rpc_processor/coders/node_info.rs b/veilid-core/src/rpc_processor/coders/node_info.rs index 02f9bd78..14ab95b3 100644 --- a/veilid-core/src/rpc_processor/coders/node_info.rs +++ b/veilid-core/src/rpc_processor/coders/node_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_node_info( node_info: &NodeInfo, diff --git a/veilid-core/src/rpc_processor/coders/node_status.rs b/veilid-core/src/rpc_processor/coders/node_status.rs index a07e977f..ed72123b 100644 --- a/veilid-core/src/rpc_processor/coders/node_status.rs +++ b/veilid-core/src/rpc_processor/coders/node_status.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_public_internet_node_status( public_internet_node_status: &PublicInternetNodeStatus, diff --git a/veilid-core/src/rpc_processor/coders/nonce.rs b/veilid-core/src/rpc_processor/coders/nonce.rs index 5eb39dce..ac7d48c0 100644 --- a/veilid-core/src/rpc_processor/coders/nonce.rs +++ b/veilid-core/src/rpc_processor/coders/nonce.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_nonce(nonce: &Nonce, builder: &mut veilid_capnp::nonce24::Builder) { builder.set_u0(u64::from_be_bytes( diff --git a/veilid-core/src/rpc_processor/coders/operations/answer.rs b/veilid-core/src/rpc_processor/coders/operations/answer.rs index e6d801a3..7dd429a2 100644 --- a/veilid-core/src/rpc_processor/coders/operations/answer.rs +++ b/veilid-core/src/rpc_processor/coders/operations/answer.rs @@ -1,6 +1,4 @@ use super::*; -use crate::*; -use rpc_processor::*; #[derive(Debug, Clone)] pub struct RPCAnswer { diff --git a/veilid-core/src/rpc_processor/coders/operations/mod.rs b/veilid-core/src/rpc_processor/coders/operations/mod.rs index 687ca68e..7caf0fb2 100644 --- a/veilid-core/src/rpc_processor/coders/operations/mod.rs +++ b/veilid-core/src/rpc_processor/coders/operations/mod.rs @@ -7,7 +7,6 @@ mod operation_complete_tunnel; mod operation_find_block; mod operation_find_node; mod operation_get_value; -mod operation_node_info_update; mod operation_return_receipt; mod operation_route; mod operation_set_value; @@ -31,7 +30,6 @@ pub use operation_complete_tunnel::*; pub use operation_find_block::*; pub use operation_find_node::*; pub use operation_get_value::*; -pub use operation_node_info_update::*; pub use operation_return_receipt::*; pub use operation_route::*; pub use operation_set_value::*; @@ -45,3 +43,5 @@ pub use operation_watch_value::*; pub use question::*; pub use respond_to::*; pub use statement::*; + +use super::*; diff --git a/veilid-core/src/rpc_processor/coders/operations/operation.rs b/veilid-core/src/rpc_processor/coders/operations/operation.rs index a33ab29c..f23c1df8 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub enum RPCOperationKind { @@ -17,10 +16,7 @@ impl RPCOperationKind { } } - pub fn decode( - kind_reader: &veilid_capnp::operation::kind::Reader, - opt_sender_node_id: Option<&DHTKey>, - ) -> Result { + pub fn decode(kind_reader: &veilid_capnp::operation::kind::Reader) -> Result { let which_reader = kind_reader.which().map_err(RPCError::protocol)?; let out = match which_reader { veilid_capnp::operation::kind::Which::Question(r) => { @@ -30,7 +26,7 @@ impl RPCOperationKind { } veilid_capnp::operation::kind::Which::Statement(r) => { let q_reader = r.map_err(RPCError::protocol)?; - let out = RPCStatement::decode(&q_reader, opt_sender_node_id)?; + let out = RPCStatement::decode(&q_reader)?; RPCOperationKind::Statement(out) } veilid_capnp::operation::kind::Which::Answer(r) => { @@ -57,26 +53,32 @@ impl RPCOperationKind { #[derive(Debug, Clone)] pub struct RPCOperation { - op_id: u64, + op_id: OperationId, sender_node_info: Option, + target_node_info_ts: Timestamp, kind: RPCOperationKind, } impl RPCOperation { - pub fn new_question(question: RPCQuestion, sender_node_info: Option) -> Self { + pub fn new_question( + question: RPCQuestion, + sender_signed_node_info: SenderSignedNodeInfo, + ) -> Self { Self { - op_id: intf::get_random_u64(), - sender_node_info, + op_id: OperationId::new(get_random_u64()), + sender_node_info: sender_signed_node_info.signed_node_info, + target_node_info_ts: sender_signed_node_info.target_node_info_ts, kind: RPCOperationKind::Question(question), } } pub fn new_statement( statement: RPCStatement, - sender_node_info: Option, + sender_signed_node_info: SenderSignedNodeInfo, ) -> Self { Self { - op_id: intf::get_random_u64(), - sender_node_info, + op_id: OperationId::new(get_random_u64()), + sender_node_info: sender_signed_node_info.signed_node_info, + target_node_info_ts: sender_signed_node_info.target_node_info_ts, kind: RPCOperationKind::Statement(statement), } } @@ -84,22 +86,26 @@ impl RPCOperation { pub fn new_answer( request: &RPCOperation, answer: RPCAnswer, - sender_node_info: Option, + sender_signed_node_info: SenderSignedNodeInfo, ) -> Self { Self { op_id: request.op_id, - sender_node_info, + sender_node_info: sender_signed_node_info.signed_node_info, + target_node_info_ts: sender_signed_node_info.target_node_info_ts, kind: RPCOperationKind::Answer(answer), } } - pub fn op_id(&self) -> u64 { + pub fn op_id(&self) -> OperationId { self.op_id } pub fn sender_node_info(&self) -> Option<&SignedNodeInfo> { self.sender_node_info.as_ref() } + pub fn target_node_info_ts(&self) -> Timestamp { + self.target_node_info_ts + } pub fn kind(&self) -> &RPCOperationKind { &self.kind @@ -113,7 +119,7 @@ impl RPCOperation { operation_reader: &veilid_capnp::operation::Reader, opt_sender_node_id: Option<&DHTKey>, ) -> Result { - let op_id = operation_reader.get_op_id(); + let op_id = OperationId::new(operation_reader.get_op_id()); let sender_node_info = if operation_reader.has_sender_node_info() { if let Some(sender_node_id) = opt_sender_node_id { @@ -129,22 +135,26 @@ impl RPCOperation { None }; + let target_node_info_ts = Timestamp::new(operation_reader.get_target_node_info_ts()); + let kind_reader = operation_reader.get_kind(); - let kind = RPCOperationKind::decode(&kind_reader, opt_sender_node_id)?; + let kind = RPCOperationKind::decode(&kind_reader)?; Ok(RPCOperation { op_id, sender_node_info, + target_node_info_ts, kind, }) } pub fn encode(&self, builder: &mut veilid_capnp::operation::Builder) -> Result<(), RPCError> { - builder.set_op_id(self.op_id); + builder.set_op_id(self.op_id.as_u64()); if let Some(sender_info) = &self.sender_node_info { let mut si_builder = builder.reborrow().init_sender_node_info(); encode_signed_node_info(&sender_info, &mut si_builder)?; } + builder.set_target_node_info_ts(self.target_node_info_ts.as_u64()); let mut k_builder = builder.reborrow().init_kind(); self.kind.encode(&mut k_builder)?; Ok(()) diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_app_call.rs b/veilid-core/src/rpc_processor/coders/operations/operation_app_call.rs index 609999bb..b1360b9a 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_app_call.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_app_call.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationAppCallQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_app_message.rs b/veilid-core/src/rpc_processor/coders/operations/operation_app_message.rs index 5a844f02..5c969be7 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_app_message.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_app_message.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationAppMessage { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs b/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs index c9ca4e96..3484ab0c 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_cancel_tunnel.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationCancelTunnelQ { @@ -10,7 +9,7 @@ impl RPCOperationCancelTunnelQ { pub fn decode( reader: &veilid_capnp::operation_cancel_tunnel_q::Reader, ) -> Result { - let id = reader.get_id(); + let id = TunnelId::new(reader.get_id()); Ok(RPCOperationCancelTunnelQ { id }) } @@ -18,7 +17,7 @@ impl RPCOperationCancelTunnelQ { &self, builder: &mut veilid_capnp::operation_cancel_tunnel_q::Builder, ) -> Result<(), RPCError> { - builder.set_id(self.id); + builder.set_id(self.id.as_u64()); Ok(()) } @@ -36,7 +35,7 @@ impl RPCOperationCancelTunnelA { ) -> Result { match reader.which().map_err(RPCError::protocol)? { veilid_capnp::operation_cancel_tunnel_a::Which::Tunnel(r) => { - Ok(RPCOperationCancelTunnelA::Tunnel(r)) + Ok(RPCOperationCancelTunnelA::Tunnel(TunnelId::new(r))) } veilid_capnp::operation_cancel_tunnel_a::Which::Error(r) => { let tunnel_error = decode_tunnel_error(r.map_err(RPCError::protocol)?); @@ -50,7 +49,7 @@ impl RPCOperationCancelTunnelA { ) -> Result<(), RPCError> { match self { RPCOperationCancelTunnelA::Tunnel(p) => { - builder.set_tunnel(*p); + builder.set_tunnel(p.as_u64()); } RPCOperationCancelTunnelA::Error(e) => { builder.set_error(encode_tunnel_error(*e)); diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs b/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs index 453c38c0..e4737a2d 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_complete_tunnel.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationCompleteTunnelQ { @@ -13,7 +12,7 @@ impl RPCOperationCompleteTunnelQ { pub fn decode( reader: &veilid_capnp::operation_complete_tunnel_q::Reader, ) -> Result { - let id = reader.get_id(); + let id = TunnelId::new(reader.get_id()); let local_mode = match reader.get_local_mode().map_err(RPCError::protocol)? { veilid_capnp::TunnelEndpointMode::Raw => TunnelMode::Raw, veilid_capnp::TunnelEndpointMode::Turn => TunnelMode::Turn, @@ -33,7 +32,7 @@ impl RPCOperationCompleteTunnelQ { &self, builder: &mut veilid_capnp::operation_complete_tunnel_q::Builder, ) -> Result<(), RPCError> { - builder.set_id(self.id); + builder.set_id(self.id.as_u64()); builder.set_local_mode(match self.local_mode { TunnelMode::Raw => veilid_capnp::TunnelEndpointMode::Raw, TunnelMode::Turn => veilid_capnp::TunnelEndpointMode::Turn, diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs b/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs index 5503ad31..ce42da3f 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_find_block.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationFindBlockQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs b/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs index 95ca3ea5..cf6bb675 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_find_node.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationFindNodeQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs index bb800511..f9fc9959 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_get_value.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationGetValueQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_node_info_update.rs b/veilid-core/src/rpc_processor/coders/operations/operation_node_info_update.rs deleted file mode 100644 index 5f077816..00000000 --- a/veilid-core/src/rpc_processor/coders/operations/operation_node_info_update.rs +++ /dev/null @@ -1,33 +0,0 @@ -use crate::*; -use rpc_processor::*; - -#[derive(Debug, Clone)] -pub struct RPCOperationNodeInfoUpdate { - pub signed_node_info: SignedNodeInfo, -} - -impl RPCOperationNodeInfoUpdate { - pub fn decode( - reader: &veilid_capnp::operation_node_info_update::Reader, - opt_sender_node_id: Option<&DHTKey>, - ) -> Result { - if opt_sender_node_id.is_none() { - return Err(RPCError::protocol( - "can't decode node info update without sender node id", - )); - } - let sender_node_id = opt_sender_node_id.unwrap(); - let sni_reader = reader.get_signed_node_info().map_err(RPCError::protocol)?; - let signed_node_info = decode_signed_node_info(&sni_reader, sender_node_id)?; - - Ok(RPCOperationNodeInfoUpdate { signed_node_info }) - } - pub fn encode( - &self, - builder: &mut veilid_capnp::operation_node_info_update::Builder, - ) -> Result<(), RPCError> { - let mut sni_builder = builder.reborrow().init_signed_node_info(); - encode_signed_node_info(&self.signed_node_info, &mut sni_builder)?; - Ok(()) - } -} diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_return_receipt.rs b/veilid-core/src/rpc_processor/coders/operations/operation_return_receipt.rs index 31c1d213..bd7517a7 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_return_receipt.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_return_receipt.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationReturnReceipt { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_route.rs b/veilid-core/src/rpc_processor/coders/operations/operation_route.rs index 68c97191..88d65fe9 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_route.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_route.rs @@ -1,18 +1,19 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RoutedOperation { pub version: u8, + pub sequencing: Sequencing, pub signatures: Vec, pub nonce: Nonce, pub data: Vec, } impl RoutedOperation { - pub fn new(version: u8, nonce: Nonce, data: Vec) -> Self { + pub fn new(version: u8, sequencing: Sequencing, nonce: Nonce, data: Vec) -> Self { Self { version, + sequencing, signatures: Vec::new(), nonce, data, @@ -35,12 +36,14 @@ impl RoutedOperation { } let version = reader.get_version(); + let sequencing = decode_sequencing(reader.get_sequencing().map_err(RPCError::protocol)?); let n_reader = reader.get_nonce().map_err(RPCError::protocol)?; let nonce = decode_nonce(&n_reader); let data = reader.get_data().map_err(RPCError::protocol)?.to_vec(); Ok(RoutedOperation { version, + sequencing, signatures, nonce, data, @@ -52,6 +55,9 @@ impl RoutedOperation { builder: &mut veilid_capnp::routed_operation::Builder, ) -> Result<(), RPCError> { builder.reborrow().set_version(self.version); + builder + .reborrow() + .set_sequencing(encode_sequencing(self.sequencing)); let mut sigs_builder = builder.reborrow().init_signatures( self.signatures .len() diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs index 18c430d1..23a34421 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_set_value.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationSetValueQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs b/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs index e31a6f47..4b8a6fd3 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_signal.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationSignal { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs b/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs index 08c1982b..274b0af8 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_start_tunnel.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationStartTunnelQ { @@ -12,7 +11,7 @@ impl RPCOperationStartTunnelQ { pub fn decode( reader: &veilid_capnp::operation_start_tunnel_q::Reader, ) -> Result { - let id = reader.get_id(); + let id = TunnelId::new(reader.get_id()); let local_mode = match reader.get_local_mode().map_err(RPCError::protocol)? { veilid_capnp::TunnelEndpointMode::Raw => TunnelMode::Raw, veilid_capnp::TunnelEndpointMode::Turn => TunnelMode::Turn, @@ -29,7 +28,7 @@ impl RPCOperationStartTunnelQ { &self, builder: &mut veilid_capnp::operation_start_tunnel_q::Builder, ) -> Result<(), RPCError> { - builder.set_id(self.id); + builder.set_id(self.id.as_u64()); builder.set_local_mode(match self.local_mode { TunnelMode::Raw => veilid_capnp::TunnelEndpointMode::Raw, TunnelMode::Turn => veilid_capnp::TunnelEndpointMode::Turn, diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_status.rs b/veilid-core/src/rpc_processor/coders/operations/operation_status.rs index 71ed2117..9ab480a8 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_status.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_status.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationStatusQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_supply_block.rs b/veilid-core/src/rpc_processor/coders/operations/operation_supply_block.rs index 67b7ab00..d593650d 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_supply_block.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_supply_block.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationSupplyBlockQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_validate_dial_info.rs b/veilid-core/src/rpc_processor/coders/operations/operation_validate_dial_info.rs index a11f2501..63a8bd40 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_validate_dial_info.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_validate_dial_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationValidateDialInfo { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_value_changed.rs b/veilid-core/src/rpc_processor/coders/operations/operation_value_changed.rs index c1847118..3d1c08cf 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_value_changed.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_value_changed.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationValueChanged { diff --git a/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs b/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs index cbb08fcb..00c0199c 100644 --- a/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs +++ b/veilid-core/src/rpc_processor/coders/operations/operation_watch_value.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub struct RPCOperationWatchValueQ { diff --git a/veilid-core/src/rpc_processor/coders/operations/question.rs b/veilid-core/src/rpc_processor/coders/operations/question.rs index 02995b26..4e7e3966 100644 --- a/veilid-core/src/rpc_processor/coders/operations/question.rs +++ b/veilid-core/src/rpc_processor/coders/operations/question.rs @@ -1,6 +1,4 @@ use super::*; -use crate::*; -use rpc_processor::*; #[derive(Debug, Clone)] pub struct RPCQuestion { diff --git a/veilid-core/src/rpc_processor/coders/operations/respond_to.rs b/veilid-core/src/rpc_processor/coders/operations/respond_to.rs index 79c4e358..f05b6d08 100644 --- a/veilid-core/src/rpc_processor/coders/operations/respond_to.rs +++ b/veilid-core/src/rpc_processor/coders/operations/respond_to.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; #[derive(Debug, Clone)] pub enum RespondTo { diff --git a/veilid-core/src/rpc_processor/coders/operations/statement.rs b/veilid-core/src/rpc_processor/coders/operations/statement.rs index 96c71a4c..1c2ddf59 100644 --- a/veilid-core/src/rpc_processor/coders/operations/statement.rs +++ b/veilid-core/src/rpc_processor/coders/operations/statement.rs @@ -1,6 +1,4 @@ use super::*; -use crate::*; -use rpc_processor::*; #[derive(Debug, Clone)] pub struct RPCStatement { @@ -20,12 +18,9 @@ impl RPCStatement { pub fn desc(&self) -> &'static str { self.detail.desc() } - pub fn decode( - reader: &veilid_capnp::statement::Reader, - opt_sender_node_id: Option<&DHTKey>, - ) -> Result { + pub fn decode(reader: &veilid_capnp::statement::Reader) -> Result { let d_reader = reader.get_detail(); - let detail = RPCStatementDetail::decode(&d_reader, opt_sender_node_id)?; + let detail = RPCStatementDetail::decode(&d_reader)?; Ok(RPCStatement { detail }) } pub fn encode(&self, builder: &mut veilid_capnp::statement::Builder) -> Result<(), RPCError> { @@ -38,7 +33,6 @@ impl RPCStatement { pub enum RPCStatementDetail { ValidateDialInfo(RPCOperationValidateDialInfo), Route(RPCOperationRoute), - NodeInfoUpdate(RPCOperationNodeInfoUpdate), ValueChanged(RPCOperationValueChanged), Signal(RPCOperationSignal), ReturnReceipt(RPCOperationReturnReceipt), @@ -50,7 +44,6 @@ impl RPCStatementDetail { match self { RPCStatementDetail::ValidateDialInfo(_) => "ValidateDialInfo", RPCStatementDetail::Route(_) => "Route", - RPCStatementDetail::NodeInfoUpdate(_) => "NodeInfoUpdate", RPCStatementDetail::ValueChanged(_) => "ValueChanged", RPCStatementDetail::Signal(_) => "Signal", RPCStatementDetail::ReturnReceipt(_) => "ReturnReceipt", @@ -59,7 +52,6 @@ impl RPCStatementDetail { } pub fn decode( reader: &veilid_capnp::statement::detail::Reader, - opt_sender_node_id: Option<&DHTKey>, ) -> Result { let which_reader = reader.which().map_err(RPCError::protocol)?; let out = match which_reader { @@ -73,11 +65,6 @@ impl RPCStatementDetail { let out = RPCOperationRoute::decode(&op_reader)?; RPCStatementDetail::Route(out) } - veilid_capnp::statement::detail::NodeInfoUpdate(r) => { - let op_reader = r.map_err(RPCError::protocol)?; - let out = RPCOperationNodeInfoUpdate::decode(&op_reader, opt_sender_node_id)?; - RPCStatementDetail::NodeInfoUpdate(out) - } veilid_capnp::statement::detail::ValueChanged(r) => { let op_reader = r.map_err(RPCError::protocol)?; let out = RPCOperationValueChanged::decode(&op_reader)?; @@ -110,9 +97,6 @@ impl RPCStatementDetail { d.encode(&mut builder.reborrow().init_validate_dial_info()) } RPCStatementDetail::Route(d) => d.encode(&mut builder.reborrow().init_route()), - RPCStatementDetail::NodeInfoUpdate(d) => { - d.encode(&mut builder.reborrow().init_node_info_update()) - } RPCStatementDetail::ValueChanged(d) => { d.encode(&mut builder.reborrow().init_value_changed()) } diff --git a/veilid-core/src/rpc_processor/coders/peer_info.rs b/veilid-core/src/rpc_processor/coders/peer_info.rs index 5c7e67ab..5844b8ab 100644 --- a/veilid-core/src/rpc_processor/coders/peer_info.rs +++ b/veilid-core/src/rpc_processor/coders/peer_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_peer_info( peer_info: &PeerInfo, diff --git a/veilid-core/src/rpc_processor/coders/protocol_type_set.rs b/veilid-core/src/rpc_processor/coders/protocol_type_set.rs index 37db7e19..6ebf71e7 100644 --- a/veilid-core/src/rpc_processor/coders/protocol_type_set.rs +++ b/veilid-core/src/rpc_processor/coders/protocol_type_set.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_protocol_type_set( protocol_type_set: &ProtocolTypeSet, diff --git a/veilid-core/src/rpc_processor/coders/sender_info.rs b/veilid-core/src/rpc_processor/coders/sender_info.rs index 5fbea344..45fd96c8 100644 --- a/veilid-core/src/rpc_processor/coders/sender_info.rs +++ b/veilid-core/src/rpc_processor/coders/sender_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_sender_info( sender_info: &SenderInfo, diff --git a/veilid-core/src/rpc_processor/coders/sequencing.rs b/veilid-core/src/rpc_processor/coders/sequencing.rs new file mode 100644 index 00000000..187762fc --- /dev/null +++ b/veilid-core/src/rpc_processor/coders/sequencing.rs @@ -0,0 +1,17 @@ +use super::*; + +pub fn encode_sequencing(sequencing: Sequencing) -> veilid_capnp::Sequencing { + match sequencing { + Sequencing::NoPreference => veilid_capnp::Sequencing::NoPreference, + Sequencing::PreferOrdered => veilid_capnp::Sequencing::PreferOrdered, + Sequencing::EnsureOrdered => veilid_capnp::Sequencing::EnsureOrdered, + } +} + +pub fn decode_sequencing(sequencing: veilid_capnp::Sequencing) -> Sequencing { + match sequencing { + veilid_capnp::Sequencing::NoPreference => Sequencing::NoPreference, + veilid_capnp::Sequencing::PreferOrdered => Sequencing::PreferOrdered, + veilid_capnp::Sequencing::EnsureOrdered => Sequencing::EnsureOrdered, + } +} diff --git a/veilid-core/src/rpc_processor/coders/signal_info.rs b/veilid-core/src/rpc_processor/coders/signal_info.rs index 7a272973..5e9edc84 100644 --- a/veilid-core/src/rpc_processor/coders/signal_info.rs +++ b/veilid-core/src/rpc_processor/coders/signal_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_signal_info( signal_info: &SignalInfo, diff --git a/veilid-core/src/rpc_processor/coders/signed_direct_node_info.rs b/veilid-core/src/rpc_processor/coders/signed_direct_node_info.rs index a04d3445..bca21bb5 100644 --- a/veilid-core/src/rpc_processor/coders/signed_direct_node_info.rs +++ b/veilid-core/src/rpc_processor/coders/signed_direct_node_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_signed_direct_node_info( signed_direct_node_info: &SignedDirectNodeInfo, @@ -11,7 +10,7 @@ pub fn encode_signed_direct_node_info( builder .reborrow() - .set_timestamp(signed_direct_node_info.timestamp); + .set_timestamp(signed_direct_node_info.timestamp.into()); let mut sig_builder = builder.reborrow().init_signature(); let Some(signature) = &signed_direct_node_info.signature else { @@ -37,7 +36,7 @@ pub fn decode_signed_direct_node_info( .get_signature() .map_err(RPCError::protocol)?; - let timestamp = reader.reborrow().get_timestamp(); + let timestamp = reader.reborrow().get_timestamp().into(); let signature = decode_signature(&sig_reader); diff --git a/veilid-core/src/rpc_processor/coders/signed_node_info.rs b/veilid-core/src/rpc_processor/coders/signed_node_info.rs index 64ae9c80..2af7cefd 100644 --- a/veilid-core/src/rpc_processor/coders/signed_node_info.rs +++ b/veilid-core/src/rpc_processor/coders/signed_node_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_signed_node_info( signed_node_info: &SignedNodeInfo, diff --git a/veilid-core/src/rpc_processor/coders/signed_relayed_node_info.rs b/veilid-core/src/rpc_processor/coders/signed_relayed_node_info.rs index 646f6597..21d3266b 100644 --- a/veilid-core/src/rpc_processor/coders/signed_relayed_node_info.rs +++ b/veilid-core/src/rpc_processor/coders/signed_relayed_node_info.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_signed_relayed_node_info( signed_relayed_node_info: &SignedRelayedNodeInfo, @@ -17,7 +16,7 @@ pub fn encode_signed_relayed_node_info( builder .reborrow() - .set_timestamp(signed_relayed_node_info.timestamp); + .set_timestamp(signed_relayed_node_info.timestamp.into()); let mut sig_builder = builder.reborrow().init_signature(); encode_signature(&signed_relayed_node_info.signature, &mut sig_builder); @@ -51,7 +50,7 @@ pub fn decode_signed_relayed_node_info( .reborrow() .get_signature() .map_err(RPCError::protocol)?; - let timestamp = reader.reborrow().get_timestamp(); + let timestamp = reader.reborrow().get_timestamp().into(); let signature = decode_signature(&sig_reader); diff --git a/veilid-core/src/rpc_processor/coders/socket_address.rs b/veilid-core/src/rpc_processor/coders/socket_address.rs index 3a9c2a42..41542550 100644 --- a/veilid-core/src/rpc_processor/coders/socket_address.rs +++ b/veilid-core/src/rpc_processor/coders/socket_address.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_socket_address( socket_address: &SocketAddress, diff --git a/veilid-core/src/rpc_processor/coders/tunnel.rs b/veilid-core/src/rpc_processor/coders/tunnel.rs index da12781c..994cd0da 100644 --- a/veilid-core/src/rpc_processor/coders/tunnel.rs +++ b/veilid-core/src/rpc_processor/coders/tunnel.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_tunnel_mode(tunnel_mode: TunnelMode) -> veilid_capnp::TunnelEndpointMode { match tunnel_mode { @@ -59,8 +58,8 @@ pub fn encode_full_tunnel( full_tunnel: &FullTunnel, builder: &mut veilid_capnp::full_tunnel::Builder, ) -> Result<(), RPCError> { - builder.set_id(full_tunnel.id); - builder.set_timeout(full_tunnel.timeout); + builder.set_id(full_tunnel.id.as_u64()); + builder.set_timeout(full_tunnel.timeout.as_u64()); let mut l_builder = builder.reborrow().init_local(); encode_tunnel_endpoint(&full_tunnel.local, &mut l_builder)?; let mut r_builder = builder.reborrow().init_remote(); @@ -71,8 +70,8 @@ pub fn encode_full_tunnel( pub fn decode_full_tunnel( reader: &veilid_capnp::full_tunnel::Reader, ) -> Result { - let id = reader.get_id(); - let timeout = reader.get_timeout(); + let id = TunnelId::new(reader.get_id()); + let timeout = TimestampDuration::new(reader.get_timeout()); let l_reader = reader.get_local().map_err(RPCError::protocol)?; let local = decode_tunnel_endpoint(&l_reader)?; let r_reader = reader.get_remote().map_err(RPCError::protocol)?; @@ -90,8 +89,8 @@ pub fn encode_partial_tunnel( partial_tunnel: &PartialTunnel, builder: &mut veilid_capnp::partial_tunnel::Builder, ) -> Result<(), RPCError> { - builder.set_id(partial_tunnel.id); - builder.set_timeout(partial_tunnel.timeout); + builder.set_id(partial_tunnel.id.as_u64()); + builder.set_timeout(partial_tunnel.timeout.as_u64()); let mut l_builder = builder.reborrow().init_local(); encode_tunnel_endpoint(&partial_tunnel.local, &mut l_builder)?; Ok(()) @@ -100,8 +99,8 @@ pub fn encode_partial_tunnel( pub fn decode_partial_tunnel( reader: &veilid_capnp::partial_tunnel::Reader, ) -> Result { - let id = reader.get_id(); - let timeout = reader.get_timeout(); + let id = TunnelId::new(reader.get_id()); + let timeout = TimestampDuration::new(reader.get_timeout()); let l_reader = reader.get_local().map_err(RPCError::protocol)?; let local = decode_tunnel_endpoint(&l_reader)?; diff --git a/veilid-core/src/rpc_processor/coders/value_data.rs b/veilid-core/src/rpc_processor/coders/value_data.rs index cd3ad900..ba859423 100644 --- a/veilid-core/src/rpc_processor/coders/value_data.rs +++ b/veilid-core/src/rpc_processor/coders/value_data.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_value_data( value_data: &ValueData, diff --git a/veilid-core/src/rpc_processor/coders/value_key.rs b/veilid-core/src/rpc_processor/coders/value_key.rs index 7561b3c6..fcb1c0a2 100644 --- a/veilid-core/src/rpc_processor/coders/value_key.rs +++ b/veilid-core/src/rpc_processor/coders/value_key.rs @@ -1,5 +1,4 @@ -use crate::*; -use rpc_processor::*; +use super::*; pub fn encode_value_key( value_key: &ValueKey, diff --git a/veilid-core/src/rpc_processor/destination.rs b/veilid-core/src/rpc_processor/destination.rs index d7a10d0b..f5c7069f 100644 --- a/veilid-core/src/rpc_processor/destination.rs +++ b/veilid-core/src/rpc_processor/destination.rs @@ -205,7 +205,7 @@ impl RPCProcessor { private_route, safety_selection, } => { - let PrivateRouteHops::FirstHop(pr_first_hop) = &private_route.hops else { + let Some(avoid_node_id) = private_route.first_hop_node_id() else { return Err(RPCError::internal("destination private route must have first hop")); }; @@ -217,10 +217,19 @@ impl RPCProcessor { let route_node = match rss .has_remote_private_route_seen_our_node_info(&private_route.public_key) { - true => RouteNode::NodeId(NodeId::new(routing_table.node_id())), - false => RouteNode::PeerInfo( - routing_table.get_own_peer_info(RoutingDomain::PublicInternet), - ), + true => { + if !routing_table.has_valid_own_node_info(RoutingDomain::PublicInternet) { + return Ok(NetworkResult::no_connection_other("Own node info must be valid to use private route")); + } + RouteNode::NodeId(NodeId::new(routing_table.node_id())) + } + false => { + let Some(own_peer_info) = + routing_table.get_own_peer_info(RoutingDomain::PublicInternet) else { + return Ok(NetworkResult::no_connection_other("Own peer info must be valid to use private route")); + }; + RouteNode::PeerInfo(own_peer_info) + }, }; Ok(NetworkResult::value(RespondTo::PrivateRoute( @@ -228,17 +237,23 @@ impl RPCProcessor { ))) } SafetySelection::Safe(safety_spec) => { - // Sent directly but with a safety route, respond to private route - let avoid_node_id = match &pr_first_hop.node { - RouteNode::NodeId(n) => n.key, - RouteNode::PeerInfo(p) => p.node_id.key, - }; + // Sent to a private route via a safety route, respond to private route - let Some(pr_key) = rss - .get_private_route_for_safety_spec(safety_spec, &[avoid_node_id]) - .map_err(RPCError::internal)? else { - return Ok(NetworkResult::no_connection_other("no private route for response at this time")); - }; + // Check for loopback test + let pr_key = if safety_spec.preferred_route + == Some(private_route.public_key) + { + // Private route is also safety route during loopback test + private_route.public_key + } else { + // Get the privat route to respond to that matches the safety route spec we sent the request with + let Some(pr_key) = rss + .get_private_route_for_safety_spec(safety_spec, &[avoid_node_id]) + .map_err(RPCError::internal)? else { + return Ok(NetworkResult::no_connection_other("no private route for response at this time")); + }; + pr_key + }; // Get the assembled route for response let private_route = rss diff --git a/veilid-core/src/rpc_processor/mod.rs b/veilid-core/src/rpc_processor/mod.rs index 1a5c1781..eebcd017 100644 --- a/veilid-core/src/rpc_processor/mod.rs +++ b/veilid-core/src/rpc_processor/mod.rs @@ -9,7 +9,6 @@ mod rpc_error; mod rpc_find_block; mod rpc_find_node; mod rpc_get_value; -mod rpc_node_info_update; mod rpc_return_receipt; mod rpc_route; mod rpc_set_value; @@ -28,8 +27,8 @@ pub use rpc_error::*; pub use rpc_status::*; use super::*; + use crate::crypto::*; -use crate::xx::*; use futures_util::StreamExt; use network_manager::*; use receipt_manager::*; @@ -38,8 +37,6 @@ use stop_token::future::FutureExt; ///////////////////////////////////////////////////////////////////// -type OperationId = u64; - #[derive(Debug, Clone)] struct RPCMessageHeaderDetailDirect { /// The decoded header of the envelope @@ -55,6 +52,8 @@ struct RPCMessageHeaderDetailDirect { /// Header details for rpc messages received over only a safety route but not a private route #[derive(Debug, Clone)] struct RPCMessageHeaderDetailSafetyRouted { + /// Remote safety route used + remote_safety_route: DHTKey, /// The sequencing used for this route sequencing: Sequencing, } @@ -62,6 +61,8 @@ struct RPCMessageHeaderDetailSafetyRouted { /// Header details for rpc messages received over a private route #[derive(Debug, Clone)] struct RPCMessageHeaderDetailPrivateRouted { + /// Remote safety route used (or possibly node id the case of no safety route) + remote_safety_route: DHTKey, /// The private route we received the rpc over private_route: DHTKey, // The safety spec for replying to this private routed rpc @@ -79,9 +80,9 @@ enum RPCMessageHeaderDetail { #[derive(Debug, Clone)] struct RPCMessageHeader { /// Time the message was received, not sent - timestamp: u64, + timestamp: Timestamp, /// The length in bytes of the rpc message body - body_len: u64, + body_len: ByteCount, /// The header detail depending on which way the message was received detail: RPCMessageHeaderDetail, } @@ -109,16 +110,6 @@ impl RPCMessageData { } } -// impl ReaderSegments for RPCMessageData { -// fn get_segment(&self, idx: u32) -> Option<&[u8]> { -// if idx > 0 { -// None -// } else { -// Some(self.contents.as_slice()) -// } -// } -// } - #[derive(Debug)] struct RPCMessageEncoded { header: RPCMessageHeader, @@ -141,54 +132,81 @@ where .map_err(RPCError::protocol) .map_err(logthru_rpc!())?; Ok(buffer) - // let wordvec = builder - // .into_reader() - // .canonicalize() - // .map_err(RPCError::protocol) - // .map_err(logthru_rpc!())?; - // Ok(capnp::Word::words_to_bytes(wordvec.as_slice()).to_vec()) } -// fn reader_to_vec<'a, T>(reader: &capnp::message::Reader) -> Result, RPCError> -// where -// T: capnp::message::ReaderSegments + 'a, -// { -// let wordvec = reader -// .canonicalize() -// .map_err(RPCError::protocol) -// .map_err(logthru_rpc!())?; -// Ok(capnp::Word::words_to_bytes(wordvec.as_slice()).to_vec()) -// } - #[derive(Debug)] struct WaitableReply { - dest: Destination, handle: OperationWaitHandle, - timeout: u64, + timeout_us: TimestampDuration, node_ref: NodeRef, - send_ts: u64, + send_ts: Timestamp, send_data_kind: SendDataKind, + safety_route: Option, + remote_private_route: Option, + reply_private_route: Option, } ///////////////////////////////////////////////////////////////////// #[derive(Clone, Debug, Default)] pub struct Answer { - pub latency: u64, // how long it took to get this answer - pub answer: T, // the answer itself + pub latency: TimestampDuration, // how long it took to get this answer + pub answer: T, // the answer itself } impl Answer { - pub fn new(latency: u64, answer: T) -> Self { + pub fn new(latency: TimestampDuration, answer: T) -> Self { Self { latency, answer } } } +/// An operation that has been fully prepared for envelope r struct RenderedOperation { - message: Vec, // The rendered operation bytes - node_id: DHTKey, // Destination node id we're sending to - node_ref: NodeRef, // Node to send envelope to (may not be destination node id in case of relay) - hop_count: usize, // Total safety + private route hop count + 1 hop for the initial send + /// The rendered operation bytes + message: Vec, + /// Destination node id we're sending to + node_id: DHTKey, + /// Node to send envelope to (may not be destination node id in case of relay) + node_ref: NodeRef, + /// Total safety + private route hop count + 1 hop for the initial send + hop_count: usize, + /// The safety route used to send the message + safety_route: Option, + /// The private route used to send the message + remote_private_route: Option, + /// The private route requested to receive the reply + reply_private_route: Option, } + +/// Node information exchanged during every RPC message +#[derive(Default, Debug, Clone)] +pub struct SenderSignedNodeInfo { + /// The current signed node info of the sender if required + signed_node_info: Option, + /// The last timestamp of the target's node info to assist remote node with sending its latest node info + target_node_info_ts: Timestamp, +} +impl SenderSignedNodeInfo { + pub fn new_no_sni(target_node_info_ts: Timestamp) -> Self { + Self { + signed_node_info: None, + target_node_info_ts, + } + } + pub fn new(sender_signed_node_info: SignedNodeInfo, target_node_info_ts: Timestamp) -> Self { + Self { + signed_node_info: Some(sender_signed_node_info), + target_node_info_ts, + } + } +} + +#[derive(Copy, Clone, Debug)] +enum RPCKind { + Question, + Statement, + Answer, +} + ///////////////////////////////////////////////////////////////////// pub struct RPCProcessorInner { @@ -198,7 +216,7 @@ pub struct RPCProcessorInner { } pub struct RPCProcessorUnlockedInner { - timeout: u64, + timeout_us: TimestampDuration, queue_size: u32, concurrency: u32, max_route_hop_count: usize, @@ -236,10 +254,10 @@ impl RPCProcessor { // set up channel let mut concurrency = c.network.rpc.concurrency; let queue_size = c.network.rpc.queue_size; - let timeout = ms_to_us(c.network.rpc.timeout_ms); + let timeout_us = TimestampDuration::new(ms_to_us(c.network.rpc.timeout_ms)); let max_route_hop_count = c.network.rpc.max_route_hop_count as usize; if concurrency == 0 { - concurrency = intf::get_concurrency() / 2; + concurrency = get_concurrency() / 2; if concurrency == 0 { concurrency = 1; } @@ -247,7 +265,7 @@ impl RPCProcessor { let validate_dial_info_receipt_time_ms = c.network.dht.validate_dial_info_receipt_time_ms; RPCProcessorUnlockedInner { - timeout, + timeout_us, queue_size, concurrency, max_route_hop_count, @@ -296,7 +314,7 @@ impl RPCProcessor { for _ in 0..self.unlocked_inner.concurrency { let this = self.clone(); let receiver = channel.1.clone(); - let jh = intf::spawn(Self::rpc_worker( + let jh = spawn(Self::rpc_worker( this, inner.stop_source.as_ref().unwrap().token(), receiver, @@ -425,72 +443,58 @@ impl RPCProcessor { async fn wait_for_reply( &self, waitable_reply: WaitableReply, - ) -> Result, RPCError> { + ) -> Result, RPCError> { let out = self .unlocked_inner .waiting_rpc_table - .wait_for_op(waitable_reply.handle, waitable_reply.timeout) + .wait_for_op(waitable_reply.handle, waitable_reply.timeout_us) .await; match &out { Err(_) | Ok(TimeoutOr::Timeout) => { - waitable_reply.node_ref.stats_question_lost(); + self.record_question_lost( + waitable_reply.send_ts, + waitable_reply.node_ref.clone(), + waitable_reply.safety_route, + waitable_reply.remote_private_route, + waitable_reply.reply_private_route, + ); } Ok(TimeoutOr::Value((rpcreader, _))) => { // Reply received - let recv_ts = intf::get_timestamp(); - waitable_reply.node_ref.stats_answer_rcvd( + let recv_ts = get_aligned_timestamp(); + + // Record answer received + self.record_answer_received( waitable_reply.send_ts, recv_ts, rpcreader.header.body_len, - ); - // Process private route replies - if let Destination::PrivateRoute { - private_route, - safety_selection, - } = &waitable_reply.dest - { - let rss = self.routing_table.route_spec_store(); - - // If we received a reply from a private route, mark it as such - if let Err(e) = - rss.mark_remote_private_route_replied(&private_route.public_key, recv_ts) - { - log_rpc!(error "private route missing: {}", e); - } - - // If we sent to a private route without a safety route - // We need to mark our own node info as having been seen so we can optimize sending it - if let SafetySelection::Unsafe(_) = safety_selection { - if let Err(e) = rss.mark_remote_private_route_seen_our_node_info( - &private_route.public_key, - recv_ts, - ) { - log_rpc!(error "private route missing: {}", e); - } - } - } + waitable_reply.node_ref.clone(), + waitable_reply.safety_route, + waitable_reply.remote_private_route, + waitable_reply.reply_private_route, + ) } }; - out } - // Wrap an operation with a private route inside a safety route + /// Wrap an operation with a private route inside a safety route fn wrap_with_route( &self, safety_selection: SafetySelection, - private_route: PrivateRoute, + remote_private_route: PrivateRoute, + reply_private_route: Option, message_data: Vec, ) -> Result, RPCError> { let routing_table = self.routing_table(); let rss = routing_table.route_spec_store(); - - let pr_hop_count = private_route.hop_count; - let pr_pubkey = private_route.public_key; + let pr_is_stub = remote_private_route.is_stub(); + let pr_hop_count = remote_private_route.hop_count; + let pr_pubkey = remote_private_route.public_key; // Compile the safety route with the private route let compiled_route: CompiledRoute = match rss - .compile_safety_route(safety_selection, private_route) + .compile_safety_route(safety_selection, remote_private_route) .map_err(RPCError::internal)? { Some(cr) => cr, @@ -500,6 +504,8 @@ impl RPCProcessor { )) } }; + let sr_is_stub = compiled_route.safety_route.is_stub(); + let sr_pubkey = compiled_route.safety_route.public_key; // Encrypt routed operation // Xmsg + ENC(Xmsg, DH(PKapr, SKbsr)) @@ -514,7 +520,12 @@ impl RPCProcessor { // Make the routed operation // xxx: replace MAX_CRYPTO_VERSION with the version from the factory - let operation = RoutedOperation::new(MAX_CRYPTO_VERSION, nonce, enc_msg_data); + let operation = RoutedOperation::new( + MAX_CRYPTO_VERSION, + safety_selection.get_sequencing(), + nonce, + enc_msg_data, + ); // Prepare route operation let sr_hop_count = compiled_route.safety_route.hop_count; @@ -522,9 +533,11 @@ impl RPCProcessor { safety_route: compiled_route.safety_route, operation, }; + let ssni_route = self + .get_sender_signed_node_info(&Destination::direct(compiled_route.first_hop.clone()))?; let operation = RPCOperation::new_statement( RPCStatement::new(RPCStatementDetail::Route(route_operation)), - None, + ssni_route, ); // Convert message to bytes and return it @@ -542,6 +555,9 @@ impl RPCProcessor { node_id: out_node_id, node_ref: compiled_route.first_hop, hop_count: out_hop_count, + safety_route: if sr_is_stub { None } else { Some(sr_pubkey) }, + remote_private_route: if pr_is_stub { None } else { Some(pr_pubkey) }, + reply_private_route, }; Ok(NetworkResult::value(out)) @@ -567,6 +583,15 @@ impl RPCProcessor { builder_to_vec(msg_builder)? }; + // Get reply private route if we are asking for one to be used in our 'respond to' + let reply_private_route = match operation.kind() { + RPCOperationKind::Question(q) => match q.respond_to() { + RespondTo::Sender => None, + RespondTo::PrivateRoute(pr) => Some(pr.public_key), + }, + RPCOperationKind::Statement(_) | RPCOperationKind::Answer(_) => None, + }; + // To where are we sending the request match dest { Destination::Direct { @@ -603,6 +628,9 @@ impl RPCProcessor { node_ref.set_sequencing(sequencing) } + // Reply private route should be None here, even for questions + assert!(reply_private_route.is_none()); + // If no safety route is being used, and we're not sending to a private // route, we can use a direct envelope instead of routing out = NetworkResult::value(RenderedOperation { @@ -610,6 +638,9 @@ impl RPCProcessor { node_id, node_ref, hop_count: 1, + safety_route: None, + remote_private_route: None, + reply_private_route: None, }); } SafetySelection::Safe(_) => { @@ -628,7 +659,12 @@ impl RPCProcessor { PrivateRoute::new_stub(node_id, RouteNode::PeerInfo(peer_info)); // Wrap with safety route - out = self.wrap_with_route(safety_selection, private_route, message)?; + out = self.wrap_with_route( + safety_selection, + private_route, + reply_private_route, + message, + )?; } }; } @@ -639,89 +675,339 @@ impl RPCProcessor { // Send to private route // --------------------- // Reply with 'route' operation - out = self.wrap_with_route(safety_selection, private_route, message)?; + out = self.wrap_with_route( + safety_selection, + private_route, + reply_private_route, + message, + )?; } } Ok(out) } - // Get signed node info to package with RPC messages to improve - // routing table caching when it is okay to do so - // This is only done in the PublicInternet routing domain because - // as far as we can tell this is the only domain that will really benefit - fn get_sender_signed_node_info(&self, dest: &Destination) -> Option { + /// Get signed node info to package with RPC messages to improve + /// routing table caching when it is okay to do so + #[instrument(level = "trace", skip(self), ret, err)] + fn get_sender_signed_node_info( + &self, + dest: &Destination, + ) -> Result { // Don't do this if the sender is to remain private // Otherwise we would be attaching the original sender's identity to the final destination, // thus defeating the purpose of the safety route entirely :P match dest.get_safety_selection() { SafetySelection::Unsafe(_) => {} SafetySelection::Safe(_) => { - return None; + return Ok(SenderSignedNodeInfo::default()); } } - // Don't do this if our own signed node info isn't valid yet - let routing_table = self.routing_table(); - if !routing_table.has_valid_own_node_info(RoutingDomain::PublicInternet) { - return None; - } - match dest { + // Get the target we're sending to + let routing_table = self.routing_table(); + let target = match dest { Destination::Direct { target, safety_selection: _, - } => { - // If the target has seen our node info already don't do this - if target.has_seen_our_node_info(RoutingDomain::PublicInternet) { - return None; - } - Some( - routing_table - .get_own_peer_info(RoutingDomain::PublicInternet) - .signed_node_info, - ) - } + } => target.clone(), Destination::Relay { relay: _, target, safety_selection: _, } => { if let Some(target) = routing_table.lookup_node_ref(*target) { - if target.has_seen_our_node_info(RoutingDomain::PublicInternet) { - return None; - } - Some( - routing_table - .get_own_peer_info(RoutingDomain::PublicInternet) - .signed_node_info, - ) + target } else { - None + // Target was not in our routing table + return Ok(SenderSignedNodeInfo::default()); } } Destination::PrivateRoute { private_route: _, safety_selection: _, - } => None, + } => { + return Ok(SenderSignedNodeInfo::default()); + } + }; + + let Some(routing_domain) = target.best_routing_domain() else { + // No routing domain for target? + return Err(RPCError::internal(format!("No routing domain for target: {}", target))); + }; + + // Get the target's node info timestamp + let target_node_info_ts = target.node_info_ts(routing_domain); + + // Don't return our node info if it's not valid yet + let Some(own_peer_info) = routing_table.get_own_peer_info(routing_domain) else { + return Ok(SenderSignedNodeInfo::new_no_sni(target_node_info_ts)); + }; + + // Get our node info timestamp + let our_node_info_ts = own_peer_info.signed_node_info.timestamp(); + + // If the target has seen our node info already don't send it again + if target.has_seen_our_node_info_ts(routing_domain, our_node_info_ts) { + return Ok(SenderSignedNodeInfo::new_no_sni(target_node_info_ts)); + } + + Ok(SenderSignedNodeInfo::new( + own_peer_info.signed_node_info, + target_node_info_ts, + )) + } + + /// Record failure to send to node or route + fn record_send_failure( + &self, + rpc_kind: RPCKind, + send_ts: Timestamp, + node_ref: NodeRef, + safety_route: Option, + remote_private_route: Option, + ) { + let wants_answer = matches!(rpc_kind, RPCKind::Question); + + // Record for node if this was not sent via a route + if safety_route.is_none() && remote_private_route.is_none() { + node_ref.stats_failed_to_send(send_ts, wants_answer); + return; + } + + // If safety route was in use, record failure to send there + if let Some(sr_pubkey) = &safety_route { + let rss = self.routing_table.route_spec_store(); + rss.with_route_stats(send_ts, sr_pubkey, |s| s.record_send_failed()); + } else { + // If no safety route was in use, then it's the private route's fault if we have one + if let Some(pr_pubkey) = &remote_private_route { + let rss = self.routing_table.route_spec_store(); + rss.with_route_stats(send_ts, pr_pubkey, |s| s.record_send_failed()); + } } } - // Issue a question over the network, possibly using an anonymized route + /// Record question lost to node or route + fn record_question_lost( + &self, + send_ts: Timestamp, + node_ref: NodeRef, + safety_route: Option, + remote_private_route: Option, + private_route: Option, + ) { + // Record for node if this was not sent via a route + if safety_route.is_none() && remote_private_route.is_none() { + node_ref.stats_question_lost(); + return; + } + // Get route spec store + let rss = self.routing_table.route_spec_store(); + + // If safety route was used, record question lost there + if let Some(sr_pubkey) = &safety_route { + let rss = self.routing_table.route_spec_store(); + rss.with_route_stats(send_ts, sr_pubkey, |s| { + s.record_question_lost(); + }); + } + // If remote private route was used, record question lost there + if let Some(rpr_pubkey) = &remote_private_route { + rss.with_route_stats(send_ts, rpr_pubkey, |s| { + s.record_question_lost(); + }); + } + // If private route was used, record question lost there + if let Some(pr_pubkey) = &private_route { + rss.with_route_stats(send_ts, pr_pubkey, |s| { + s.record_question_lost(); + }); + } + } + + /// Record success sending to node or route + fn record_send_success( + &self, + rpc_kind: RPCKind, + send_ts: Timestamp, + bytes: ByteCount, + node_ref: NodeRef, + safety_route: Option, + remote_private_route: Option, + ) { + let wants_answer = matches!(rpc_kind, RPCKind::Question); + + // Record for node if this was not sent via a route + if safety_route.is_none() && remote_private_route.is_none() { + node_ref.stats_question_sent(send_ts, bytes, wants_answer); + return; + } + + // Get route spec store + let rss = self.routing_table.route_spec_store(); + + // If safety route was used, record send there + if let Some(sr_pubkey) = &safety_route { + rss.with_route_stats(send_ts, sr_pubkey, |s| { + s.record_sent(send_ts, bytes); + }); + } + + // If remote private route was used, record send there + if let Some(pr_pubkey) = &remote_private_route { + let rss = self.routing_table.route_spec_store(); + rss.with_route_stats(send_ts, pr_pubkey, |s| { + s.record_sent(send_ts, bytes); + }); + } + } + + /// Record answer received from node or route + fn record_answer_received( + &self, + send_ts: Timestamp, + recv_ts: Timestamp, + bytes: ByteCount, + node_ref: NodeRef, + safety_route: Option, + remote_private_route: Option, + reply_private_route: Option, + ) { + // Record stats for remote node if this was direct + if safety_route.is_none() && remote_private_route.is_none() && reply_private_route.is_none() + { + node_ref.stats_answer_rcvd(send_ts, recv_ts, bytes); + return; + } + // Get route spec store + let rss = self.routing_table.route_spec_store(); + + // Get latency for all local routes + let mut total_local_latency = TimestampDuration::new(0u64); + let total_latency: TimestampDuration = recv_ts.saturating_sub(send_ts); + + // If safety route was used, record route there + if let Some(sr_pubkey) = &safety_route { + rss.with_route_stats(send_ts, sr_pubkey, |s| { + // If we received an answer, the safety route we sent over can be considered tested + s.record_tested(recv_ts); + + // If we used a safety route to send, use our last tested latency + total_local_latency += s.latency_stats().average + }); + } + + // If local private route was used, record route there + if let Some(pr_pubkey) = &reply_private_route { + rss.with_route_stats(send_ts, pr_pubkey, |s| { + // Record received bytes + s.record_received(recv_ts, bytes); + + // If we used a private route to receive, use our last tested latency + total_local_latency += s.latency_stats().average + }); + } + + // If remote private route was used, record there + if let Some(rpr_pubkey) = &remote_private_route { + rss.with_route_stats(send_ts, rpr_pubkey, |s| { + // Record received bytes + s.record_received(recv_ts, bytes); + + // The remote route latency is recorded using the total latency minus the total local latency + let remote_latency = total_latency.saturating_sub(total_local_latency); + s.record_latency(remote_latency); + }); + + // If we sent to a private route without a safety route + // We need to mark our own node info as having been seen so we can optimize sending it + if let Err(e) = rss.mark_remote_private_route_seen_our_node_info(&rpr_pubkey, recv_ts) { + log_rpc!(error "private route missing: {}", e); + } + + // We can't record local route latency if a remote private route was used because + // there is no way other than the prior latency estimation to determine how much time was spent + // in the remote private route + // Instead, we rely on local route testing to give us latency numbers for our local routes + } else { + // If no remote private route was used, then record half the total latency on our local routes + // This is fine because if we sent with a local safety route, + // then we must have received with a local private route too, per the design rules + if let Some(sr_pubkey) = &safety_route { + let rss = self.routing_table.route_spec_store(); + rss.with_route_stats(send_ts, sr_pubkey, |s| { + s.record_latency(total_latency / 2u64); + }); + } + if let Some(pr_pubkey) = &reply_private_route { + rss.with_route_stats(send_ts, pr_pubkey, |s| { + s.record_latency(total_latency / 2u64); + }); + } + } + } + + /// Record question or statement received from node or route + fn record_question_received(&self, msg: &RPCMessage) { + let recv_ts = msg.header.timestamp; + let bytes = msg.header.body_len; + + // Process messages based on how they were received + match &msg.header.detail { + // Process direct messages + RPCMessageHeaderDetail::Direct(_) => { + if let Some(sender_nr) = msg.opt_sender_nr.clone() { + sender_nr.stats_question_rcvd(recv_ts, bytes); + return; + } + } + // Process messages that arrived with no private route (private route stub) + RPCMessageHeaderDetail::SafetyRouted(d) => { + let rss = self.routing_table.route_spec_store(); + + // This may record nothing if the remote safety route is not also + // a remote private route that been imported, but that's okay + rss.with_route_stats(recv_ts, &d.remote_safety_route, |s| { + s.record_received(recv_ts, bytes); + }); + } + // Process messages that arrived to our private route + RPCMessageHeaderDetail::PrivateRouted(d) => { + let rss = self.routing_table.route_spec_store(); + + // This may record nothing if the remote safety route is not also + // a remote private route that been imported, but that's okay + // it could also be a node id if no remote safety route was used + // in which case this also will do nothing + rss.with_route_stats(recv_ts, &d.remote_safety_route, |s| { + s.record_received(recv_ts, bytes); + }); + + // Record for our local private route we received over + rss.with_route_stats(recv_ts, &d.private_route, |s| { + s.record_received(recv_ts, bytes); + }); + } + } + } + + /// Issue a question over the network, possibly using an anonymized route #[instrument(level = "debug", skip(self, question), err)] async fn question( &self, dest: Destination, question: RPCQuestion, ) -> Result, RPCError> { - // Get sender info if we should send that - let opt_sender_info = self.get_sender_signed_node_info(&dest); + // Get sender signed node info if we should send that + let ssni = self.get_sender_signed_node_info(&dest)?; // Wrap question in operation - let operation = RPCOperation::new_question(question, opt_sender_info); + let operation = RPCOperation::new_question(question, ssni); let op_id = operation.op_id(); // Log rpc send - trace!(target: "rpc_message", dir = "send", kind = "question", op_id, desc = operation.kind().desc(), ?dest); + trace!(target: "rpc_message", dir = "send", kind = "question", op_id = op_id.as_u64(), desc = operation.kind().desc(), ?dest); // Produce rendered operation let RenderedOperation { @@ -729,59 +1015,55 @@ impl RPCProcessor { node_id, node_ref, hop_count, + safety_route, + remote_private_route, + reply_private_route, } = network_result_try!(self.render_operation(dest.clone(), &operation)?); // Calculate answer timeout // Timeout is number of hops times the timeout per hop - let timeout = self.unlocked_inner.timeout * (hop_count as u64); + let timeout_us = self.unlocked_inner.timeout_us * (hop_count as u64); // Set up op id eventual let handle = self.unlocked_inner.waiting_rpc_table.add_op_waiter(op_id); // Send question - let bytes = message.len() as u64; - let send_ts = intf::get_timestamp(); + let bytes: ByteCount = (message.len() as u64).into(); + let send_ts = get_aligned_timestamp(); let send_data_kind = network_result_try!(self .network_manager() .send_envelope(node_ref.clone(), Some(node_id), message) .await .map_err(|e| { // If we're returning an error, clean up - node_ref - .stats_failed_to_send(send_ts, true); + self.record_send_failure(RPCKind::Question, send_ts, node_ref.clone(), safety_route, remote_private_route); RPCError::network(e) })? => { // If we couldn't send we're still cleaning up - node_ref - .stats_failed_to_send(send_ts, true); + self.record_send_failure(RPCKind::Question, send_ts, node_ref.clone(), safety_route, remote_private_route); } ); // Successfully sent - node_ref.stats_question_sent(send_ts, bytes, true); - - // Private route stats - if let Destination::PrivateRoute { - private_route, - safety_selection: _, - } = &dest - { - let rss = self.routing_table.route_spec_store(); - if let Err(e) = - rss.mark_remote_private_route_used(&private_route.public_key, intf::get_timestamp()) - { - log_rpc!(error "private route missing: {}", e); - } - } + self.record_send_success( + RPCKind::Question, + send_ts, + bytes, + node_ref.clone(), + safety_route, + remote_private_route, + ); // Pass back waitable reply completion Ok(NetworkResult::value(WaitableReply { - dest, handle, - timeout, + timeout_us, node_ref, send_ts, send_data_kind, + safety_route, + remote_private_route, + reply_private_route, })) } @@ -792,14 +1074,14 @@ impl RPCProcessor { dest: Destination, statement: RPCStatement, ) -> Result, RPCError> { - // Get sender info if we should send that - let opt_sender_info = self.get_sender_signed_node_info(&dest); + // Get sender signed node info if we should send that + let ssni = self.get_sender_signed_node_info(&dest)?; // Wrap statement in operation - let operation = RPCOperation::new_statement(statement, opt_sender_info); + let operation = RPCOperation::new_statement(statement, ssni); // Log rpc send - trace!(target: "rpc_message", dir = "send", kind = "statement", op_id = operation.op_id(), desc = operation.kind().desc(), ?dest); + trace!(target: "rpc_message", dir = "send", kind = "statement", op_id = operation.op_id().as_u64(), desc = operation.kind().desc(), ?dest); // Produce rendered operation let RenderedOperation { @@ -807,29 +1089,37 @@ impl RPCProcessor { node_id, node_ref, hop_count: _, + safety_route, + remote_private_route, + reply_private_route: _, } = network_result_try!(self.render_operation(dest, &operation)?); // Send statement - let bytes = message.len() as u64; - let send_ts = intf::get_timestamp(); + let bytes: ByteCount = (message.len() as u64).into(); + let send_ts = get_aligned_timestamp(); let _send_data_kind = network_result_try!(self .network_manager() .send_envelope(node_ref.clone(), Some(node_id), message) .await .map_err(|e| { // If we're returning an error, clean up - node_ref - .stats_failed_to_send(send_ts, true); + self.record_send_failure(RPCKind::Statement, send_ts, node_ref.clone(), safety_route, remote_private_route); RPCError::network(e) })? => { // If we couldn't send we're still cleaning up - node_ref - .stats_failed_to_send(send_ts, true); + self.record_send_failure(RPCKind::Statement, send_ts, node_ref.clone(), safety_route, remote_private_route); } ); // Successfully sent - node_ref.stats_question_sent(send_ts, bytes, true); + self.record_send_success( + RPCKind::Statement, + send_ts, + bytes, + node_ref, + safety_route, + remote_private_route, + ); Ok(NetworkResult::value(())) } @@ -845,14 +1135,14 @@ impl RPCProcessor { // Extract destination from respond_to let dest = network_result_try!(self.get_respond_to_destination(&request)); - // Get sender info if we should send that - let opt_sender_info = self.get_sender_signed_node_info(&dest); + // Get sender signed node info if we should send that + let ssni = self.get_sender_signed_node_info(&dest)?; // Wrap answer in operation - let operation = RPCOperation::new_answer(&request.operation, answer, opt_sender_info); + let operation = RPCOperation::new_answer(&request.operation, answer, ssni); // Log rpc send - trace!(target: "rpc_message", dir = "send", kind = "answer", op_id = operation.op_id(), desc = operation.kind().desc(), ?dest); + trace!(target: "rpc_message", dir = "send", kind = "answer", op_id = operation.op_id().as_u64(), desc = operation.kind().desc(), ?dest); // Produce rendered operation let RenderedOperation { @@ -860,28 +1150,36 @@ impl RPCProcessor { node_id, node_ref, hop_count: _, + safety_route, + remote_private_route, + reply_private_route: _, } = network_result_try!(self.render_operation(dest, &operation)?); // Send the reply - let bytes = message.len() as u64; - let send_ts = intf::get_timestamp(); + let bytes: ByteCount = (message.len() as u64).into(); + let send_ts = get_aligned_timestamp(); network_result_try!(self.network_manager() .send_envelope(node_ref.clone(), Some(node_id), message) .await .map_err(|e| { // If we're returning an error, clean up - node_ref - .stats_failed_to_send(send_ts, true); + self.record_send_failure(RPCKind::Answer, send_ts, node_ref.clone(), safety_route, remote_private_route); RPCError::network(e) })? => { // If we couldn't send we're still cleaning up - node_ref - .stats_failed_to_send(send_ts, false); + self.record_send_failure(RPCKind::Answer, send_ts, node_ref.clone(), safety_route, remote_private_route); } ); // Reply successfully sent - node_ref.stats_answer_sent(bytes); + self.record_send_success( + RPCKind::Answer, + send_ts, + bytes, + node_ref, + safety_route, + remote_private_route, + ); Ok(NetworkResult::value(())) } @@ -933,10 +1231,10 @@ impl RPCProcessor { opt_sender_nr = self.routing_table().lookup_node_ref(sender_node_id) } - // Mark this sender as having seen our node info over this routing domain - // because it managed to reach us over that routing domain + // Update the 'seen our node info' timestamp to determine if this node needs a + // 'node info update' ping if let Some(sender_nr) = &opt_sender_nr { - sender_nr.set_seen_our_node_info(routing_domain); + sender_nr.set_our_node_info_ts(routing_domain, operation.target_node_info_ts()); } // Make the RPC message @@ -966,9 +1264,11 @@ impl RPCProcessor { } }; - // Process stats + // Process stats for questions/statements received let kind = match msg.operation.kind() { RPCOperationKind::Question(_) => { + self.record_question_received(&msg); + if let Some(sender_nr) = msg.opt_sender_nr.clone() { sender_nr.stats_question_rcvd(msg.header.timestamp, msg.header.body_len); } @@ -987,7 +1287,7 @@ impl RPCProcessor { }; // Log rpc receive - trace!(target: "rpc_message", dir = "recv", kind, op_id = msg.operation.op_id(), desc = msg.operation.kind().desc(), header = ?msg.header); + trace!(target: "rpc_message", dir = "recv", kind, op_id = msg.operation.op_id().as_u64(), desc = msg.operation.kind().desc(), header = ?msg.header); // Process specific message kind match msg.operation.kind() { @@ -1009,7 +1309,6 @@ impl RPCProcessor { self.process_validate_dial_info(msg).await } RPCStatementDetail::Route(_) => self.process_route(msg).await, - RPCStatementDetail::NodeInfoUpdate(_) => self.process_node_info_update(msg).await, RPCStatementDetail::ValueChanged(_) => self.process_value_changed(msg).await, RPCStatementDetail::Signal(_) => self.process_signal(msg).await, RPCStatementDetail::ReturnReceipt(_) => self.process_return_receipt(msg).await, @@ -1048,13 +1347,8 @@ impl RPCProcessor { Ok(v) => v, }; - cfg_if::cfg_if! { - if #[cfg(debug_assertions)] { - network_result_value_or_log!(warn res => {}); - } else { - network_result_value_or_log!(debug res => {}); - } - } + + network_result_value_or_log!(res => {}); } } @@ -1075,8 +1369,8 @@ impl RPCProcessor { connection_descriptor, routing_domain, }), - timestamp: intf::get_timestamp(), - body_len: body.len() as u64, + timestamp: get_aligned_timestamp(), + body_len: ByteCount::new(body.len() as u64), }, data: RPCMessageData { contents: body }, }; @@ -1094,16 +1388,18 @@ impl RPCProcessor { #[instrument(level = "trace", skip(self, body), err)] pub fn enqueue_safety_routed_message( &self, + remote_safety_route: DHTKey, sequencing: Sequencing, body: Vec, ) -> EyreResult<()> { let msg = RPCMessageEncoded { header: RPCMessageHeader { detail: RPCMessageHeaderDetail::SafetyRouted(RPCMessageHeaderDetailSafetyRouted { + remote_safety_route, sequencing, }), - timestamp: intf::get_timestamp(), - body_len: body.len() as u64, + timestamp: get_aligned_timestamp(), + body_len: (body.len() as u64).into(), }, data: RPCMessageData { contents: body }, }; @@ -1121,6 +1417,7 @@ impl RPCProcessor { #[instrument(level = "trace", skip(self, body), err)] pub fn enqueue_private_routed_message( &self, + remote_safety_route: DHTKey, private_route: DHTKey, safety_spec: SafetySpec, body: Vec, @@ -1129,12 +1426,13 @@ impl RPCProcessor { header: RPCMessageHeader { detail: RPCMessageHeaderDetail::PrivateRouted( RPCMessageHeaderDetailPrivateRouted { + remote_safety_route, private_route, safety_spec, }, ), - timestamp: intf::get_timestamp(), - body_len: body.len() as u64, + timestamp: get_aligned_timestamp(), + body_len: (body.len() as u64).into(), }, data: RPCMessageData { contents: body }, }; diff --git a/veilid-core/src/rpc_processor/operation_waiter.rs b/veilid-core/src/rpc_processor/operation_waiter.rs index 4dc71332..0dfdd926 100644 --- a/veilid-core/src/rpc_processor/operation_waiter.rs +++ b/veilid-core/src/rpc_processor/operation_waiter.rs @@ -93,7 +93,7 @@ where .waiting_op_table .remove(&op_id) .ok_or_else(RPCError::else_internal(format!( - "Unmatched app call id, possibly too late for timeout: {}", + "Unmatched operation id: {}", op_id )))? }; @@ -104,9 +104,9 @@ where pub async fn wait_for_op( &self, mut handle: OperationWaitHandle, - timeout: u64, - ) -> Result, RPCError> { - let timeout_ms = u32::try_from(timeout / 1000u64) + timeout_us: TimestampDuration, + ) -> Result, RPCError> { + let timeout_ms = u32::try_from(timeout_us.as_u64() / 1000u64) .map_err(|e| RPCError::map_internal("invalid timeout")(e))?; // Take the instance @@ -114,23 +114,24 @@ where let eventual_instance = handle.eventual_instance.take().unwrap(); // wait for eventualvalue - let start_ts = intf::get_timestamp(); - let res = intf::timeout(timeout_ms, eventual_instance) + let start_ts = get_aligned_timestamp(); + let res = timeout(timeout_ms, eventual_instance) .await .into_timeout_or(); Ok(res .on_timeout(|| { log_rpc!(debug "op wait timed out: {}", handle.op_id); + // debug_print_backtrace(); self.cancel_op_waiter(handle.op_id); }) .map(|res| { let (_span_id, ret) = res.take_value().unwrap(); - let end_ts = intf::get_timestamp(); + let end_ts = get_aligned_timestamp(); //xxx: causes crash (Missing otel data span extensions) // Span::current().follows_from(span_id); - (ret, end_ts - start_ts) + (ret, end_ts.saturating_sub(start_ts)) })) } } diff --git a/veilid-core/src/rpc_processor/rpc_app_call.rs b/veilid-core/src/rpc_processor/rpc_app_call.rs index e068e133..b0506be9 100644 --- a/veilid-core/src/rpc_processor/rpc_app_call.rs +++ b/veilid-core/src/rpc_processor/rpc_app_call.rs @@ -73,7 +73,7 @@ impl RPCProcessor { let res = self .unlocked_inner .waiting_app_call_table - .wait_for_op(handle, self.unlocked_inner.timeout) + .wait_for_op(handle, self.unlocked_inner.timeout_us) .await?; let (message, _latency) = match res { TimeoutOr::Timeout => { @@ -93,7 +93,7 @@ impl RPCProcessor { } /// Exposed to API for apps to return app call answers - pub async fn app_call_reply(&self, id: u64, message: Vec) -> Result<(), RPCError> { + pub async fn app_call_reply(&self, id: OperationId, message: Vec) -> Result<(), RPCError> { self.unlocked_inner .waiting_app_call_table .complete_op_waiter(id, message) diff --git a/veilid-core/src/rpc_processor/rpc_find_node.rs b/veilid-core/src/rpc_processor/rpc_find_node.rs index 94549971..098c9714 100644 --- a/veilid-core/src/rpc_processor/rpc_find_node.rs +++ b/veilid-core/src/rpc_processor/rpc_find_node.rs @@ -92,19 +92,16 @@ impl RPCProcessor { // add node information for the requesting node to our routing table let routing_table = self.routing_table(); - let has_valid_own_node_info = - routing_table.has_valid_own_node_info(RoutingDomain::PublicInternet); - let own_peer_info = routing_table.get_own_peer_info(RoutingDomain::PublicInternet); + let Some(own_peer_info) = routing_table.get_own_peer_info(RoutingDomain::PublicInternet) else { + // Our own node info is not yet available, drop this request. + return Ok(NetworkResult::service_unavailable()); + }; // find N nodes closest to the target node in our routing table let filter = Box::new( move |rti: &RoutingTableInner, _k: DHTKey, v: Option>| { - rti.filter_has_valid_signed_node_info( - RoutingDomain::PublicInternet, - has_valid_own_node_info, - v, - ) + rti.filter_has_valid_signed_node_info(RoutingDomain::PublicInternet, true, v) }, ) as RoutingTableEntryFilter; let filters = VecDeque::from([filter]); @@ -114,12 +111,7 @@ impl RPCProcessor { filters, // transform |rti, k, v| { - rti.transform_to_peer_info( - RoutingDomain::PublicInternet, - own_peer_info.clone(), - k, - v, - ) + rti.transform_to_peer_info(RoutingDomain::PublicInternet, &own_peer_info, k, v) }, ); diff --git a/veilid-core/src/rpc_processor/rpc_node_info_update.rs b/veilid-core/src/rpc_processor/rpc_node_info_update.rs deleted file mode 100644 index 427ec30f..00000000 --- a/veilid-core/src/rpc_processor/rpc_node_info_update.rs +++ /dev/null @@ -1,84 +0,0 @@ -use super::*; - -impl RPCProcessor { - // Sends a our node info to another node - #[instrument(level = "trace", skip(self), ret, err)] - pub async fn rpc_call_node_info_update( - self, - target: NodeRef, - routing_domain: RoutingDomain, - ) -> Result, RPCError> { - // Get the signed node info for the desired routing domain to send update with - let signed_node_info = self - .routing_table() - .get_own_peer_info(routing_domain) - .signed_node_info; - let node_info_update = RPCOperationNodeInfoUpdate { signed_node_info }; - let statement = RPCStatement::new(RPCStatementDetail::NodeInfoUpdate(node_info_update)); - - // Send the node_info_update request to the specific routing domain requested - network_result_try!( - self.statement( - Destination::direct( - target.filtered_clone(NodeRefFilter::new().with_routing_domain(routing_domain)) - ), - statement, - ) - .await? - ); - - Ok(NetworkResult::value(())) - } - - #[instrument(level = "trace", skip(self, msg), fields(msg.operation.op_id), ret, err)] - pub(crate) async fn process_node_info_update( - &self, - msg: RPCMessage, - ) -> Result, RPCError> { - let detail = match msg.header.detail { - RPCMessageHeaderDetail::Direct(detail) => detail, - RPCMessageHeaderDetail::SafetyRouted(_) | RPCMessageHeaderDetail::PrivateRouted(_) => { - return Ok(NetworkResult::invalid_message( - "node_info_update must be direct", - )); - } - }; - let sender_node_id = detail.envelope.get_sender_id(); - let routing_domain = detail.routing_domain; - - // Get the statement - let node_info_update = match msg.operation.into_kind() { - RPCOperationKind::Statement(s) => match s.into_detail() { - RPCStatementDetail::NodeInfoUpdate(s) => s, - _ => panic!("not a node info update"), - }, - _ => panic!("not a statement"), - }; - - // Update our routing table with signed node info - if !self.filter_node_info(routing_domain, &node_info_update.signed_node_info) { - return Ok(NetworkResult::invalid_message(format!( - "node info doesn't belong in {:?} routing domain: {}", - routing_domain, sender_node_id - ))); - } - - if self - .routing_table() - .register_node_with_signed_node_info( - routing_domain, - sender_node_id, - node_info_update.signed_node_info, - false, - ) - .is_none() - { - return Ok(NetworkResult::invalid_message(format!( - "could not register node info update {}", - sender_node_id - ))); - } - - Ok(NetworkResult::value(())) - } -} diff --git a/veilid-core/src/rpc_processor/rpc_route.rs b/veilid-core/src/rpc_processor/rpc_route.rs index 4abdd681..ad5c8164 100644 --- a/veilid-core/src/rpc_processor/rpc_route.rs +++ b/veilid-core/src/rpc_processor/rpc_route.rs @@ -4,16 +4,17 @@ impl RPCProcessor { #[instrument(level = "trace", skip_all, err)] async fn process_route_safety_route_hop( &self, - route: RPCOperationRoute, + routed_operation: RoutedOperation, route_hop: RouteHop, + safety_route: SafetyRoute, ) -> Result, RPCError> { // Make sure hop count makes sense - if route.safety_route.hop_count as usize > self.unlocked_inner.max_route_hop_count { + if safety_route.hop_count as usize > self.unlocked_inner.max_route_hop_count { return Ok(NetworkResult::invalid_message( "Safety route hop count too high to process", )); } - if route.safety_route.hop_count == 0 { + if safety_route.hop_count == 0 { return Ok(NetworkResult::invalid_message( "Safety route hop count should not be zero if there are more hops", )); @@ -25,7 +26,7 @@ impl RPCProcessor { } // Get next hop node ref - let next_hop_nr = match route_hop.node { + let mut next_hop_nr = match route_hop.node { RouteNode::NodeId(id) => { // let Some(nr) = self.routing_table.lookup_node_ref(id.key) else { @@ -52,14 +53,17 @@ impl RPCProcessor { } }; + // Apply sequencing preference + next_hop_nr.set_sequencing(routed_operation.sequencing); + // Pass along the route let next_hop_route = RPCOperationRoute { safety_route: SafetyRoute { - public_key: route.safety_route.public_key, - hop_count: route.safety_route.hop_count - 1, + public_key: safety_route.public_key, + hop_count: safety_route.hop_count - 1, hops: SafetyRouteHops::Data(route_hop.next_hop.unwrap()), }, - operation: route.operation, + operation: routed_operation, }; let next_hop_route_stmt = RPCStatement::new(RPCStatementDetail::Route(next_hop_route)); @@ -84,7 +88,7 @@ impl RPCProcessor { } // Get next hop node ref - let next_hop_nr = match &next_route_node { + let mut next_hop_nr = match &next_route_node { RouteNode::NodeId(id) => { // self.routing_table @@ -109,6 +113,9 @@ impl RPCProcessor { } }?; + // Apply sequencing preference + next_hop_nr.set_sequencing(routed_operation.sequencing); + // Pass along the route let next_hop_route = RPCOperationRoute { safety_route: SafetyRoute { @@ -133,27 +140,17 @@ impl RPCProcessor { #[instrument(level = "trace", skip_all, err)] fn process_safety_routed_operation( &self, - detail: RPCMessageHeaderDetailDirect, + _detail: RPCMessageHeaderDetailDirect, routed_operation: RoutedOperation, - safety_route: &SafetyRoute, + remote_sr_pubkey: DHTKey, ) -> Result, RPCError> { - // Get sequencing preference - let sequencing = if detail - .connection_descriptor - .protocol_type() - .is_connection_oriented() - { - Sequencing::EnsureOrdered - } else { - Sequencing::NoPreference - }; // Now that things are valid, decrypt the routed operation with DEC(nonce, DH(the SR's public key, the PR's (or node's) secret) // xxx: punish nodes that send messages that fail to decrypt eventually? How to do this for safety routes? let node_id_secret = self.routing_table.node_id_secret(); let dh_secret = self .crypto - .cached_dh(&safety_route.public_key, &node_id_secret) + .cached_dh(&remote_sr_pubkey, &node_id_secret) .map_err(RPCError::protocol)?; let body = match Crypto::decrypt_aead( &routed_operation.data, @@ -168,7 +165,7 @@ impl RPCProcessor { }; // Pass message to RPC system - self.enqueue_safety_routed_message(sequencing, body) + self.enqueue_safety_routed_message(remote_sr_pubkey, routed_operation.sequencing, body) .map_err(RPCError::internal)?; Ok(NetworkResult::value(())) @@ -180,31 +177,43 @@ impl RPCProcessor { &self, detail: RPCMessageHeaderDetailDirect, routed_operation: RoutedOperation, - safety_route: &SafetyRoute, - private_route: &PrivateRoute, + remote_sr_pubkey: DHTKey, + pr_pubkey: DHTKey, ) -> Result, RPCError> { // Get sender id let sender_id = detail.envelope.get_sender_id(); // Look up the private route and ensure it's one in our spec store + // Ensure the route is validated, and construct a return safetyspec that matches the inbound preferences let rss = self.routing_table.route_spec_store(); let Some((secret_key, safety_spec)) = rss - .validate_signatures( - &private_route.public_key, + .with_signature_validated_route( + &pr_pubkey, &routed_operation.signatures, &routed_operation.data, sender_id, + |rsd| { + ( + rsd.get_secret_key(), + SafetySpec { + preferred_route: Some(pr_pubkey), + hop_count: rsd.hop_count(), + stability: rsd.get_stability(), + sequencing: routed_operation.sequencing, + }, + ) + } ) - .map_err(RPCError::protocol)? else { return Ok(NetworkResult::invalid_message("signatures did not validate for private route")); }; + // Now that things are valid, decrypt the routed operation with DEC(nonce, DH(the SR's public key, the PR's (or node's) secret) // xxx: punish nodes that send messages that fail to decrypt eventually. How to do this for private routes? let dh_secret = self .crypto - .cached_dh(&safety_route.public_key, &secret_key) + .cached_dh(&remote_sr_pubkey, &secret_key) .map_err(RPCError::protocol)?; let body = Crypto::decrypt_aead( &routed_operation.data, @@ -217,7 +226,7 @@ impl RPCProcessor { ))?; // Pass message to RPC system - self.enqueue_private_routed_message(private_route.public_key, safety_spec, body) + self.enqueue_private_routed_message(remote_sr_pubkey, pr_pubkey, safety_spec, body) .map_err(RPCError::internal)?; Ok(NetworkResult::value(())) @@ -228,65 +237,123 @@ impl RPCProcessor { &self, detail: RPCMessageHeaderDetailDirect, routed_operation: RoutedOperation, - safety_route: &SafetyRoute, - private_route: &PrivateRoute, + remote_sr_pubkey: DHTKey, + pr_pubkey: DHTKey, ) -> Result, RPCError> { - // Make sure hop count makes sense - if safety_route.hop_count != 0 { - return Ok(NetworkResult::invalid_message( - "Safety hop count should be zero if switched to private route", - )); - } - if private_route.hop_count != 0 { - return Ok(NetworkResult::invalid_message( - "Private route hop count should be zero if we are at the end", - )); - } // If the private route public key is our node id, then this was sent via safety route to our node directly // so there will be no signatures to validate - if private_route.public_key == self.routing_table.node_id() { + if pr_pubkey == self.routing_table.node_id() { // The private route was a stub - self.process_safety_routed_operation(detail, routed_operation, safety_route) + self.process_safety_routed_operation(detail, routed_operation, remote_sr_pubkey) } else { // Both safety and private routes used, should reply with a safety route self.process_private_routed_operation( detail, routed_operation, - safety_route, - private_route, + remote_sr_pubkey, + pr_pubkey, ) } } #[instrument(level = "trace", skip_all, err)] pub(crate) async fn process_private_route_first_hop( &self, - operation: RoutedOperation, + mut routed_operation: RoutedOperation, sr_pubkey: DHTKey, - private_route: &PrivateRoute, + mut private_route: PrivateRoute, ) -> Result, RPCError> { - let PrivateRouteHops::FirstHop(pr_first_hop) = &private_route.hops else { + let Some(pr_first_hop) = private_route.pop_first_hop() else { return Ok(NetworkResult::invalid_message("switching from safety route to private route requires first hop")); }; + // Check for loopback test where private route is the same as safety route + if sr_pubkey == private_route.public_key { + // If so, we're going to turn this thing right around without transiting the network + let PrivateRouteHops::Data(route_hop_data) = private_route.hops else { + return Ok(NetworkResult::invalid_message("Loopback test requires hops")); + }; + + // Decrypt route hop data + let route_hop = network_result_try!(self.decrypt_private_route_hop_data(&route_hop_data, &private_route.public_key, &mut routed_operation)?); + + // Ensure hop count > 0 + if private_route.hop_count == 0 { + return Ok(NetworkResult::invalid_message( + "route should not be at the end", + )); + } + + // Make next PrivateRoute and pass it on + return self.process_route_private_route_hop( + routed_operation, + route_hop.node, + sr_pubkey, + PrivateRoute { + public_key: private_route.public_key, + hop_count: private_route.hop_count - 1, + hops: route_hop + .next_hop + .map(|rhd| PrivateRouteHops::Data(rhd)) + .unwrap_or(PrivateRouteHops::Empty), + }, + ) + .await; + } + // Switching to private route from safety route self.process_route_private_route_hop( - operation, - pr_first_hop.node.clone(), + routed_operation, + pr_first_hop, sr_pubkey, - PrivateRoute { - public_key: private_route.public_key, - hop_count: private_route.hop_count - 1, - hops: pr_first_hop - .next_hop - .clone() - .map(|rhd| PrivateRouteHops::Data(rhd)) - .unwrap_or(PrivateRouteHops::Empty), - }, + private_route, ) .await } + /// Decrypt route hop data and sign routed operation + pub(crate) fn decrypt_private_route_hop_data(&self, route_hop_data: &RouteHopData, pr_pubkey: &DHTKey, route_operation: &mut RoutedOperation) -> Result, RPCError> + { + // Decrypt the blob with DEC(nonce, DH(the PR's public key, this hop's secret) + let node_id_secret = self.routing_table.node_id_secret(); + let dh_secret = self + .crypto + .cached_dh(&pr_pubkey, &node_id_secret) + .map_err(RPCError::protocol)?; + let dec_blob_data = match Crypto::decrypt_aead( + &route_hop_data.blob, + &route_hop_data.nonce, + &dh_secret, + None, + ) { + Ok(v) => v, + Err(e) => { + return Ok(NetworkResult::invalid_message(format!("unable to decrypt private route hop data: {}", e))); + } + }; + let dec_blob_reader = RPCMessageData::new(dec_blob_data).get_reader()?; + + // Decode next RouteHop + let route_hop = { + let rh_reader = dec_blob_reader + .get_root::() + .map_err(RPCError::protocol)?; + decode_route_hop(&rh_reader)? + }; + + // Sign the operation if this is not our last hop + // as the last hop is already signed by the envelope + if route_hop.next_hop.is_some() { + let node_id = self.routing_table.node_id(); + let node_id_secret = self.routing_table.node_id_secret(); + let sig = sign(&node_id, &node_id_secret, &route_operation.data) + .map_err(RPCError::internal)?; + route_operation.signatures.push(sig); + } + + Ok(NetworkResult::value(route_hop)) + } + #[instrument(level = "trace", skip(self, msg), ret, err)] pub(crate) async fn process_route( &self, @@ -322,14 +389,14 @@ impl RPCProcessor { // See what kind of safety route we have going on here match route.safety_route.hops { // There is a safety route hop - SafetyRouteHops::Data(ref d) => { + SafetyRouteHops::Data(ref route_hop_data) => { // Decrypt the blob with DEC(nonce, DH(the SR's public key, this hop's secret) let node_id_secret = self.routing_table.node_id_secret(); let dh_secret = self .crypto .cached_dh(&route.safety_route.public_key, &node_id_secret) .map_err(RPCError::protocol)?; - let mut dec_blob_data = Crypto::decrypt_aead(&d.blob, &d.nonce, &dh_secret, None) + let mut dec_blob_data = Crypto::decrypt_aead(&route_hop_data.blob, &route_hop_data.nonce, &dh_secret, None) .map_err(RPCError::protocol)?; // See if this is last hop in safety route, if so, we're decoding a PrivateRoute not a RouteHop @@ -353,7 +420,7 @@ impl RPCProcessor { network_result_try!(self.process_private_route_first_hop( route.operation, route.safety_route.public_key, - &private_route, + private_route, ) .await?); } else if dec_blob_tag == 0 { @@ -366,16 +433,16 @@ impl RPCProcessor { }; // Continue the full safety route with another hop - network_result_try!(self.process_route_safety_route_hop(route, route_hop) + network_result_try!(self.process_route_safety_route_hop(route.operation, route_hop, route.safety_route) .await?); } else { return Ok(NetworkResult::invalid_message("invalid blob tag")); } } // No safety route left, now doing private route - SafetyRouteHops::Private(ref private_route) => { + SafetyRouteHops::Private(private_route) => { // See if we have a hop, if not, we are at the end of the private route - match &private_route.hops { + match private_route.hops { PrivateRouteHops::FirstHop(_) => { // Safety route was a stub, start with the beginning of the private route network_result_try!(self.process_private_route_first_hop( @@ -386,33 +453,10 @@ impl RPCProcessor { .await?); } PrivateRouteHops::Data(route_hop_data) => { - // Decrypt the blob with DEC(nonce, DH(the PR's public key, this hop's secret) - let node_id_secret = self.routing_table.node_id_secret(); - let dh_secret = self - .crypto - .cached_dh(&private_route.public_key, &node_id_secret) - .map_err(RPCError::protocol)?; - let dec_blob_data = match Crypto::decrypt_aead( - &route_hop_data.blob, - &route_hop_data.nonce, - &dh_secret, - None, - ) { - Ok(v) => v, - Err(e) => { - return Ok(NetworkResult::invalid_message(format!("unable to decrypt private route hop data: {}", e))); - } - }; - let dec_blob_reader = RPCMessageData::new(dec_blob_data).get_reader()?; - - // Decode next RouteHop - let route_hop = { - let rh_reader = dec_blob_reader - .get_root::() - .map_err(RPCError::protocol)?; - decode_route_hop(&rh_reader)? - }; - + + // Decrypt route hop data + let route_hop = network_result_try!(self.decrypt_private_route_hop_data(&route_hop_data, &private_route.public_key, &mut route.operation)?); + // Ensure hop count > 0 if private_route.hop_count == 0 { return Ok(NetworkResult::invalid_message( @@ -420,16 +464,6 @@ impl RPCProcessor { )); } - // Sign the operation if this is not our last hop - // as the last hop is already signed by the envelope - if route_hop.next_hop.is_some() { - let node_id = self.routing_table.node_id(); - let node_id_secret = self.routing_table.node_id_secret(); - let sig = sign(&node_id, &node_id_secret, &route.operation.data) - .map_err(RPCError::internal)?; - route.operation.signatures.push(sig); - } - // Make next PrivateRoute and pass it on network_result_try!(self.process_route_private_route_hop( route.operation, @@ -453,13 +487,18 @@ impl RPCProcessor { "route should be at the end", )); } + if route.safety_route.hop_count != 0 { + return Ok(NetworkResult::invalid_message( + "Safety hop count should be zero if switched to private route", + )); + } // No hops left, time to process the routed operation network_result_try!(self.process_routed_operation( detail, route.operation, - &route.safety_route, - private_route, + route.safety_route.public_key, + private_route.public_key, )?); } } diff --git a/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs b/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs index d1910709..df1a58a2 100644 --- a/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs +++ b/veilid-core/src/rpc_processor/rpc_validate_dial_info.rs @@ -26,7 +26,7 @@ impl RPCProcessor { // Send the validate_dial_info request // This can only be sent directly, as relays can not validate dial info - network_result_value_or_log!(debug self.statement(Destination::direct(peer), statement) + network_result_value_or_log!(self.statement(Destination::direct(peer), statement) .await? => { return Ok(false); } @@ -144,7 +144,7 @@ impl RPCProcessor { // Send the validate_dial_info request // This can only be sent directly, as relays can not validate dial info - network_result_value_or_log!(debug self.statement(Destination::direct(peer), statement) + network_result_value_or_log!(self.statement(Destination::direct(peer), statement) .await? => { continue; } diff --git a/veilid-core/src/tests/android/.idea/.name b/veilid-core/src/tests/android/.idea/.name deleted file mode 100644 index 49552fb2..00000000 --- a/veilid-core/src/tests/android/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -VeilidCore Tests \ No newline at end of file diff --git a/veilid-core/src/tests/android/mod.rs b/veilid-core/src/tests/android/mod.rs new file mode 100644 index 00000000..b8ba9645 --- /dev/null +++ b/veilid-core/src/tests/android/mod.rs @@ -0,0 +1,54 @@ +use super::native::*; +use crate::*; +use backtrace::Backtrace; +use jni::{objects::JClass, objects::JObject, JNIEnv}; +use std::panic; +use tracing_subscriber::prelude::*; + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn Java_com_veilid_veilid_1core_1android_1tests_MainActivity_run_1tests( + env: JNIEnv, + _class: JClass, + ctx: JObject, +) { + veilid_core_setup_android_tests(env, ctx); + block_on(async { + run_all_tests().await; + }) +} + +pub fn veilid_core_setup_android_tests(env: JNIEnv, ctx: JObject) { + // Set up subscriber and layers + let filter = VeilidLayerFilter::new(VeilidConfigLogLevel::Trace, None); + let layer = paranoid_android::layer("veilid-core"); + tracing_subscriber::registry() + .with(layer.with_filter(filter)) + .init(); + + // Set up panic hook for backtraces + panic::set_hook(Box::new(|panic_info| { + let bt = Backtrace::new(); + if let Some(location) = panic_info.location() { + error!( + "panic occurred in file '{}' at line {}", + location.file(), + location.line(), + ); + } else { + error!("panic occurred but can't get location information..."); + } + if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + error!("panic payload: {:?}", s); + } else if let Some(s) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", s); + } else if let Some(a) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", a); + } else { + error!("no panic payload"); + } + error!("Backtrace:\n{:?}", bt); + })); + + veilid_core_setup_android(env, ctx); +} diff --git a/veilid-core/src/tests/android/remove_from_all_devices.sh b/veilid-core/src/tests/android/remove_from_all_devices.sh deleted file mode 100755 index d3eace9d..00000000 --- a/veilid-core/src/tests/android/remove_from_all_devices.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -./adb+.sh uninstall com.veilid.veilidcore.veilidcore_android_tests - diff --git a/veilid-core/src/tests/android/settings.gradle b/veilid-core/src/tests/android/settings.gradle deleted file mode 100644 index bd623c72..00000000 --- a/veilid-core/src/tests/android/settings.gradle +++ /dev/null @@ -1,2 +0,0 @@ -include ':app' -rootProject.name = "VeilidCore Tests" \ No newline at end of file diff --git a/veilid-core/src/tests/android/.gitignore b/veilid-core/src/tests/android/veilid_core_android_tests/.gitignore similarity index 100% rename from veilid-core/src/tests/android/.gitignore rename to veilid-core/src/tests/android/veilid_core_android_tests/.gitignore diff --git a/veilid-core/src/tests/android/.idea/.gitignore b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/.gitignore similarity index 100% rename from veilid-core/src/tests/android/.idea/.gitignore rename to veilid-core/src/tests/android/veilid_core_android_tests/.idea/.gitignore diff --git a/veilid-core/src/tests/android/veilid_core_android_tests/.idea/.name b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/.name new file mode 100644 index 00000000..0a5c13c4 --- /dev/null +++ b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/.name @@ -0,0 +1 @@ +Veilid-Core Tests \ No newline at end of file diff --git a/veilid-core/src/tests/android/.idea/compiler.xml b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/compiler.xml similarity index 100% rename from veilid-core/src/tests/android/.idea/compiler.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/.idea/compiler.xml diff --git a/veilid-core/src/tests/android/.idea/gradle.xml b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/gradle.xml similarity index 100% rename from veilid-core/src/tests/android/.idea/gradle.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/.idea/gradle.xml diff --git a/veilid-core/src/tests/android/.idea/jarRepositories.xml b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/jarRepositories.xml similarity index 100% rename from veilid-core/src/tests/android/.idea/jarRepositories.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/.idea/jarRepositories.xml diff --git a/veilid-core/src/tests/android/.idea/misc.xml b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/misc.xml similarity index 100% rename from veilid-core/src/tests/android/.idea/misc.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/.idea/misc.xml diff --git a/veilid-core/src/tests/android/.idea/vcs.xml b/veilid-core/src/tests/android/veilid_core_android_tests/.idea/vcs.xml similarity index 100% rename from veilid-core/src/tests/android/.idea/vcs.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/.idea/vcs.xml diff --git a/veilid-core/src/tests/android/.project b/veilid-core/src/tests/android/veilid_core_android_tests/.project similarity index 100% rename from veilid-core/src/tests/android/.project rename to veilid-core/src/tests/android/veilid_core_android_tests/.project diff --git a/veilid-core/src/tests/android/.settings/org.eclipse.buildship.core.prefs b/veilid-core/src/tests/android/veilid_core_android_tests/.settings/org.eclipse.buildship.core.prefs similarity index 100% rename from veilid-core/src/tests/android/.settings/org.eclipse.buildship.core.prefs rename to veilid-core/src/tests/android/veilid_core_android_tests/.settings/org.eclipse.buildship.core.prefs diff --git a/veilid-core/src/tests/android/adb+.sh b/veilid-core/src/tests/android/veilid_core_android_tests/adb+.sh similarity index 100% rename from veilid-core/src/tests/android/adb+.sh rename to veilid-core/src/tests/android/veilid_core_android_tests/adb+.sh diff --git a/veilid-core/src/tests/android/app/.classpath b/veilid-core/src/tests/android/veilid_core_android_tests/app/.classpath similarity index 100% rename from veilid-core/src/tests/android/app/.classpath rename to veilid-core/src/tests/android/veilid_core_android_tests/app/.classpath diff --git a/veilid-core/src/tests/android/app/.gitignore b/veilid-core/src/tests/android/veilid_core_android_tests/app/.gitignore similarity index 100% rename from veilid-core/src/tests/android/app/.gitignore rename to veilid-core/src/tests/android/veilid_core_android_tests/app/.gitignore diff --git a/veilid-core/src/tests/android/app/.project b/veilid-core/src/tests/android/veilid_core_android_tests/app/.project similarity index 100% rename from veilid-core/src/tests/android/app/.project rename to veilid-core/src/tests/android/veilid_core_android_tests/app/.project diff --git a/veilid-core/src/tests/android/app/.settings/org.eclipse.buildship.core.prefs b/veilid-core/src/tests/android/veilid_core_android_tests/app/.settings/org.eclipse.buildship.core.prefs similarity index 100% rename from veilid-core/src/tests/android/app/.settings/org.eclipse.buildship.core.prefs rename to veilid-core/src/tests/android/veilid_core_android_tests/app/.settings/org.eclipse.buildship.core.prefs diff --git a/veilid-core/src/tests/android/app/CMakeLists.txt b/veilid-core/src/tests/android/veilid_core_android_tests/app/CMakeLists.txt similarity index 100% rename from veilid-core/src/tests/android/app/CMakeLists.txt rename to veilid-core/src/tests/android/veilid_core_android_tests/app/CMakeLists.txt diff --git a/veilid-core/src/tests/android/app/build.gradle b/veilid-core/src/tests/android/veilid_core_android_tests/app/build.gradle similarity index 75% rename from veilid-core/src/tests/android/app/build.gradle rename to veilid-core/src/tests/android/veilid_core_android_tests/app/build.gradle index 25ad2c38..af4abe42 100644 --- a/veilid-core/src/tests/android/app/build.gradle +++ b/veilid-core/src/tests/android/veilid_core_android_tests/app/build.gradle @@ -3,13 +3,13 @@ plugins { } android { - compileSdkVersion 30 - buildToolsVersion "30.0.3" + compileSdkVersion 33 + buildToolsVersion "33.0.1" defaultConfig { - applicationId "com.veilid.veilidcore.veilidcore_android_tests" + applicationId "com.veilid.veilid_core_android_tests" minSdkVersion 24 - targetSdkVersion 30 + targetSdkVersion 33 versionCode 1 versionName "1.0" @@ -38,38 +38,37 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - ndkVersion '22.0.7026061' + ndkVersion '25.1.8937393' // Required to copy libc++_shared.so externalNativeBuild { cmake { + version '3.22.1' path file('CMakeLists.txt') } } + namespace 'com.veilid.veilid_core_android_tests' } dependencies { - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.2' - implementation 'androidx.security:security-crypto:1.1.0-alpha03' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.security:security-crypto:1.1.0-alpha04' } apply plugin: 'org.mozilla.rust-android-gradle.rust-android' cargo { - module = "../../../../../veilid-core" + module = "../../../../../" libname = "veilid_core" targets = ["arm", "arm64", "x86", "x86_64"] - targetDirectory = "../../../../../target" + targetDirectory = "../../../../../../target" prebuiltToolchains = true profile = gradle.startParameter.taskNames.any{it.toLowerCase().contains("debug")} ? "debug" : "release" pythonCommand = "python3" features { - defaultAnd("android_tests", "rt-tokio") + defaultAnd("veilid_core_android_tests", "rt-tokio") } } diff --git a/veilid-core/src/tests/android/app/cpplink.cpp b/veilid-core/src/tests/android/veilid_core_android_tests/app/cpplink.cpp similarity index 100% rename from veilid-core/src/tests/android/app/cpplink.cpp rename to veilid-core/src/tests/android/veilid_core_android_tests/app/cpplink.cpp diff --git a/veilid-core/src/tests/android/app/proguard-rules.pro b/veilid-core/src/tests/android/veilid_core_android_tests/app/proguard-rules.pro similarity index 100% rename from veilid-core/src/tests/android/app/proguard-rules.pro rename to veilid-core/src/tests/android/veilid_core_android_tests/app/proguard-rules.pro diff --git a/veilid-core/src/tests/android/app/src/main/AndroidManifest.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/AndroidManifest.xml similarity index 88% rename from veilid-core/src/tests/android/app/src/main/AndroidManifest.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/AndroidManifest.xml index a0f77f64..c7052ecf 100644 --- a/veilid-core/src/tests/android/app/src/main/AndroidManifest.xml +++ b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + @@ -13,7 +12,8 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.VeilidCoreTests"> - + diff --git a/veilid-core/src/tests/android/app/src/main/java/com/veilid/veilid-core/veilid-core_android_tests/MainActivity.java b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/java/com/veilid/veilid_core_android_tests/MainActivity.java similarity index 86% rename from veilid-core/src/tests/android/app/src/main/java/com/veilid/veilid-core/veilid-core_android_tests/MainActivity.java rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/java/com/veilid/veilid_core_android_tests/MainActivity.java index 00724e1b..d04edb8e 100644 --- a/veilid-core/src/tests/android/app/src/main/java/com/veilid/veilid-core/veilid-core_android_tests/MainActivity.java +++ b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/java/com/veilid/veilid_core_android_tests/MainActivity.java @@ -1,4 +1,4 @@ -package com.veilid.veilidcore.veilidcore_android_tests; +package com.veilid.veilid_core_android_tests; import androidx.appcompat.app.AppCompatActivity; import android.content.Context; @@ -23,6 +23,8 @@ public class MainActivity extends AppCompatActivity { public void run() { run_tests(this.context); + ((MainActivity)this.context).finish(); + System.exit(0); } } diff --git a/veilid-core/src/tests/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/drawable-v24/ic_launcher_foreground.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/drawable/ic_launcher_background.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/drawable/ic_launcher_background.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/drawable/ic_launcher_background.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/drawable/ic_launcher_background.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/layout/activity_main.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/layout/activity_main.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/layout/activity_main.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/layout/activity_main.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher_round.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher_round.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/veilid-core/src/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/veilid-core/src/tests/android/app/src/main/res/values-night/themes.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values-night/themes.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/values-night/themes.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values-night/themes.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/values/colors.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values/colors.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/values/colors.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values/colors.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/values/strings.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values/strings.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/values/strings.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values/strings.xml diff --git a/veilid-core/src/tests/android/app/src/main/res/values/themes.xml b/veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values/themes.xml similarity index 100% rename from veilid-core/src/tests/android/app/src/main/res/values/themes.xml rename to veilid-core/src/tests/android/veilid_core_android_tests/app/src/main/res/values/themes.xml diff --git a/veilid-core/src/tests/android/build.gradle b/veilid-core/src/tests/android/veilid_core_android_tests/build.gradle similarity index 89% rename from veilid-core/src/tests/android/build.gradle rename to veilid-core/src/tests/android/veilid_core_android_tests/build.gradle index 96496236..ffb3ba0e 100644 --- a/veilid-core/src/tests/android/build.gradle +++ b/veilid-core/src/tests/android/veilid_core_android_tests/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath "com.android.tools.build:gradle:4.1.2" + classpath 'com.android.tools.build:gradle:7.3.1' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -13,7 +13,7 @@ buildscript { } plugins { - id "org.mozilla.rust-android-gradle.rust-android" version "0.9.0" + id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" } allprojects { diff --git a/veilid-core/src/tests/android/gradle.properties b/veilid-core/src/tests/android/veilid_core_android_tests/gradle.properties similarity index 100% rename from veilid-core/src/tests/android/gradle.properties rename to veilid-core/src/tests/android/veilid_core_android_tests/gradle.properties diff --git a/veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.jar b/veilid-core/src/tests/android/veilid_core_android_tests/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.jar rename to veilid-core/src/tests/android/veilid_core_android_tests/gradle/wrapper/gradle-wrapper.jar diff --git a/veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.properties b/veilid-core/src/tests/android/veilid_core_android_tests/gradle/wrapper/gradle-wrapper.properties similarity index 93% rename from veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.properties rename to veilid-core/src/tests/android/veilid_core_android_tests/gradle/wrapper/gradle-wrapper.properties index 3a56e3d2..c5da459e 100644 --- a/veilid-core/src/tests/android/gradle/wrapper/gradle-wrapper.properties +++ b/veilid-core/src/tests/android/veilid_core_android_tests/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip diff --git a/veilid-core/src/tests/android/gradlew b/veilid-core/src/tests/android/veilid_core_android_tests/gradlew similarity index 100% rename from veilid-core/src/tests/android/gradlew rename to veilid-core/src/tests/android/veilid_core_android_tests/gradlew diff --git a/veilid-core/src/tests/android/gradlew.bat b/veilid-core/src/tests/android/veilid_core_android_tests/gradlew.bat similarity index 100% rename from veilid-core/src/tests/android/gradlew.bat rename to veilid-core/src/tests/android/veilid_core_android_tests/gradlew.bat diff --git a/veilid-core/src/tests/android/install_on_all_devices.sh b/veilid-core/src/tests/android/veilid_core_android_tests/install_on_all_devices.sh similarity index 100% rename from veilid-core/src/tests/android/install_on_all_devices.sh rename to veilid-core/src/tests/android/veilid_core_android_tests/install_on_all_devices.sh diff --git a/veilid-core/src/tests/android/veilid_core_android_tests/remove_from_all_devices.sh b/veilid-core/src/tests/android/veilid_core_android_tests/remove_from_all_devices.sh new file mode 100755 index 00000000..b13b8775 --- /dev/null +++ b/veilid-core/src/tests/android/veilid_core_android_tests/remove_from_all_devices.sh @@ -0,0 +1,3 @@ +#!/bin/bash +./adb+.sh uninstall com.veilid.veilid_core_android_tests + diff --git a/veilid-core/src/tests/android/veilid_core_android_tests/settings.gradle b/veilid-core/src/tests/android/veilid_core_android_tests/settings.gradle new file mode 100644 index 00000000..24554dcf --- /dev/null +++ b/veilid-core/src/tests/android/veilid_core_android_tests/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name = "Veilid-Core Tests" \ No newline at end of file diff --git a/veilid-core/src/tests/common/mod.rs b/veilid-core/src/tests/common/mod.rs index ab47cdd5..f0fbc066 100644 --- a/veilid-core/src/tests/common/mod.rs +++ b/veilid-core/src/tests/common/mod.rs @@ -1,11 +1,5 @@ -pub mod test_async_tag_lock; pub mod test_host_interface; pub mod test_protected_store; pub mod test_table_store; pub mod test_veilid_config; pub mod test_veilid_core; - -use super::*; - -pub use crypto::tests::*; -pub use network_manager::tests::*; diff --git a/veilid-core/src/tests/common/test_host_interface.rs b/veilid-core/src/tests/common/test_host_interface.rs index 6bad2e12..d1a52cdc 100644 --- a/veilid-core/src/tests/common/test_host_interface.rs +++ b/veilid-core/src/tests/common/test_host_interface.rs @@ -1,469 +1,13 @@ -use crate::xx::*; use crate::*; -cfg_if! { - if #[cfg(target_arch = "wasm32")] { - use js_sys::*; - } else { - use std::time::{Duration, SystemTime}; - } -} - -pub async fn test_log() { - info!("testing log"); -} - -pub async fn test_get_timestamp() { - info!("testing get_timestamp"); - let t1 = intf::get_timestamp(); - let t2 = intf::get_timestamp(); - assert!(t2 >= t1); -} - -pub async fn test_eventual() { - info!("testing Eventual"); - { - let e1 = Eventual::new(); - let i1 = e1.instance_clone(1u32); - let i2 = e1.instance_clone(2u32); - let i3 = e1.instance_clone(3u32); - drop(i3); - let i4 = e1.instance_clone(4u32); - drop(i2); - - let jh = intf::spawn(async move { - intf::sleep(1000).await; - e1.resolve(); - }); - - assert_eq!(i1.await, 1u32); - assert_eq!(i4.await, 4u32); - - jh.await; - } - { - let e1 = Eventual::new(); - let i1 = e1.instance_clone(1u32); - let i2 = e1.instance_clone(2u32); - let i3 = e1.instance_clone(3u32); - let i4 = e1.instance_clone(4u32); - let e1_c1 = e1.clone(); - let jh = intf::spawn(async move { - let i5 = e1.instance_clone(5u32); - let i6 = e1.instance_clone(6u32); - assert_eq!(i1.await, 1u32); - assert_eq!(i5.await, 5u32); - assert_eq!(i6.await, 6u32); - }); - intf::sleep(1000).await; - let resolved = e1_c1.resolve(); - drop(i2); - drop(i3); - assert_eq!(i4.await, 4u32); - resolved.await; - jh.await; - } - { - let e1 = Eventual::new(); - let i1 = e1.instance_clone(1u32); - let i2 = e1.instance_clone(2u32); - let e1_c1 = e1.clone(); - let jh = intf::spawn(async move { - assert_eq!(i1.await, 1u32); - assert_eq!(i2.await, 2u32); - }); - intf::sleep(1000).await; - e1_c1.resolve().await; - - jh.await; - - e1_c1.reset(); - // - let j1 = e1.instance_clone(1u32); - let j2 = e1.instance_clone(2u32); - let jh = intf::spawn(async move { - assert_eq!(j1.await, 1u32); - assert_eq!(j2.await, 2u32); - }); - intf::sleep(1000).await; - e1_c1.resolve().await; - - jh.await; - - e1_c1.reset(); - } -} - -pub async fn test_eventual_value() { - info!("testing Eventual Value"); - { - let e1 = EventualValue::::new(); - let i1 = e1.instance(); - let i2 = e1.instance(); - let i3 = e1.instance(); - drop(i3); - let i4 = e1.instance(); - drop(i2); - - let e1_c1 = e1.clone(); - let jh = intf::spawn(async move { - intf::sleep(1000).await; - e1_c1.resolve(3u32); - }); - - i1.await; - i4.await; - jh.await; - assert_eq!(e1.take_value(), Some(3u32)); - } - { - let e1 = EventualValue::new(); - let i1 = e1.instance(); - let i2 = e1.instance(); - let i3 = e1.instance(); - let i4 = e1.instance(); - let e1_c1 = e1.clone(); - let jh = intf::spawn(async move { - let i5 = e1.instance(); - let i6 = e1.instance(); - i1.await; - i5.await; - i6.await; - }); - intf::sleep(1000).await; - let resolved = e1_c1.resolve(4u16); - drop(i2); - drop(i3); - i4.await; - resolved.await; - jh.await; - assert_eq!(e1_c1.take_value(), Some(4u16)); - } - { - let e1 = EventualValue::new(); - assert_eq!(e1.take_value(), None); - let i1 = e1.instance(); - let i2 = e1.instance(); - let e1_c1 = e1.clone(); - let jh = intf::spawn(async move { - i1.await; - i2.await; - }); - intf::sleep(1000).await; - e1_c1.resolve(5u32).await; - jh.await; - assert_eq!(e1_c1.take_value(), Some(5u32)); - e1_c1.reset(); - assert_eq!(e1_c1.take_value(), None); - // - let j1 = e1.instance(); - let j2 = e1.instance(); - let jh = intf::spawn(async move { - j1.await; - j2.await; - }); - intf::sleep(1000).await; - e1_c1.resolve(6u32).await; - jh.await; - assert_eq!(e1_c1.take_value(), Some(6u32)); - e1_c1.reset(); - assert_eq!(e1_c1.take_value(), None); - } -} - -pub async fn test_eventual_value_clone() { - info!("testing Eventual Value Clone"); - { - let e1 = EventualValueClone::::new(); - let i1 = e1.instance(); - let i2 = e1.instance(); - let i3 = e1.instance(); - drop(i3); - let i4 = e1.instance(); - drop(i2); - - let jh = intf::spawn(async move { - intf::sleep(1000).await; - e1.resolve(3u32); - }); - - assert_eq!(i1.await, 3); - assert_eq!(i4.await, 3); - - jh.await; - } - - { - let e1 = EventualValueClone::new(); - let i1 = e1.instance(); - let i2 = e1.instance(); - let i3 = e1.instance(); - let i4 = e1.instance(); - let e1_c1 = e1.clone(); - let jh = intf::spawn(async move { - let i5 = e1.instance(); - let i6 = e1.instance(); - assert_eq!(i1.await, 4); - assert_eq!(i5.await, 4); - assert_eq!(i6.await, 4); - }); - intf::sleep(1000).await; - let resolved = e1_c1.resolve(4u16); - drop(i2); - drop(i3); - assert_eq!(i4.await, 4); - resolved.await; - jh.await; - } - - { - let e1 = EventualValueClone::new(); - let i1 = e1.instance(); - let i2 = e1.instance(); - let e1_c1 = e1.clone(); - let jh = intf::spawn(async move { - assert_eq!(i1.await, 5); - assert_eq!(i2.await, 5); - }); - intf::sleep(1000).await; - e1_c1.resolve(5u32).await; - jh.await; - e1_c1.reset(); - // - let j1 = e1.instance(); - let j2 = e1.instance(); - let jh = intf::spawn(async move { - assert_eq!(j1.await, 6); - assert_eq!(j2.await, 6); - }); - intf::sleep(1000).await; - e1_c1.resolve(6u32).await; - jh.await; - e1_c1.reset(); - } -} -pub async fn test_interval() { - info!("testing interval"); - - let tick: Arc> = Arc::new(Mutex::new(0u32)); - let stopper = intf::interval(1000, move || { - let tick = tick.clone(); - async move { - let mut tick = tick.lock(); - trace!("tick {}", tick); - *tick += 1; - } - }); - - intf::sleep(5500).await; - - stopper.await; -} - -pub async fn test_timeout() { - info!("testing timeout"); - - let tick: Arc> = Arc::new(Mutex::new(0u32)); - let tick_1 = tick.clone(); - assert!( - intf::timeout(2500, async move { - let mut tick = tick_1.lock(); - trace!("tick {}", tick); - intf::sleep(1000).await; - *tick += 1; - trace!("tick {}", tick); - intf::sleep(1000).await; - *tick += 1; - trace!("tick {}", tick); - intf::sleep(1000).await; - *tick += 1; - trace!("tick {}", tick); - intf::sleep(1000).await; - *tick += 1; - }) - .await - .is_err(), - "should have timed out" - ); - - let ticks = *tick.lock(); - assert!(ticks <= 2); -} - -pub async fn test_sleep() { - info!("testing sleep"); - cfg_if! { - if #[cfg(target_arch = "wasm32")] { - - let t1 = Date::now(); - intf::sleep(1000).await; - let t2 = Date::now(); - assert!((t2-t1) >= 1000.0); - - } else { - - let sys_time = SystemTime::now(); - let one_sec = Duration::from_secs(1); - - intf::sleep(1000).await; - assert!(sys_time.elapsed().unwrap() >= one_sec); - } - } -} - -macro_rules! assert_split_url { - ($url:expr, $scheme:expr, $host:expr) => { - assert_eq!( - SplitUrl::from_str($url), - Ok(SplitUrl::new($scheme, None, $host, None, None)) - ); - }; - ($url:expr, $scheme:expr, $host:expr, $port:expr) => { - assert_eq!( - SplitUrl::from_str($url), - Ok(SplitUrl::new($scheme, None, $host, $port, None)) - ); - }; - ($url:expr, $scheme:expr, $host:expr, $port:expr, $path:expr) => { - assert_eq!( - SplitUrl::from_str($url), - Ok(SplitUrl::new( - $scheme, - None, - $host, - $port, - Some(SplitUrlPath::new( - $path, - Option::::None, - Option::::None - )) - )) - ); - }; - ($url:expr, $scheme:expr, $host:expr, $port:expr, $path:expr, $frag:expr, $query:expr) => { - assert_eq!( - SplitUrl::from_str($url), - Ok(SplitUrl::new( - $scheme, - None, - $host, - $port, - Some(SplitUrlPath::new($path, $frag, $query)) - )) - ); - }; -} - -macro_rules! assert_split_url_parse { - ($url:expr) => { - let url = $url; - let su1 = SplitUrl::from_str(url).expect("should parse"); - assert_eq!(su1.to_string(), url); - }; -} - -fn host>(s: S) -> SplitUrlHost { - SplitUrlHost::Hostname(s.as_ref().to_owned()) -} - -fn ip>(s: S) -> SplitUrlHost { - SplitUrlHost::IpAddr(IpAddr::from_str(s.as_ref()).unwrap()) -} - -pub async fn test_split_url() { - info!("testing split_url"); - - assert_split_url!("http://foo", "http", host("foo")); - assert_split_url!("http://foo:1234", "http", host("foo"), Some(1234)); - assert_split_url!("http://foo:1234/", "http", host("foo"), Some(1234), ""); - assert_split_url!( - "http://foo:1234/asdf/qwer", - "http", - host("foo"), - Some(1234), - "asdf/qwer" - ); - assert_split_url!("http://foo/", "http", host("foo"), None, ""); - assert_split_url!("http://11.2.3.144/", "http", ip("11.2.3.144"), None, ""); - assert_split_url!("http://[1111::2222]/", "http", ip("1111::2222"), None, ""); - assert_split_url!( - "http://[1111::2222]:123/", - "http", - ip("1111::2222"), - Some(123), - "" - ); - - assert_split_url!( - "http://foo/asdf/qwer", - "http", - host("foo"), - None, - "asdf/qwer" - ); - assert_split_url!( - "http://foo/asdf/qwer#3", - "http", - host("foo"), - None, - "asdf/qwer", - Some("3"), - Option::::None - ); - assert_split_url!( - "http://foo/asdf/qwer?xxx", - "http", - host("foo"), - None, - "asdf/qwer", - Option::::None, - Some("xxx") - ); - assert_split_url!( - "http://foo/asdf/qwer#yyy?xxx", - "http", - host("foo"), - None, - "asdf/qwer", - Some("yyy"), - Some("xxx") - ); - assert_err!(SplitUrl::from_str("://asdf")); - assert_err!(SplitUrl::from_str("")); - assert_err!(SplitUrl::from_str("::")); - assert_err!(SplitUrl::from_str("://:")); - assert_err!(SplitUrl::from_str("a://:")); - assert_err!(SplitUrl::from_str("a://:1243")); - assert_err!(SplitUrl::from_str("a://:65536")); - assert_err!(SplitUrl::from_str("a://:-16")); - assert_err!(SplitUrl::from_str("a:///")); - assert_err!(SplitUrl::from_str("a:///qwer:")); - assert_err!(SplitUrl::from_str("a:///qwer://")); - assert_err!(SplitUrl::from_str("a://qwer://")); - assert_err!(SplitUrl::from_str("a://[1111::2222]:/")); - assert_err!(SplitUrl::from_str("a://[1111::2222]:")); - - assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord#qux?zuz"); - assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord#qux"); - assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord?zuz"); - assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord/"); - assert_split_url_parse!("sch://foo:bar@baz.com:1234//"); - assert_split_url_parse!("sch://foo:bar@baz.com:1234"); - assert_split_url_parse!("sch://foo:bar@[1111::2222]:1234"); - assert_split_url_parse!("sch://foo:bar@[::]:1234"); - assert_split_url_parse!("sch://foo:bar@1.2.3.4:1234"); - assert_split_url_parse!("sch://@baz.com:1234"); - assert_split_url_parse!("sch://baz.com/asdf/asdf"); - assert_split_url_parse!("sch://baz.com/"); - assert_split_url_parse!("s://s"); -} - cfg_if! { if #[cfg(not(target_arch = "wasm32"))] { + use intf::network_interfaces::NetworkInterfaces; + pub async fn test_network_interfaces() { info!("testing network interfaces"); - let t1 = intf::get_timestamp(); - let interfaces = intf::utils::network_interfaces::NetworkInterfaces::new(); + let t1 = get_timestamp(); + let interfaces = NetworkInterfaces::new(); let count = 100; for x in 0..count { info!("loop {}", x); @@ -471,107 +15,15 @@ cfg_if! { error!("error refreshing interfaces: {}", e); } } - let t2 = intf::get_timestamp(); + let t2 = get_timestamp(); let tdiff = ((t2 - t1) as f64)/1000000.0f64; info!("running network interface test with {} iterations took {} seconds", count, tdiff); - info!("interfaces: {:#?}", interfaces) - } - } -} - -pub async fn test_get_random_u64() { - info!("testing random number generator for u64"); - let t1 = intf::get_timestamp(); - let count = 10000; - for _ in 0..count { - let _ = intf::get_random_u64(); - } - let t2 = intf::get_timestamp(); - let tdiff = ((t2 - t1) as f64) / 1000000.0f64; - info!( - "running network interface test with {} iterations took {} seconds", - count, tdiff - ); -} - -pub async fn test_get_random_u32() { - info!("testing random number generator for u32"); - let t1 = intf::get_timestamp(); - let count = 10000; - for _ in 0..count { - let _ = intf::get_random_u32(); - } - let t2 = intf::get_timestamp(); - let tdiff = ((t2 - t1) as f64) / 1000000.0f64; - info!( - "running network interface test with {} iterations took {} seconds", - count, tdiff - ); -} - -pub async fn test_must_join_single_future() { - info!("testing must join single future"); - let sf = MustJoinSingleFuture::::new(); - assert_eq!(sf.check().await, Ok(None)); - assert_eq!( - sf.single_spawn(async { - intf::sleep(2000).await; - 69 - }) - .await, - Ok((None, true)) - ); - assert_eq!(sf.check().await, Ok(None)); - assert_eq!(sf.single_spawn(async { panic!() }).await, Ok((None, false))); - assert_eq!(sf.join().await, Ok(Some(69))); - assert_eq!( - sf.single_spawn(async { - intf::sleep(1000).await; - 37 - }) - .await, - Ok((None, true)) - ); - intf::sleep(2000).await; - assert_eq!( - sf.single_spawn(async { - intf::sleep(1000).await; - 27 - }) - .await, - Ok((Some(37), true)) - ); - intf::sleep(2000).await; - assert_eq!(sf.join().await, Ok(Some(27))); - assert_eq!(sf.check().await, Ok(None)); -} - -pub async fn test_tools() { - info!("testing retry_falloff_log"); - let mut last_us = 0u64; - for x in 0..1024 { - let cur_us = x as u64 * 1000000u64; - if retry_falloff_log(last_us, cur_us, 10_000_000u64, 6_000_000_000u64, 2.0f64) { - info!(" retry at {} secs", timestamp_to_secs(cur_us)); - last_us = cur_us; + //info!("interfaces: {:#?}", interfaces) } } } pub async fn test_all() { - test_log().await; - test_get_timestamp().await; - test_tools().await; - test_split_url().await; - test_get_random_u64().await; - test_get_random_u32().await; - test_sleep().await; #[cfg(not(target_arch = "wasm32"))] test_network_interfaces().await; - test_must_join_single_future().await; - test_eventual().await; - test_eventual_value().await; - test_eventual_value_clone().await; - test_interval().await; - test_timeout().await; } diff --git a/veilid-core/src/tests/common/test_protected_store.rs b/veilid-core/src/tests/common/test_protected_store.rs index ef48c11a..49d56d4e 100644 --- a/veilid-core/src/tests/common/test_protected_store.rs +++ b/veilid-core/src/tests/common/test_protected_store.rs @@ -1,5 +1,4 @@ use super::test_veilid_config::*; -use crate::xx::*; use crate::*; async fn startup() -> VeilidAPI { diff --git a/veilid-core/src/tests/common/test_table_store.rs b/veilid-core/src/tests/common/test_table_store.rs index 5e3478a3..659e7527 100644 --- a/veilid-core/src/tests/common/test_table_store.rs +++ b/veilid-core/src/tests/common/test_table_store.rs @@ -1,5 +1,4 @@ use super::test_veilid_config::*; -use crate::xx::*; use crate::*; async fn startup() -> VeilidAPI { @@ -64,7 +63,7 @@ pub async fn test_store_delete_load(ts: TableStore) { "should not load missing key" ); assert!( - db.store(1, b"foo", b"1234567890").is_ok(), + db.store(1, b"foo", b"1234567890").await.is_ok(), "should store new key" ); assert_eq!( @@ -75,23 +74,25 @@ pub async fn test_store_delete_load(ts: TableStore) { assert_eq!(db.load(1, b"foo").unwrap(), Some(b"1234567890".to_vec())); assert!( - db.store(1, b"bar", b"FNORD").is_ok(), + db.store(1, b"bar", b"FNORD").await.is_ok(), "should store new key" ); assert!( - db.store(0, b"bar", b"ABCDEFGHIJKLMNOPQRSTUVWXYZ").is_ok(), + db.store(0, b"bar", b"ABCDEFGHIJKLMNOPQRSTUVWXYZ") + .await + .is_ok(), "should store new key" ); assert!( - db.store(2, b"bar", b"FNORD").is_ok(), + db.store(2, b"bar", b"FNORD").await.is_ok(), "should store new key" ); assert!( - db.store(2, b"baz", b"QWERTY").is_ok(), + db.store(2, b"baz", b"QWERTY").await.is_ok(), "should store new key" ); assert!( - db.store(2, b"bar", b"QWERTYUIOP").is_ok(), + db.store(2, b"bar", b"QWERTYUIOP").await.is_ok(), "should store new key" ); @@ -103,10 +104,10 @@ pub async fn test_store_delete_load(ts: TableStore) { assert_eq!(db.load(2, b"bar").unwrap(), Some(b"QWERTYUIOP".to_vec())); assert_eq!(db.load(2, b"baz").unwrap(), Some(b"QWERTY".to_vec())); - assert_eq!(db.delete(1, b"bar").unwrap(), true); - assert_eq!(db.delete(1, b"bar").unwrap(), false); + assert_eq!(db.delete(1, b"bar").await.unwrap(), true); + assert_eq!(db.delete(1, b"bar").await.unwrap(), false); assert!( - db.delete(4, b"bar").is_err(), + db.delete(4, b"bar").await.is_err(), "can't delete from column that doesn't exist" ); @@ -129,7 +130,7 @@ pub async fn test_frozen(ts: TableStore) { let db = ts.open("test", 3).await.expect("should have opened"); let (dht_key, _) = generate_secret(); - assert!(db.store_rkyv(0, b"asdf", &dht_key).is_ok()); + assert!(db.store_rkyv(0, b"asdf", &dht_key).await.is_ok()); assert_eq!(db.load_rkyv::(0, b"qwer").unwrap(), None); @@ -142,7 +143,7 @@ pub async fn test_frozen(ts: TableStore) { assert_eq!(d, Some(dht_key), "keys should be equal"); assert!( - db.store(1, b"foo", b"1234567890").is_ok(), + db.store(1, b"foo", b"1234567890").await.is_ok(), "should store new key" ); diff --git a/veilid-core/src/tests/common/test_veilid_config.rs b/veilid-core/src/tests/common/test_veilid_config.rs index e73e79a1..3743ca7e 100644 --- a/veilid-core/src/tests/common/test_veilid_config.rs +++ b/veilid-core/src/tests/common/test_veilid_config.rs @@ -1,7 +1,7 @@ #![allow(clippy::bool_assert_comparison)] -use crate::xx::*; use crate::*; + cfg_if! { if #[cfg(not(target_arch = "wasm32"))] { use std::fs::File; @@ -85,7 +85,7 @@ cfg_if! { fn get_data_dir() -> PathBuf { cfg_if! { if #[cfg(target_os = "android")] { - PathBuf::from(intf::utils::android::get_files_dir()) + PathBuf::from(crate::intf::android::get_files_dir()) } else { use directories::*; @@ -156,8 +156,8 @@ cfg_if! { } } -fn update_callback(update: VeilidUpdate) { - println!("update_callback: {:?}", update); +fn update_callback(_update: VeilidUpdate) { + // println!("update_callback: {:?}", update); } pub fn setup_veilid_core() -> (UpdateCallback, ConfigCallback) { @@ -222,7 +222,6 @@ fn config_callback(key: String) -> ConfigCallbackReturn { "network.dht.min_peer_refresh_time_ms" => Ok(Box::new(2_000u32)), "network.dht.validate_dial_info_receipt_time_ms" => Ok(Box::new(5_000u32)), "network.upnp" => Ok(Box::new(false)), - "network.natpmp" => Ok(Box::new(false)), "network.detect_address_changes" => Ok(Box::new(true)), "network.restricted_nat_retries" => Ok(Box::new(3u32)), "network.tls.certificate_path" => Ok(Box::new(get_certfile_path())), @@ -352,7 +351,6 @@ pub async fn test_config() { ); assert_eq!(inner.network.upnp, false); - assert_eq!(inner.network.natpmp, false); assert_eq!(inner.network.detect_address_changes, true); assert_eq!(inner.network.restricted_nat_retries, 3u32); assert_eq!(inner.network.tls.certificate_path, get_certfile_path()); diff --git a/veilid-core/src/tests/common/test_veilid_core.rs b/veilid-core/src/tests/common/test_veilid_core.rs index ed31f2a6..27bf3e2e 100644 --- a/veilid-core/src/tests/common/test_veilid_core.rs +++ b/veilid-core/src/tests/common/test_veilid_core.rs @@ -1,5 +1,4 @@ use super::test_veilid_config::*; -use crate::xx::*; use crate::*; pub async fn test_startup_shutdown() { @@ -20,9 +19,9 @@ pub async fn test_attach_detach() { .await .expect("startup failed"); api.attach().await.unwrap(); - intf::sleep(5000).await; + sleep(5000).await; api.detach().await.unwrap(); - intf::sleep(2000).await; + sleep(2000).await; api.shutdown().await; info!("--- test auto detach ---"); @@ -31,7 +30,7 @@ pub async fn test_attach_detach() { .await .expect("startup failed"); api.attach().await.unwrap(); - intf::sleep(5000).await; + sleep(5000).await; api.shutdown().await; info!("--- test detach without attach ---"); diff --git a/veilid-core/src/tests/ios/mod.rs b/veilid-core/src/tests/ios/mod.rs new file mode 100644 index 00000000..fe4fc783 --- /dev/null +++ b/veilid-core/src/tests/ios/mod.rs @@ -0,0 +1,46 @@ +use super::native::*; +use crate::*; +use backtrace::Backtrace; +use std::panic; +use tracing_oslog::OsLogger; +use tracing_subscriber::prelude::*; + +#[no_mangle] +#[allow(dead_code)] +pub extern "C" fn run_veilid_core_tests() { + block_on(async { + veilid_core_setup_ios_tests(); + run_all_tests().await; + }); +} + +pub fn veilid_core_setup_ios_tests() { + // Set up subscriber and layers + let filter = VeilidLayerFilter::new(VeilidConfigLogLevel::Trace, None); + tracing_subscriber::registry() + .with(OsLogger::new("com.veilid.veilidcore-tests", "").with_filter(filter)) + .init(); + + panic::set_hook(Box::new(|panic_info| { + let bt = Backtrace::new(); + if let Some(location) = panic_info.location() { + error!( + "panic occurred in file '{}' at line {}", + location.file(), + location.line(), + ); + } else { + error!("panic occurred but can't get location information..."); + } + if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + error!("panic payload: {:?}", s); + } else if let Some(s) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", s); + } else if let Some(a) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", a); + } else { + error!("no panic payload"); + } + error!("Backtrace:\n{:?}", bt); + })); +} diff --git a/veilid-core/src/tests/ios/.gitignore b/veilid-core/src/tests/ios/veilidcore-tests/.gitignore similarity index 100% rename from veilid-core/src/tests/ios/.gitignore rename to veilid-core/src/tests/ios/veilidcore-tests/.gitignore diff --git a/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests.xcodeproj/project.pbxproj b/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests.xcodeproj/project.pbxproj index 9467bfd8..2b35a5b9 100644 --- a/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests.xcodeproj/project.pbxproj +++ b/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests.xcodeproj/project.pbxproj @@ -167,7 +167,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "../../../../ios_build.sh --features ios_tests\n"; + shellScript = "../../../../../scripts/ios_build.sh veilid_core --features veilid_core_ios_tests,rt-tokio\n"; }; /* End PBXShellScriptBuildPhase section */ @@ -209,7 +209,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = arm64; + ARCHS = "$(ARCHS_STANDARD)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -264,6 +264,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; @@ -271,7 +272,7 @@ isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; - ARCHS = arm64; + ARCHS = "$(ARCHS_STANDARD)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -319,6 +320,7 @@ SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; @@ -339,11 +341,11 @@ ); OTHER_LDFLAGS = ""; "OTHER_LDFLAGS[sdk=iphoneos*]" = ( - "-L../../../../../target/aarch64-apple-ios/debug", + "-L../../../../../target/lipo-ios/debug", "-lveilid_core", ); "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( - "-L../../../../../target/x86_64-apple-ios/debug", + "-L../../../../../target/lipo-ios-sim/debug", "-lveilid_core", ); PRODUCT_BUNDLE_IDENTIFIER = "com.veilid.veilidcore-tests"; @@ -371,11 +373,11 @@ ); OTHER_LDFLAGS = ""; "OTHER_LDFLAGS[sdk=iphoneos*]" = ( - "-L../../../../../target/aarch64-apple-ios/release", + "-L../../../../../target/lipo-ios/release", "-lveilid_core", ); "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( - "-L../../../../../target/x86_64-apple-ios/release", + "-L../../../../../target/lipo-ios-sim/release", "-lveilid_core", ); PRODUCT_BUNDLE_IDENTIFIER = "com.veilid.veilidcore-tests"; diff --git a/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests/ViewController.swift b/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests/ViewController.swift index dd663282..66ffe67e 100644 --- a/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests/ViewController.swift +++ b/veilid-core/src/tests/ios/veilidcore-tests/veilidcore-tests/ViewController.swift @@ -6,12 +6,14 @@ // import UIKit +import Darwin class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() run_veilid_core_tests() + exit(0) } diff --git a/veilid-core/src/tests/mod.rs b/veilid-core/src/tests/mod.rs index a4bdf876..2a050ac5 100644 --- a/veilid-core/src/tests/mod.rs +++ b/veilid-core/src/tests/mod.rs @@ -1,5 +1,14 @@ +#[cfg(all(target_os = "android", feature = "veilid_core_android_tests"))] +mod android; pub mod common; +#[cfg(all(target_os = "ios", feature = "veilid_core_ios_tests"))] +mod ios; #[cfg(not(target_arch = "wasm32"))] mod native; +#[allow(unused_imports)] use super::*; + +pub use common::*; +pub use crypto::tests::*; +pub use network_manager::tests::*; diff --git a/veilid-core/src/tests/native/mod.rs b/veilid-core/src/tests/native/mod.rs index d693eaa5..a05f1116 100644 --- a/veilid-core/src/tests/native/mod.rs +++ b/veilid-core/src/tests/native/mod.rs @@ -1,163 +1,73 @@ //! Test suite for Native #![cfg(not(target_arch = "wasm32"))] - -mod test_async_peek_stream; - use crate::crypto::tests::*; use crate::network_manager::tests::*; use crate::tests::common::*; -use crate::xx::*; - -#[cfg(all(target_os = "android", feature = "android_tests"))] -use jni::{objects::JClass, objects::JObject, JNIEnv}; - -#[cfg(all(target_os = "android", feature = "android_tests"))] -#[no_mangle] -#[allow(non_snake_case)] -pub extern "system" fn Java_com_veilid_veilidcore_veilidcore_1android_1tests_MainActivity_run_1tests( - env: JNIEnv, - _class: JClass, - ctx: JObject, -) { - crate::intf::utils::android::veilid_core_setup_android( - env, - ctx, - "veilid_core", - crate::veilid_config::VeilidConfigLogLevel::Trace, - ); - run_all_tests(); -} - -#[cfg(all(target_os = "ios", feature = "ios_tests"))] -#[no_mangle] -pub extern "C" fn run_veilid_core_tests() { - let log_path: std::path::PathBuf = [ - std::env::var("HOME").unwrap().as_str(), - "Documents", - "veilid-core.log", - ] - .iter() - .collect(); - crate::intf::utils::ios_test_setup::veilid_core_setup( - "veilid-core", - Some(Level::Trace), - Some((Level::Trace, log_path.as_path())), - ); - run_all_tests(); -} +use crate::*; /////////////////////////////////////////////////////////////////////////// #[allow(dead_code)] -pub fn run_all_tests() { - info!("TEST: exec_test_host_interface"); - exec_test_host_interface(); - info!("TEST: exec_test_dht_key"); - exec_test_dht_key(); - info!("TEST: exec_test_veilid_core"); - exec_test_veilid_core(); - info!("TEST: exec_test_veilid_config"); - exec_test_veilid_config(); - info!("TEST: exec_test_async_peek_stream"); - exec_test_async_peek_stream(); - info!("TEST: exec_test_connection_table"); - exec_test_connection_table(); - info!("TEST: exec_test_table_store"); - exec_test_table_store(); - info!("TEST: exec_test_protected_store"); - exec_test_protected_store(); - info!("TEST: exec_test_crypto"); - exec_test_crypto(); - info!("TEST: exec_test_envelope_receipt"); - exec_test_envelope_receipt(); - info!("TEST: exec_test_async_tag_lock"); - exec_test_async_tag_lock(); +pub async fn run_all_tests() { + info!("TEST: test_host_interface"); + test_host_interface::test_all().await; + info!("TEST: test_dht_key"); + test_dht_key::test_all().await; + info!("TEST: test_veilid_core"); + test_veilid_core::test_all().await; + info!("TEST: test_veilid_config"); + test_veilid_config::test_all().await; + info!("TEST: test_connection_table"); + test_connection_table::test_all().await; + info!("TEST: test_table_store"); + test_table_store::test_all().await; + info!("TEST: test_protected_store"); + test_protected_store::test_all().await; + info!("TEST: test_crypto"); + test_crypto::test_all().await; + info!("TEST: test_envelope_receipt"); + test_envelope_receipt::test_all().await; info!("Finished unit tests"); } +#[allow(dead_code)] #[cfg(feature = "rt-tokio")] -fn block_on, T>(f: F) -> T { +pub fn block_on, T>(f: F) -> T { let rt = tokio::runtime::Runtime::new().unwrap(); - let local = tokio::task::LocalSet::new(); - local.block_on(&rt, f) + rt.block_on(f) } + #[cfg(feature = "rt-async-std")] -fn block_on, T>(f: F) -> T { +#[allow(dead_code)] +pub fn block_on, T>(f: F) -> T { async_std::task::block_on(f) } -fn exec_test_host_interface() { - block_on(async { - test_host_interface::test_all().await; - }); -} -fn exec_test_dht_key() { - block_on(async { - test_dht_key::test_all().await; - }); -} -fn exec_test_veilid_core() { - block_on(async { - test_veilid_core::test_all().await; - }); -} -fn exec_test_veilid_config() { - block_on(async { - test_veilid_config::test_all().await; - }) -} -fn exec_test_async_peek_stream() { - block_on(async { - test_async_peek_stream::test_all().await; - }) -} -fn exec_test_connection_table() { - block_on(async { - test_connection_table::test_all().await; - }) -} -fn exec_test_table_store() { - block_on(async { - test_table_store::test_all().await; - }) -} -fn exec_test_protected_store() { - block_on(async { - test_protected_store::test_all().await; - }) -} -fn exec_test_crypto() { - block_on(async { - test_crypto::test_all().await; - }) -} -fn exec_test_envelope_receipt() { - block_on(async { - test_envelope_receipt::test_all().await; - }) -} -fn exec_test_async_tag_lock() { - block_on(async { - test_async_tag_lock::test_all().await; - }) -} /////////////////////////////////////////////////////////////////////////// cfg_if! { if #[cfg(test)] { use serial_test::serial; - use simplelog::*; use std::sync::Once; static SETUP_ONCE: Once = Once::new(); pub fn setup() { SETUP_ONCE.call_once(|| { - let mut cb = ConfigBuilder::new(); - for ig in crate::DEFAULT_LOG_IGNORE_LIST { - cb.add_filter_ignore_str(ig); + cfg_if! { + if #[cfg(feature = "tracing")] { + use tracing_subscriber::{filter, fmt, prelude::*}; + let mut filters = filter::Targets::new().with_default(filter::LevelFilter::TRACE); + for ig in DEFAULT_LOG_IGNORE_LIST { + filters = filters.with_target(ig, filter::LevelFilter::OFF); + } + let fmt_layer = fmt::layer(); + tracing_subscriber::registry() + .with(fmt_layer) + .with(filters) + .init(); + } } - TestLogger::init(LevelFilter::Trace, cb.build()).unwrap(); }); } @@ -165,77 +75,82 @@ cfg_if! { #[serial] fn run_test_host_interface() { setup(); - exec_test_host_interface(); + block_on(async { + test_host_interface::test_all().await; + }); } #[test] #[serial] fn run_test_dht_key() { setup(); - exec_test_dht_key(); + block_on(async { + test_dht_key::test_all().await; + }); } #[test] #[serial] fn run_test_veilid_core() { setup(); - exec_test_veilid_core(); + block_on(async { + test_veilid_core::test_all().await; + }); } #[test] #[serial] fn run_test_veilid_config() { setup(); - exec_test_veilid_config(); - } - - #[test] - #[serial] - fn run_test_async_peek_stream() { - setup(); - exec_test_async_peek_stream(); + block_on(async { + test_veilid_config::test_all().await; + }) } #[test] #[serial] fn run_test_connection_table() { setup(); - exec_test_connection_table(); + block_on(async { + test_connection_table::test_all().await; + }) } #[test] #[serial] fn run_test_table_store() { setup(); - exec_test_table_store(); + block_on(async { + test_table_store::test_all().await; + }) } #[test] #[serial] fn run_test_protected_store() { setup(); - exec_test_protected_store(); + block_on(async { + test_protected_store::test_all().await; + }) } #[test] #[serial] fn run_test_crypto() { setup(); - exec_test_crypto(); + block_on(async { + test_crypto::test_all().await; + }) } #[test] #[serial] fn run_test_envelope_receipt() { setup(); - exec_test_envelope_receipt(); + block_on(async { + test_envelope_receipt::test_all().await; + }) } - #[test] - #[serial] - fn run_test_async_tag_lock() { - setup(); - exec_test_async_tag_lock(); - } } } diff --git a/veilid-core/src/veilid_api/aligned_u64.rs b/veilid-core/src/veilid_api/aligned_u64.rs new file mode 100644 index 00000000..31c161aa --- /dev/null +++ b/veilid-core/src/veilid_api/aligned_u64.rs @@ -0,0 +1,122 @@ +use super::*; + +/// Aligned u64 +/// Required on 32-bit platforms for serialization because Rust aligns u64 on 4 byte boundaries +/// And zero-copy serialization with Rkyv requires 8-byte alignment + +#[derive( + Clone, + Default, + PartialEq, + Eq, + PartialOrd, + Ord, + Copy, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[repr(C, align(8))] +#[archive_attr(repr(C, align(8)), derive(CheckBytes))] +pub struct AlignedU64(u64); + +impl From for AlignedU64 { + fn from(v: u64) -> Self { + AlignedU64(v) + } +} +impl From for u64 { + fn from(v: AlignedU64) -> Self { + v.0 + } +} + +impl fmt::Display for AlignedU64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (&self.0 as &dyn fmt::Display).fmt(f) + } +} + +impl fmt::Debug for AlignedU64 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (&self.0 as &dyn fmt::Debug).fmt(f) + } +} + +impl FromStr for AlignedU64 { + type Err = ::Err; + fn from_str(s: &str) -> Result { + Ok(AlignedU64(u64::from_str(s)?)) + } +} + +impl> core::ops::Add for AlignedU64 { + type Output = Self; + + fn add(self, rhs: Rhs) -> Self { + Self(self.0 + rhs.into()) + } +} + +impl> core::ops::AddAssign for AlignedU64 { + fn add_assign(&mut self, rhs: Rhs) { + self.0 += rhs.into(); + } +} + +impl> core::ops::Sub for AlignedU64 { + type Output = Self; + + fn sub(self, rhs: Rhs) -> Self { + Self(self.0 - rhs.into()) + } +} + +impl> core::ops::SubAssign for AlignedU64 { + fn sub_assign(&mut self, rhs: Rhs) { + self.0 -= rhs.into(); + } +} + +impl> core::ops::Mul for AlignedU64 { + type Output = Self; + + fn mul(self, rhs: Rhs) -> Self { + Self(self.0 * rhs.into()) + } +} + +impl> core::ops::MulAssign for AlignedU64 { + fn mul_assign(&mut self, rhs: Rhs) { + self.0 *= rhs.into(); + } +} + +impl> core::ops::Div for AlignedU64 { + type Output = Self; + + fn div(self, rhs: Rhs) -> Self { + Self(self.0 / rhs.into()) + } +} + +impl> core::ops::DivAssign for AlignedU64 { + fn div_assign(&mut self, rhs: Rhs) { + self.0 /= rhs.into(); + } +} + +impl AlignedU64 { + pub const fn new(v: u64) -> Self { + Self(v) + } + pub fn as_u64(self) -> u64 { + self.0 + } + pub fn saturating_sub(self, rhs: Self) -> Self { + Self(self.0.saturating_sub(rhs.0)) + } +} diff --git a/veilid-core/src/veilid_api/api.rs b/veilid-core/src/veilid_api/api.rs new file mode 100644 index 00000000..a9a572e9 --- /dev/null +++ b/veilid-core/src/veilid_api/api.rs @@ -0,0 +1,288 @@ +use super::*; + +///////////////////////////////////////////////////////////////////////////////////////////////////// + +struct VeilidAPIInner { + context: Option, +} + +impl fmt::Debug for VeilidAPIInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "VeilidAPIInner") + } +} + +impl Drop for VeilidAPIInner { + fn drop(&mut self) { + if let Some(context) = self.context.take() { + spawn_detached(api_shutdown(context)); + } + } +} + +#[derive(Clone, Debug)] +pub struct VeilidAPI { + inner: Arc>, +} + +impl VeilidAPI { + #[instrument(skip_all)] + pub(crate) fn new(context: VeilidCoreContext) -> Self { + Self { + inner: Arc::new(Mutex::new(VeilidAPIInner { + context: Some(context), + })), + } + } + + #[instrument(skip_all)] + pub async fn shutdown(self) { + let context = { self.inner.lock().context.take() }; + if let Some(context) = context { + api_shutdown(context).await; + } + } + + pub fn is_shutdown(&self) -> bool { + self.inner.lock().context.is_none() + } + + //////////////////////////////////////////////////////////////// + // Accessors + pub fn config(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.config.clone()); + } + Err(VeilidAPIError::NotInitialized) + } + pub fn crypto(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.crypto.clone()); + } + Err(VeilidAPIError::NotInitialized) + } + pub fn table_store(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.table_store.clone()); + } + Err(VeilidAPIError::not_initialized()) + } + pub fn block_store(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.block_store.clone()); + } + Err(VeilidAPIError::not_initialized()) + } + pub fn protected_store(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.protected_store.clone()); + } + Err(VeilidAPIError::not_initialized()) + } + pub fn attachment_manager(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.attachment_manager.clone()); + } + Err(VeilidAPIError::not_initialized()) + } + pub fn network_manager(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.attachment_manager.network_manager()); + } + Err(VeilidAPIError::not_initialized()) + } + pub fn rpc_processor(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.attachment_manager.network_manager().rpc_processor()); + } + Err(VeilidAPIError::NotInitialized) + } + pub fn routing_table(&self) -> Result { + let inner = self.inner.lock(); + if let Some(context) = &inner.context { + return Ok(context.attachment_manager.network_manager().routing_table()); + } + Err(VeilidAPIError::NotInitialized) + } + + //////////////////////////////////////////////////////////////// + // Attach/Detach + + // get a full copy of the current state + pub async fn get_state(&self) -> Result { + let attachment_manager = self.attachment_manager()?; + let network_manager = attachment_manager.network_manager(); + let config = self.config()?; + + let attachment = attachment_manager.get_veilid_state(); + let network = network_manager.get_veilid_state(); + let config = config.get_veilid_state(); + + Ok(VeilidState { + attachment, + network, + config, + }) + } + + // get network connectedness + + // connect to the network + #[instrument(level = "debug", err, skip_all)] + pub async fn attach(&self) -> Result<(), VeilidAPIError> { + let attachment_manager = self.attachment_manager()?; + if !attachment_manager.attach().await { + apibail_generic!("Already attached"); + } + Ok(()) + } + + // disconnect from the network + #[instrument(level = "debug", err, skip_all)] + pub async fn detach(&self) -> Result<(), VeilidAPIError> { + let attachment_manager = self.attachment_manager()?; + if !attachment_manager.detach().await { + apibail_generic!("Already detached"); + } + Ok(()) + } + + //////////////////////////////////////////////////////////////// + // Routing Context + + #[instrument(level = "debug", skip(self))] + pub fn routing_context(&self) -> RoutingContext { + RoutingContext::new(self.clone()) + } + + //////////////////////////////////////////////////////////////// + // Private route allocation + + #[instrument(level = "debug", skip(self))] + pub async fn new_private_route(&self) -> Result<(DHTKey, Vec), VeilidAPIError> { + self.new_custom_private_route(Stability::default(), Sequencing::default()) + .await + } + + #[instrument(level = "debug", skip(self))] + pub async fn new_custom_private_route( + &self, + stability: Stability, + sequencing: Sequencing, + ) -> Result<(DHTKey, Vec), VeilidAPIError> { + let default_route_hop_count: usize = { + let config = self.config()?; + let c = config.get(); + c.network.rpc.default_route_hop_count.into() + }; + + let rss = self.routing_table()?.route_spec_store(); + let r = rss + .allocate_route( + stability, + sequencing, + default_route_hop_count, + Direction::Inbound.into(), + &[], + ) + .map_err(VeilidAPIError::internal)?; + let Some(pr_pubkey) = r else { + apibail_generic!("unable to allocate route"); + }; + if !rss + .test_route(&pr_pubkey) + .await + .map_err(VeilidAPIError::no_connection)? + { + rss.release_route(&pr_pubkey); + apibail_generic!("allocated route failed to test"); + } + let private_route = rss + .assemble_private_route(&pr_pubkey, Some(true)) + .map_err(VeilidAPIError::generic)?; + let blob = match RouteSpecStore::private_route_to_blob(&private_route) { + Ok(v) => v, + Err(e) => { + rss.release_route(&pr_pubkey); + apibail_internal!(e); + } + }; + + rss.mark_route_published(&pr_pubkey, true) + .map_err(VeilidAPIError::internal)?; + + Ok((pr_pubkey, blob)) + } + + #[instrument(level = "debug", skip(self))] + pub fn import_remote_private_route(&self, blob: Vec) -> Result { + let rss = self.routing_table()?.route_spec_store(); + rss.import_remote_private_route(blob) + .map_err(|e| VeilidAPIError::invalid_argument(e, "blob", "private route blob")) + } + + #[instrument(level = "debug", skip(self))] + pub fn release_private_route(&self, key: &DHTKey) -> Result<(), VeilidAPIError> { + let rss = self.routing_table()?.route_spec_store(); + if rss.release_route(key) { + Ok(()) + } else { + Err(VeilidAPIError::invalid_argument( + "release_private_route", + "key", + key, + )) + } + } + + //////////////////////////////////////////////////////////////// + // App Calls + + #[instrument(level = "debug", skip(self))] + pub async fn app_call_reply( + &self, + id: OperationId, + message: Vec, + ) -> Result<(), VeilidAPIError> { + let rpc_processor = self.rpc_processor()?; + rpc_processor + .app_call_reply(id, message) + .await + .map_err(|e| e.into()) + } + + //////////////////////////////////////////////////////////////// + // Tunnel Building + + #[instrument(level = "debug", err, skip(self))] + pub async fn start_tunnel( + &self, + _endpoint_mode: TunnelMode, + _depth: u8, + ) -> Result { + panic!("unimplemented"); + } + + #[instrument(level = "debug", err, skip(self))] + pub async fn complete_tunnel( + &self, + _endpoint_mode: TunnelMode, + _depth: u8, + _partial_tunnel: PartialTunnel, + ) -> Result { + panic!("unimplemented"); + } + + #[instrument(level = "debug", err, skip(self))] + pub async fn cancel_tunnel(&self, _tunnel_id: TunnelId) -> Result { + panic!("unimplemented"); + } +} diff --git a/veilid-core/src/veilid_api/debug.rs b/veilid-core/src/veilid_api/debug.rs index ef0ecef7..986a3c5d 100644 --- a/veilid-core/src/veilid_api/debug.rs +++ b/veilid-core/src/veilid_api/debug.rs @@ -32,15 +32,18 @@ fn get_string(text: &str) -> Option { fn get_route_id(rss: RouteSpecStore) -> impl Fn(&str) -> Option { return move |text: &str| { + if text.is_empty() { + return None; + } match DHTKey::try_decode(text).ok() { Some(key) => { - let routes = rss.list_routes(); + let routes = rss.list_allocated_routes(|k, _| Some(*k)); if routes.contains(&key) { return Some(key); } } None => { - let routes = rss.list_routes(); + let routes = rss.list_allocated_routes(|k, _| Some(*k)); for r in routes { let rkey = r.encode(); if rkey.starts_with(text) { @@ -53,18 +56,22 @@ fn get_route_id(rss: RouteSpecStore) -> impl Fn(&str) -> Option { }; } -fn get_safety_selection(text: &str, rss: RouteSpecStore) -> Option { +fn get_safety_selection(text: &str, routing_table: RoutingTable) -> Option { + let rss = routing_table.route_spec_store(); + let default_route_hop_count = + routing_table.with_config(|c| c.network.rpc.default_route_hop_count as usize); + if text.len() != 0 && &text[0..1] == "-" { // Unsafe let text = &text[1..]; - let seq = get_sequencing(text).unwrap_or(Sequencing::NoPreference); + let seq = get_sequencing(text).unwrap_or_default(); Some(SafetySelection::Unsafe(seq)) } else { // Safe let mut preferred_route = None; - let mut hop_count = 2; - let mut stability = Stability::LowLatency; - let mut sequencing = Sequencing::NoPreference; + let mut hop_count = default_route_hop_count; + let mut stability = Stability::default(); + let mut sequencing = Sequencing::default(); for x in text.split(",") { let x = x.trim(); if let Some(pr) = get_route_id(rss.clone())(x) { @@ -111,7 +118,7 @@ fn get_destination(routing_table: RoutingTable) -> impl FnOnce(&str) -> Option impl FnOnce(&str) -> Option { - // Remove imported route - dc.imported_routes.remove(n); - info!("removed dead imported route {}", n); - return None; - } - Ok(v) => v, + let Some(private_route) = rss.get_remote_private_route(&pr_pubkey) else { + // Remove imported route + dc.imported_routes.remove(n); + info!("removed dead imported route {}", n); + return None; }; Some(Destination::private_route( private_route, - ss.unwrap_or(SafetySelection::Unsafe(Sequencing::NoPreference)), + ss.unwrap_or(SafetySelection::Unsafe(Sequencing::default())), )) } else { let (text, mods) = text @@ -279,15 +283,10 @@ fn get_debug_argument Option>( argument: &str, getter: G, ) -> Result { - if let Some(val) = getter(value) { - Ok(val) - } else { - Err(VeilidAPIError::InvalidArgument { - context: context.to_owned(), - argument: argument.to_owned(), - value: value.to_owned(), - }) - } + let Some(val) = getter(value) else { + apibail_invalid_argument!(context, argument, value); + }; + Ok(val) } fn get_debug_argument_at Option>( debug_args: &[String], @@ -297,21 +296,13 @@ fn get_debug_argument_at Option>( getter: G, ) -> Result { if pos >= debug_args.len() { - return Err(VeilidAPIError::MissingArgument { - context: context.to_owned(), - argument: argument.to_owned(), - }); + apibail_missing_argument!(context, argument); } let value = &debug_args[pos]; - if let Some(val) = getter(value) { - Ok(val) - } else { - Err(VeilidAPIError::InvalidArgument { - context: context.to_owned(), - argument: argument.to_owned(), - value: value.to_owned(), - }) - } + let Some(val) = getter(value) else { + apibail_invalid_argument!(context, argument, value); + }; + Ok(val) } impl VeilidAPI { @@ -354,11 +345,7 @@ impl VeilidAPI { } else if let Some(lim) = get_number(&arg) { limit = lim; } else { - return Err(VeilidAPIError::InvalidArgument { - context: "debug_entries".to_owned(), - argument: "unknown".to_owned(), - value: arg, - }); + apibail_invalid_argument!("debug_entries", "unknown", arg); } } @@ -415,7 +402,7 @@ impl VeilidAPI { async fn debug_restart(&self, args: String) -> Result { let args = args.trim_start(); if args.is_empty() { - return Err(VeilidAPIError::missing_argument("debug_restart", "arg_0")); + apibail_missing_argument!("debug_restart", "arg_0"); } let (arg, _rest) = args.split_once(' ').unwrap_or((args, "")); // let rest = rest.trim_start().to_owned(); @@ -434,11 +421,7 @@ impl VeilidAPI { Ok("Network restarted".to_owned()) } else { - Err(VeilidAPIError::invalid_argument( - "debug_restart", - "arg_1", - arg, - )) + apibail_invalid_argument!("debug_restart", "arg_1", arg); } } @@ -469,6 +452,10 @@ impl VeilidAPI { Ok("Connections purged".to_owned()) } else if args[0] == "routes" { // Purge route spec store + { + let mut dc = DEBUG_CACHE.lock(); + dc.imported_routes.clear(); + } let rss = self.network_manager()?.routing_table().route_spec_store(); match rss.purge().await { Ok(_) => Ok("Routes purged".to_owned()), @@ -561,6 +548,9 @@ impl VeilidAPI { NetworkResult::Timeout => { return Ok("Timeout".to_owned()); } + NetworkResult::ServiceUnavailable => { + return Ok("ServiceUnavailable".to_owned()); + } NetworkResult::NoConnection(e) => { return Ok(format!("NoConnection({})", e)); } @@ -588,8 +578,8 @@ impl VeilidAPI { }; let mut ai = 1; - let mut sequencing = Sequencing::NoPreference; - let mut stability = Stability::LowLatency; + let mut sequencing = Sequencing::default(); + let mut stability = Stability::default(); let mut hop_count = default_route_hop_count; let mut directions = DirectionSet::all(); @@ -636,11 +626,9 @@ impl VeilidAPI { let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_dht_key)?; // Release route - let out = match rss.release_route(route_id) { - Ok(()) => format!("Released"), - Err(e) => { - format!("Route release failed: {}", e) - } + let out = match rss.release_route(&route_id) { + true => "Released".to_owned(), + false => "Route does not exist".to_owned(), }; Ok(out) @@ -659,11 +647,7 @@ impl VeilidAPI { if full_val == "full" { true } else { - return Err(VeilidAPIError::invalid_argument( - "debug_route", - "full", - full_val, - )); + apibail_invalid_argument!("debug_route", "full", full_val); } } else { false @@ -730,11 +714,21 @@ impl VeilidAPI { let routing_table = netman.routing_table(); let rss = routing_table.route_spec_store(); - let routes = rss.list_routes(); - let mut out = format!("Routes: (count = {}):\n", routes.len()); + let routes = rss.list_allocated_routes(|k, _| Some(*k)); + let mut out = format!("Allocated Routes: (count = {}):\n", routes.len()); for r in routes { out.push_str(&format!("{}\n", r.encode())); } + + let remote_routes = rss.list_remote_routes(|k, _| Some(*k)); + out.push_str(&format!( + "Remote Routes: (count = {}):\n", + remote_routes.len() + )); + for r in remote_routes { + out.push_str(&format!("{}\n", r.encode())); + } + Ok(out) } async fn debug_route_import(&self, args: Vec) -> Result { @@ -757,8 +751,25 @@ impl VeilidAPI { return Ok(out); } - async fn debug_route_test(&self, _args: Vec) -> Result { - let out = "xxx".to_string(); + async fn debug_route_test(&self, args: Vec) -> Result { + // + let netman = self.network_manager()?; + let routing_table = netman.routing_table(); + let rss = routing_table.route_spec_store(); + + let route_id = get_debug_argument_at(&args, 1, "debug_route", "route_id", get_dht_key)?; + + let success = rss + .test_route(&route_id) + .await + .map_err(VeilidAPIError::internal)?; + + let out = if success { + "SUCCESS".to_owned() + } else { + "FAILED".to_owned() + }; + return Ok(out); } @@ -828,46 +839,49 @@ impl VeilidAPI { } pub async fn debug(&self, args: String) -> Result { - let args = args.trim_start(); - if args.is_empty() { - // No arguments runs help command - return self.debug_help("".to_owned()).await; - } - let (arg, rest) = args.split_once(' ').unwrap_or((args, "")); - let rest = rest.trim_start().to_owned(); + let res = { + let args = args.trim_start(); + if args.is_empty() { + // No arguments runs help command + return self.debug_help("".to_owned()).await; + } + let (arg, rest) = args.split_once(' ').unwrap_or((args, "")); + let rest = rest.trim_start().to_owned(); - if arg == "help" { - self.debug_help(rest).await - } else if arg == "buckets" { - self.debug_buckets(rest).await - } else if arg == "dialinfo" { - self.debug_dialinfo(rest).await - } else if arg == "txtrecord" { - self.debug_txtrecord(rest).await - } else if arg == "entries" { - self.debug_entries(rest).await - } else if arg == "entry" { - self.debug_entry(rest).await - } else if arg == "ping" { - self.debug_ping(rest).await - } else if arg == "contact" { - self.debug_contact(rest).await - } else if arg == "nodeinfo" { - self.debug_nodeinfo(rest).await - } else if arg == "purge" { - self.debug_purge(rest).await - } else if arg == "attach" { - self.debug_attach(rest).await - } else if arg == "detach" { - self.debug_detach(rest).await - } else if arg == "config" { - self.debug_config(rest).await - } else if arg == "restart" { - self.debug_restart(rest).await - } else if arg == "route" { - self.debug_route(rest).await - } else { - Ok(">>> Unknown command\n".to_owned()) - } + if arg == "help" { + self.debug_help(rest).await + } else if arg == "buckets" { + self.debug_buckets(rest).await + } else if arg == "dialinfo" { + self.debug_dialinfo(rest).await + } else if arg == "txtrecord" { + self.debug_txtrecord(rest).await + } else if arg == "entries" { + self.debug_entries(rest).await + } else if arg == "entry" { + self.debug_entry(rest).await + } else if arg == "ping" { + self.debug_ping(rest).await + } else if arg == "contact" { + self.debug_contact(rest).await + } else if arg == "nodeinfo" { + self.debug_nodeinfo(rest).await + } else if arg == "purge" { + self.debug_purge(rest).await + } else if arg == "attach" { + self.debug_attach(rest).await + } else if arg == "detach" { + self.debug_detach(rest).await + } else if arg == "config" { + self.debug_config(rest).await + } else if arg == "restart" { + self.debug_restart(rest).await + } else if arg == "route" { + self.debug_route(rest).await + } else { + Err(VeilidAPIError::generic("Unknown debug command")) + } + }; + res } } diff --git a/veilid-core/src/veilid_api/error.rs b/veilid-core/src/veilid_api/error.rs new file mode 100644 index 00000000..aece21d4 --- /dev/null +++ b/veilid-core/src/veilid_api/error.rs @@ -0,0 +1,199 @@ +use super::*; + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_timeout { + () => { + return Err(VeilidAPIError::timeout()) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_try_again { + () => { + return Err(VeilidAPIError::try_again()) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_generic { + ($x:expr) => { + return Err(VeilidAPIError::generic($x)) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_internal { + ($x:expr) => { + return Err(VeilidAPIError::internal($x)) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_parse_error { + ($x:expr, $y:expr) => { + return Err(VeilidAPIError::parse_error($x, $y)) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_missing_argument { + ($x:expr, $y:expr) => { + return Err(VeilidAPIError::missing_argument($x, $y)) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_invalid_argument { + ($x:expr, $y:expr, $z:expr) => { + return Err(VeilidAPIError::invalid_argument($x, $y, $z)) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_no_connection { + ($x:expr) => { + return Err(VeilidAPIError::no_connection($x)) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_key_not_found { + ($x:expr) => { + return Err(VeilidAPIError::key_not_found($x)) + }; +} + +#[allow(unused_macros)] +#[macro_export] +macro_rules! apibail_already_initialized { + () => { + return Err(VeilidAPIError::already_initialized()) + }; +} + +#[derive( + ThisError, + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +#[serde(tag = "kind")] +pub enum VeilidAPIError { + #[error("Not initialized")] + NotInitialized, + #[error("Already initialized")] + AlreadyInitialized, + #[error("Timeout")] + Timeout, + #[error("TryAgain")] + TryAgain, + #[error("Shutdown")] + Shutdown, + #[error("Key not found: {key}")] + KeyNotFound { key: DHTKey }, + #[error("No connection: {message}")] + NoConnection { message: String }, + #[error("No peer info: {node_id}")] + NoPeerInfo { node_id: NodeId }, + #[error("Internal: {message}")] + Internal { message: String }, + #[error("Unimplemented: {message}")] + Unimplemented { message: String }, + #[error("Parse error: '{message}' with value '{value}'")] + ParseError { message: String, value: String }, + #[error("Invalid argument: '{argument}' for '{context}' with value '{value}'")] + InvalidArgument { + context: String, + argument: String, + value: String, + }, + #[error("Missing argument: '{argument}' for '{context}'")] + MissingArgument { context: String, argument: String }, + #[error("Generic: {message}")] + Generic { message: String }, +} + +impl VeilidAPIError { + pub fn not_initialized() -> Self { + Self::NotInitialized + } + pub fn already_initialized() -> Self { + Self::AlreadyInitialized + } + pub fn timeout() -> Self { + Self::Timeout + } + pub fn try_again() -> Self { + Self::TryAgain + } + pub fn shutdown() -> Self { + Self::Shutdown + } + pub fn key_not_found(key: DHTKey) -> Self { + Self::KeyNotFound { key } + } + pub fn no_connection(msg: T) -> Self { + Self::NoConnection { + message: msg.to_string(), + } + } + pub fn no_peer_info(node_id: NodeId) -> Self { + Self::NoPeerInfo { node_id } + } + pub fn internal(msg: T) -> Self { + Self::Internal { + message: msg.to_string(), + } + } + pub fn unimplemented(msg: T) -> Self { + Self::Unimplemented { + message: msg.to_string(), + } + } + pub fn parse_error(msg: T, value: S) -> Self { + Self::ParseError { + message: msg.to_string(), + value: value.to_string(), + } + } + pub fn invalid_argument( + context: T, + argument: S, + value: R, + ) -> Self { + Self::InvalidArgument { + context: context.to_string(), + argument: argument.to_string(), + value: value.to_string(), + } + } + pub fn missing_argument(context: T, argument: S) -> Self { + Self::MissingArgument { + context: context.to_string(), + argument: argument.to_string(), + } + } + pub fn generic(msg: T) -> Self { + Self::Generic { + message: msg.to_string(), + } + } +} diff --git a/veilid-core/src/veilid_api/mod.rs b/veilid-core/src/veilid_api/mod.rs index 605fcf90..a63adbb2 100644 --- a/veilid-core/src/veilid_api/mod.rs +++ b/veilid-core/src/veilid_api/mod.rs @@ -1,19 +1,21 @@ #![allow(dead_code)] +mod aligned_u64; +mod api; mod debug; +mod error; mod routing_context; mod serialize_helpers; +mod types; +pub use aligned_u64::*; +pub use api::*; pub use debug::*; +pub use error::*; pub use routing_context::*; pub use serialize_helpers::*; +pub use types::*; -use crate::*; - -pub use crate::xx::{ - IpAddr, Ipv4Addr, Ipv6Addr, SendPinBoxFuture, SocketAddr, SocketAddrV4, SocketAddrV6, - ToSocketAddrs, -}; pub use alloc::string::ToString; pub use attachment_manager::AttachmentManager; pub use core::str::FromStr; @@ -21,10 +23,11 @@ pub use crypto::Crypto; pub use crypto::{generate_secret, sign, verify, DHTKey, DHTKeySecret, DHTSignature, Nonce}; pub use intf::BlockStore; pub use intf::ProtectedStore; -pub use intf::TableStore; +pub use intf::{TableDB, TableDBTransaction, TableStore}; pub use network_manager::NetworkManager; pub use routing_table::{NodeRef, NodeRefBase}; +use crate::*; use core::fmt; use core_context::{api_shutdown, VeilidCoreContext}; use enumset::*; @@ -32,2808 +35,5 @@ use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as use routing_table::{RouteSpecStore, RoutingTable}; use rpc_processor::*; use serde::*; -use xx::*; ///////////////////////////////////////////////////////////////////////////////////////////////////// - -#[allow(unused_macros)] -#[macro_export] -macro_rules! apibail_generic { - ($x:expr) => { - return Err(VeilidAPIError::generic($x)) - }; -} - -#[allow(unused_macros)] -#[macro_export] -macro_rules! apibail_internal { - ($x:expr) => { - return Err(VeilidAPIError::internal($x)) - }; -} - -#[allow(unused_macros)] -#[macro_export] -macro_rules! apibail_parse { - ($x:expr, $y:expr) => { - return Err(VeilidAPIError::parse_error($x, $y)) - }; -} - -#[derive( - ThisError, - Clone, - Debug, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -#[serde(tag = "kind")] -pub enum VeilidAPIError { - #[error("Not initialized")] - NotInitialized, - #[error("Already initialized")] - AlreadyInitialized, - #[error("Timeout")] - Timeout, - #[error("Shutdown")] - Shutdown, - #[error("Key not found: {key}")] - KeyNotFound { key: DHTKey }, - #[error("No connection: {message}")] - NoConnection { message: String }, - #[error("No peer info: {node_id}")] - NoPeerInfo { node_id: NodeId }, - #[error("Internal: {message}")] - Internal { message: String }, - #[error("Unimplemented: {message}")] - Unimplemented { message: String }, - #[error("Parse error: '{message}' with value '{value}'")] - ParseError { message: String, value: String }, - #[error("Invalid argument: '{argument}' for '{context}' with value '{value}'")] - InvalidArgument { - context: String, - argument: String, - value: String, - }, - #[error("Missing argument: '{argument}' for '{context}'")] - MissingArgument { context: String, argument: String }, - #[error("Generic: {message}")] - Generic { message: String }, -} - -impl VeilidAPIError { - pub fn not_initialized() -> Self { - Self::NotInitialized - } - pub fn already_initialized() -> Self { - Self::AlreadyInitialized - } - pub fn timeout() -> Self { - Self::Timeout - } - pub fn shutdown() -> Self { - Self::Shutdown - } - pub fn key_not_found(key: DHTKey) -> Self { - Self::KeyNotFound { key } - } - pub fn no_connection(msg: T) -> Self { - Self::NoConnection { - message: msg.to_string(), - } - } - pub fn no_peer_info(node_id: NodeId) -> Self { - Self::NoPeerInfo { node_id } - } - pub fn internal(msg: T) -> Self { - Self::Internal { - message: msg.to_string(), - } - } - pub fn unimplemented(msg: T) -> Self { - Self::Unimplemented { - message: msg.to_string(), - } - } - pub fn parse_error(msg: T, value: S) -> Self { - Self::ParseError { - message: msg.to_string(), - value: value.to_string(), - } - } - pub fn invalid_argument( - context: T, - argument: S, - value: R, - ) -> Self { - Self::InvalidArgument { - context: context.to_string(), - argument: argument.to_string(), - value: value.to_string(), - } - } - pub fn missing_argument(context: T, argument: S) -> Self { - Self::MissingArgument { - context: context.to_string(), - argument: argument.to_string(), - } - } - pub fn generic(msg: T) -> Self { - Self::Generic { - message: msg.to_string(), - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -#[derive( - Debug, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Copy, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum VeilidLogLevel { - Error = 1, - Warn, - Info, - Debug, - Trace, -} - -impl VeilidLogLevel { - pub fn from_tracing_level(level: tracing::Level) -> VeilidLogLevel { - match level { - tracing::Level::ERROR => VeilidLogLevel::Error, - tracing::Level::WARN => VeilidLogLevel::Warn, - tracing::Level::INFO => VeilidLogLevel::Info, - tracing::Level::DEBUG => VeilidLogLevel::Debug, - tracing::Level::TRACE => VeilidLogLevel::Trace, - } - } - pub fn from_log_level(level: log::Level) -> VeilidLogLevel { - match level { - log::Level::Error => VeilidLogLevel::Error, - log::Level::Warn => VeilidLogLevel::Warn, - log::Level::Info => VeilidLogLevel::Info, - log::Level::Debug => VeilidLogLevel::Debug, - log::Level::Trace => VeilidLogLevel::Trace, - } - } - pub fn to_tracing_level(&self) -> tracing::Level { - match self { - Self::Error => tracing::Level::ERROR, - Self::Warn => tracing::Level::WARN, - Self::Info => tracing::Level::INFO, - Self::Debug => tracing::Level::DEBUG, - Self::Trace => tracing::Level::TRACE, - } - } - pub fn to_log_level(&self) -> log::Level { - match self { - Self::Error => log::Level::Error, - Self::Warn => log::Level::Warn, - Self::Info => log::Level::Info, - Self::Debug => log::Level::Debug, - Self::Trace => log::Level::Trace, - } - } -} - -impl fmt::Display for VeilidLogLevel { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let text = match self { - Self::Error => "ERROR", - Self::Warn => "WARN", - Self::Info => "INFO", - Self::Debug => "DEBUG", - Self::Trace => "TRACE", - }; - write!(f, "{}", text) - } -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidLog { - pub log_level: VeilidLogLevel, - pub message: String, - pub backtrace: Option, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -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")] - pub sender: Option, - /// The content of the message to deliver to the application - #[serde(with = "json_as_base64")] - pub message: Vec, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -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")] - pub sender: Option, - /// The content of the request to deliver to the application - #[serde(with = "json_as_base64")] - pub message: Vec, - /// The id to reply to - #[serde(with = "json_as_string")] - pub id: u64, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidStateAttachment { - pub state: AttachmentState, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerTableData { - pub node_id: DHTKey, - pub peer_address: PeerAddress, - pub peer_stats: PeerStats, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidStateNetwork { - pub started: bool, - #[serde(with = "json_as_string")] - pub bps_down: u64, - #[serde(with = "json_as_string")] - pub bps_up: u64, - pub peers: Vec, -} - -#[derive( - Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidStateConfig { - pub config: VeilidConfigInner, -} - -#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -#[serde(tag = "kind")] -pub enum VeilidUpdate { - Log(VeilidLog), - AppMessage(VeilidAppMessage), - AppCall(VeilidAppCall), - Attachment(VeilidStateAttachment), - Network(VeilidStateNetwork), - Config(VeilidStateConfig), - Shutdown, -} - -#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct VeilidState { - pub attachment: VeilidStateAttachment, - pub network: VeilidStateNetwork, - pub config: VeilidStateConfig, -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -/// -#[derive( - Clone, - Debug, - Default, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct NodeId { - pub key: DHTKey, -} -impl NodeId { - pub fn new(key: DHTKey) -> Self { - Self { key } - } -} -impl fmt::Display for NodeId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", self.key.encode()) - } -} -impl FromStr for NodeId { - type Err = VeilidAPIError; - fn from_str(s: &str) -> Result { - Ok(Self { - key: DHTKey::try_decode(s)?, - }) - } -} - -#[derive( - Clone, - Debug, - Default, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct ValueKey { - pub key: DHTKey, - pub subkey: Option, -} -impl ValueKey { - pub fn new(key: DHTKey) -> Self { - Self { key, subkey: None } - } - pub fn new_subkey(key: DHTKey, subkey: String) -> Self { - Self { - key, - subkey: if subkey.is_empty() { - None - } else { - Some(subkey) - }, - } - } -} - -#[derive( - Clone, - Debug, - Default, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct ValueData { - pub data: Vec, - pub seq: u32, -} -impl ValueData { - pub fn new(data: Vec) -> Self { - Self { data, seq: 0 } - } - pub fn new_with_seq(data: Vec, seq: u32) -> Self { - Self { data, seq } - } - pub fn change(&mut self, data: Vec) { - self.data = data; - self.seq += 1; - } -} - -#[derive( - Clone, - Debug, - Default, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct BlockId { - pub key: DHTKey, -} -impl BlockId { - pub fn new(key: DHTKey) -> Self { - Self { key } - } -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -// Keep member order appropriate for sorting < preference -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum DialInfoClass { - Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port - Mapped = 1, // M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port - FullConeNAT = 2, // F = Directly reachable device without portmap behind full-cone NAT - Blocked = 3, // B = Inbound blocked at firewall but may hole punch with public address - AddressRestrictedNAT = 4, // A = Device without portmap behind address-only restricted NAT - PortRestrictedNAT = 5, // P = Device without portmap behind address-and-port restricted NAT -} - -impl DialInfoClass { - // Is a signal required to do an inbound hole-punch? - pub fn requires_signal(&self) -> bool { - matches!( - self, - Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT - ) - } - - // Does a relay node need to be allocated for this dial info? - // For full cone NAT, the relay itself may not be used but the keepalive sent to it - // is required to keep the NAT mapping valid in the router state table - pub fn requires_relay(&self) -> bool { - matches!( - self, - Self::FullConeNAT - | Self::Blocked - | Self::AddressRestrictedNAT - | Self::PortRestrictedNAT - ) - } -} - -// Ordering here matters, >= is used to check strength of sequencing requirement -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Sequencing { - NoPreference, - PreferOrdered, - EnsureOrdered, -} - -// Ordering here matters, >= is used to check strength of stability requirement -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Stability { - LowLatency, - Reliable, -} - -/// The choice of safety route to include in compiled routes -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum SafetySelection { - /// Don't use a safety route, only specify the sequencing preference - Unsafe(Sequencing), - /// Use a safety route and parameters specified by a SafetySpec - Safe(SafetySpec), -} - -/// Options for safety routes (sender privacy) -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SafetySpec { - /// preferred safety route if it still exists - pub preferred_route: Option, - /// must be greater than 0 - pub hop_count: usize, - /// prefer reliability over speed - pub stability: Stability, - /// prefer connection-oriented sequenced protocols - pub sequencing: Sequencing, -} - -// Keep member order appropriate for sorting < preference -#[derive( - Debug, - Clone, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoDetail { - pub class: DialInfoClass, - pub dial_info: DialInfo, -} - -impl MatchesDialInfoFilter for DialInfoDetail { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool { - self.dial_info.matches_filter(filter) - } -} - -impl DialInfoDetail { - pub fn ordered_sequencing_sort(a: &DialInfoDetail, b: &DialInfoDetail) -> core::cmp::Ordering { - if a.class < b.class { - return core::cmp::Ordering::Less; - } - if a.class > b.class { - return core::cmp::Ordering::Greater; - } - DialInfo::ordered_sequencing_sort(&a.dial_info, &b.dial_info) - } - pub const NO_SORT: std::option::Option< - for<'r, 's> fn( - &'r veilid_api::DialInfoDetail, - &'s veilid_api::DialInfoDetail, - ) -> std::cmp::Ordering, - > = None:: core::cmp::Ordering>; -} - -#[derive( - Copy, - Clone, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum NetworkClass { - InboundCapable = 0, // I = Inbound capable without relay, may require signal - OutboundOnly = 1, // O = Outbound only, inbound relay required except with reverse connect signal - WebApp = 2, // W = PWA, outbound relay is required in most cases - Invalid = 3, // X = Invalid network class, we don't know how to reach this node -} - -impl Default for NetworkClass { - fn default() -> Self { - Self::Invalid - } -} - -impl NetworkClass { - // Should an outbound relay be kept available? - pub fn outbound_wants_relay(&self) -> bool { - matches!(self, Self::WebApp) - } -} - -/// RoutingDomain-specific status for each node -/// is returned by the StatusA call - -/// PublicInternet RoutingDomain Status -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PublicInternetNodeStatus { - pub will_route: bool, - pub will_tunnel: bool, - pub will_signal: bool, - pub will_relay: bool, - pub will_validate_dial_info: bool, -} - -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct LocalNetworkNodeStatus { - pub will_relay: bool, - pub will_validate_dial_info: bool, -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum NodeStatus { - PublicInternet(PublicInternetNodeStatus), - LocalNetwork(LocalNetworkNodeStatus), -} - -impl NodeStatus { - pub fn will_route(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_route, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_tunnel(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_tunnel, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_signal(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_signal, - NodeStatus::LocalNetwork(_) => false, - } - } - pub fn will_relay(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_relay, - NodeStatus::LocalNetwork(ln) => ln.will_relay, - } - } - pub fn will_validate_dial_info(&self) -> bool { - match self { - NodeStatus::PublicInternet(pi) => pi.will_validate_dial_info, - NodeStatus::LocalNetwork(ln) => ln.will_validate_dial_info, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct NodeInfo { - pub network_class: NetworkClass, - #[with(RkyvEnumSet)] - pub outbound_protocols: ProtocolTypeSet, - #[with(RkyvEnumSet)] - pub address_types: AddressTypeSet, - pub min_version: u8, - pub max_version: u8, - pub dial_info_detail_list: Vec, -} - -impl NodeInfo { - pub fn first_filtered_dial_info_detail( - &self, - sort: Option, - filter: F, - ) -> Option - where - S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, - F: Fn(&DialInfoDetail) -> bool, - { - if let Some(sort) = sort { - let mut dids = self.dial_info_detail_list.clone(); - dids.sort_by(sort); - for did in dids { - if filter(&did) { - return Some(did); - } - } - } else { - for did in &self.dial_info_detail_list { - if filter(did) { - return Some(did.clone()); - } - } - }; - None - } - - pub fn all_filtered_dial_info_details( - &self, - sort: Option, - filter: F, - ) -> Vec - where - S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, - F: Fn(&DialInfoDetail) -> bool, - { - let mut dial_info_detail_list = Vec::new(); - - if let Some(sort) = sort { - let mut dids = self.dial_info_detail_list.clone(); - dids.sort_by(sort); - for did in dids { - if filter(&did) { - dial_info_detail_list.push(did); - } - } - } else { - for did in &self.dial_info_detail_list { - if filter(did) { - dial_info_detail_list.push(did.clone()); - } - } - }; - dial_info_detail_list - } - - /// Does this node has some dial info - pub fn has_dial_info(&self) -> bool { - !self.dial_info_detail_list.is_empty() - } - - /// Is some relay required either for signal or inbound relay or outbound relay? - pub fn requires_relay(&self) -> bool { - match self.network_class { - NetworkClass::InboundCapable => { - for did in &self.dial_info_detail_list { - if did.class.requires_relay() { - return true; - } - } - } - NetworkClass::OutboundOnly => { - return true; - } - NetworkClass::WebApp => { - return true; - } - NetworkClass::Invalid => {} - } - false - } - - /// Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. - pub fn can_signal(&self) -> bool { - // Must be inbound capable - if !matches!(self.network_class, NetworkClass::InboundCapable) { - return false; - } - // Do any of our dial info require signalling? if so, we can't offer signalling - for did in &self.dial_info_detail_list { - if did.class.requires_signal() { - return false; - } - } - true - } - - /// Can this node relay be an inbound relay? - pub fn can_inbound_relay(&self) -> bool { - // For now this is the same - self.can_signal() - } - - /// Is this node capable of validating dial info - pub fn can_validate_dial_info(&self) -> bool { - // For now this is the same - self.can_signal() - } -} - -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Direction { - Inbound, - Outbound, -} -pub type DirectionSet = EnumSet; - -// Keep member order appropriate for sorting < preference -// Must match DialInfo order -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum LowLevelProtocolType { - UDP, - TCP, -} - -impl LowLevelProtocolType { - pub fn is_connection_oriented(&self) -> bool { - matches!(self, LowLevelProtocolType::TCP) - } -} -pub type LowLevelProtocolTypeSet = EnumSet; - -// Keep member order appropriate for sorting < preference -// Must match DialInfo order -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum ProtocolType { - UDP, - TCP, - WS, - WSS, -} - -impl ProtocolType { - pub fn is_connection_oriented(&self) -> bool { - matches!( - self, - ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS - ) - } - pub fn low_level_protocol_type(&self) -> LowLevelProtocolType { - match self { - ProtocolType::UDP => LowLevelProtocolType::UDP, - ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS => LowLevelProtocolType::TCP, - } - } - pub fn sort_order(&self, sequencing: Sequencing) -> usize { - match self { - ProtocolType::UDP => { - if sequencing != Sequencing::NoPreference { - 3 - } else { - 0 - } - } - ProtocolType::TCP => { - if sequencing != Sequencing::NoPreference { - 0 - } else { - 1 - } - } - ProtocolType::WS => { - if sequencing != Sequencing::NoPreference { - 1 - } else { - 2 - } - } - ProtocolType::WSS => { - if sequencing != Sequencing::NoPreference { - 2 - } else { - 3 - } - } - } - } - pub fn all_ordered_set() -> ProtocolTypeSet { - ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS - } -} - -pub type ProtocolTypeSet = EnumSet; - -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, - EnumSetType, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum AddressType { - IPV4, - IPV6, -} -pub type AddressTypeSet = EnumSet; - -// Routing domain here is listed in order of preference, keep in order -#[allow(clippy::derive_hash_xor_eq)] -#[derive( - Debug, - Ord, - PartialOrd, - Hash, - EnumSetType, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[enumset(repr = "u8")] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum RoutingDomain { - LocalNetwork = 0, - PublicInternet = 1, -} -impl RoutingDomain { - pub const fn count() -> usize { - 2 - } - pub const fn all() -> [RoutingDomain; RoutingDomain::count()] { - // Routing domain here is listed in order of preference, keep in order - [RoutingDomain::LocalNetwork, RoutingDomain::PublicInternet] - } -} -pub type RoutingDomainSet = EnumSet; - -#[derive( - Copy, - Clone, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum Address { - IPV4(Ipv4Addr), - IPV6(Ipv6Addr), -} - -impl Default for Address { - fn default() -> Self { - Address::IPV4(Ipv4Addr::new(0, 0, 0, 0)) - } -} - -impl Address { - pub fn from_socket_addr(sa: SocketAddr) -> Address { - match sa { - SocketAddr::V4(v4) => Address::IPV4(*v4.ip()), - SocketAddr::V6(v6) => Address::IPV6(*v6.ip()), - } - } - pub fn from_ip_addr(addr: IpAddr) -> Address { - match addr { - IpAddr::V4(v4) => Address::IPV4(v4), - IpAddr::V6(v6) => Address::IPV6(v6), - } - } - pub fn address_type(&self) -> AddressType { - match self { - Address::IPV4(_) => AddressType::IPV4, - Address::IPV6(_) => AddressType::IPV6, - } - } - pub fn address_string(&self) -> String { - match self { - Address::IPV4(v4) => v4.to_string(), - Address::IPV6(v6) => v6.to_string(), - } - } - pub fn address_string_with_port(&self, port: u16) -> String { - match self { - Address::IPV4(v4) => format!("{}:{}", v4, port), - Address::IPV6(v6) => format!("[{}]:{}", v6, port), - } - } - pub fn is_unspecified(&self) -> bool { - match self { - Address::IPV4(v4) => ipv4addr_is_unspecified(v4), - Address::IPV6(v6) => ipv6addr_is_unspecified(v6), - } - } - pub fn is_global(&self) -> bool { - match self { - Address::IPV4(v4) => ipv4addr_is_global(v4) && !ipv4addr_is_multicast(v4), - Address::IPV6(v6) => ipv6addr_is_unicast_global(v6), - } - } - pub fn is_local(&self) -> bool { - match self { - Address::IPV4(v4) => { - ipv4addr_is_private(v4) - || ipv4addr_is_link_local(v4) - || ipv4addr_is_ietf_protocol_assignment(v4) - } - Address::IPV6(v6) => { - ipv6addr_is_unicast_site_local(v6) - || ipv6addr_is_unicast_link_local(v6) - || ipv6addr_is_unique_local(v6) - } - } - } - pub fn to_ip_addr(&self) -> IpAddr { - match self { - Self::IPV4(a) => IpAddr::V4(*a), - Self::IPV6(a) => IpAddr::V6(*a), - } - } - pub fn to_socket_addr(&self, port: u16) -> SocketAddr { - SocketAddr::new(self.to_ip_addr(), port) - } - pub fn to_canonical(&self) -> Address { - match self { - Address::IPV4(v4) => Address::IPV4(*v4), - Address::IPV6(v6) => match v6.to_ipv4() { - Some(v4) => Address::IPV4(v4), - None => Address::IPV6(*v6), - }, - } - } -} - -impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Address::IPV4(v4) => write!(f, "{}", v4), - Address::IPV6(v6) => write!(f, "{}", v6), - } - } -} - -impl FromStr for Address { - type Err = VeilidAPIError; - fn from_str(host: &str) -> Result { - if let Ok(addr) = Ipv4Addr::from_str(host) { - Ok(Address::IPV4(addr)) - } else if let Ok(addr) = Ipv6Addr::from_str(host) { - Ok(Address::IPV6(addr)) - } else { - Err(VeilidAPIError::parse_error( - "Address::from_str failed", - host, - )) - } - } -} - -#[derive( - Copy, - Default, - Clone, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SocketAddress { - address: Address, - port: u16, -} - -impl SocketAddress { - pub fn new(address: Address, port: u16) -> Self { - Self { address, port } - } - pub fn from_socket_addr(sa: SocketAddr) -> SocketAddress { - Self { - address: Address::from_socket_addr(sa), - port: sa.port(), - } - } - pub fn address(&self) -> Address { - self.address - } - pub fn address_type(&self) -> AddressType { - self.address.address_type() - } - pub fn port(&self) -> u16 { - self.port - } - pub fn to_canonical(&self) -> SocketAddress { - SocketAddress { - address: self.address.to_canonical(), - port: self.port, - } - } - pub fn to_ip_addr(&self) -> IpAddr { - self.address.to_ip_addr() - } - pub fn to_socket_addr(&self) -> SocketAddr { - self.address.to_socket_addr(self.port) - } -} - -impl fmt::Display for SocketAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(f, "{}", self.to_socket_addr()) - } -} - -impl FromStr for SocketAddress { - type Err = VeilidAPIError; - fn from_str(s: &str) -> Result { - let sa = SocketAddr::from_str(s) - .map_err(|e| VeilidAPIError::parse_error("Failed to parse SocketAddress", e))?; - Ok(SocketAddress::from_socket_addr(sa)) - } -} - -////////////////////////////////////////////////////////////////// - -#[derive( - Copy, - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoFilter { - #[with(RkyvEnumSet)] - pub protocol_type_set: ProtocolTypeSet, - #[with(RkyvEnumSet)] - pub address_type_set: AddressTypeSet, -} - -impl Default for DialInfoFilter { - fn default() -> Self { - Self { - protocol_type_set: ProtocolTypeSet::all(), - address_type_set: AddressTypeSet::all(), - } - } -} - -impl DialInfoFilter { - pub fn all() -> Self { - Self { - protocol_type_set: ProtocolTypeSet::all(), - address_type_set: AddressTypeSet::all(), - } - } - pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self { - self.protocol_type_set = ProtocolTypeSet::only(protocol_type); - self - } - pub fn with_protocol_type_set(mut self, protocol_set: ProtocolTypeSet) -> Self { - self.protocol_type_set = protocol_set; - self - } - pub fn with_address_type(mut self, address_type: AddressType) -> Self { - self.address_type_set = AddressTypeSet::only(address_type); - self - } - pub fn with_address_type_set(mut self, address_set: AddressTypeSet) -> Self { - self.address_type_set = address_set; - self - } - pub fn filtered(mut self, other_dif: &DialInfoFilter) -> Self { - self.protocol_type_set &= other_dif.protocol_type_set; - self.address_type_set &= other_dif.address_type_set; - self - } - pub fn is_dead(&self) -> bool { - self.protocol_type_set.is_empty() || self.address_type_set.is_empty() - } -} - -impl fmt::Debug for DialInfoFilter { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - let mut out = String::new(); - if self.protocol_type_set != ProtocolTypeSet::all() { - out += &format!("+{:?}", self.protocol_type_set); - } else { - out += "*"; - } - if self.address_type_set != AddressTypeSet::all() { - out += &format!("+{:?}", self.address_type_set); - } else { - out += "*"; - } - write!(f, "[{}]", out) - } -} - -pub trait MatchesDialInfoFilter { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool; -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoUDP { - pub socket_address: SocketAddress, -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoTCP { - pub socket_address: SocketAddress, -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoWS { - pub socket_address: SocketAddress, - pub request: String, -} - -#[derive( - Clone, - Default, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct DialInfoWSS { - pub socket_address: SocketAddress, - pub request: String, -} - -// Keep member order appropriate for sorting < preference -// Must match ProtocolType order -#[derive( - Clone, - Debug, - PartialEq, - PartialOrd, - Ord, - Eq, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -#[serde(tag = "kind")] -pub enum DialInfo { - UDP(DialInfoUDP), - TCP(DialInfoTCP), - WS(DialInfoWS), - WSS(DialInfoWSS), -} -impl Default for DialInfo { - fn default() -> Self { - DialInfo::UDP(DialInfoUDP::default()) - } -} - -impl fmt::Display for DialInfo { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - match self { - DialInfo::UDP(di) => write!(f, "udp|{}", di.socket_address), - DialInfo::TCP(di) => write!(f, "tcp|{}", di.socket_address), - DialInfo::WS(di) => { - let url = format!("ws://{}", di.request); - let split_url = SplitUrl::from_str(&url).unwrap(); - match split_url.host { - SplitUrlHost::Hostname(_) => { - write!(f, "ws|{}|{}", di.socket_address.to_ip_addr(), di.request) - } - SplitUrlHost::IpAddr(a) => { - if di.socket_address.to_ip_addr() == a { - write!(f, "ws|{}", di.request) - } else { - panic!("resolved address does not match url: {}", di.request); - } - } - } - } - DialInfo::WSS(di) => { - let url = format!("wss://{}", di.request); - let split_url = SplitUrl::from_str(&url).unwrap(); - match split_url.host { - SplitUrlHost::Hostname(_) => { - write!(f, "wss|{}|{}", di.socket_address.to_ip_addr(), di.request) - } - SplitUrlHost::IpAddr(_) => { - panic!( - "secure websockets can not use ip address in request: {}", - di.request - ); - } - } - } - } - } -} - -impl FromStr for DialInfo { - type Err = VeilidAPIError; - fn from_str(s: &str) -> Result { - let (proto, rest) = s.split_once('|').ok_or_else(|| { - VeilidAPIError::parse_error("DialInfo::from_str missing protocol '|' separator", s) - })?; - match proto { - "udp" => { - let socket_address = SocketAddress::from_str(rest)?; - Ok(DialInfo::udp(socket_address)) - } - "tcp" => { - let socket_address = SocketAddress::from_str(rest)?; - Ok(DialInfo::tcp(socket_address)) - } - "ws" => { - let url = format!("ws://{}", rest); - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) - })?; - if split_url.scheme != "ws" || !url.starts_with("ws://") { - return Err(VeilidAPIError::parse_error( - "incorrect scheme for WS dialinfo", - url, - )); - } - let url_port = split_url.port.unwrap_or(80u16); - - match rest.split_once('|') { - Some((sa, rest)) => { - let address = Address::from_str(sa)?; - - DialInfo::try_ws( - SocketAddress::new(address, url_port), - format!("ws://{}", rest), - ) - } - None => { - let address = Address::from_str(&split_url.host.to_string())?; - DialInfo::try_ws( - SocketAddress::new(address, url_port), - format!("ws://{}", rest), - ) - } - } - } - "wss" => { - let url = format!("wss://{}", rest); - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) - })?; - if split_url.scheme != "wss" || !url.starts_with("wss://") { - return Err(VeilidAPIError::parse_error( - "incorrect scheme for WSS dialinfo", - url, - )); - } - let url_port = split_url.port.unwrap_or(443u16); - - let (a, rest) = rest.split_once('|').ok_or_else(|| { - VeilidAPIError::parse_error( - "DialInfo::from_str missing socket address '|' separator", - s, - ) - })?; - - let address = Address::from_str(a)?; - DialInfo::try_wss( - SocketAddress::new(address, url_port), - format!("wss://{}", rest), - ) - } - _ => Err(VeilidAPIError::parse_error( - "DialInfo::from_str has invalid scheme", - s, - )), - } - } -} - -impl DialInfo { - pub fn udp_from_socketaddr(socket_addr: SocketAddr) -> Self { - Self::UDP(DialInfoUDP { - socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), - }) - } - pub fn tcp_from_socketaddr(socket_addr: SocketAddr) -> Self { - Self::TCP(DialInfoTCP { - socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), - }) - } - pub fn udp(socket_address: SocketAddress) -> Self { - Self::UDP(DialInfoUDP { - socket_address: socket_address.to_canonical(), - }) - } - pub fn tcp(socket_address: SocketAddress) -> Self { - Self::TCP(DialInfoTCP { - socket_address: socket_address.to_canonical(), - }) - } - pub fn try_ws(socket_address: SocketAddress, url: String) -> Result { - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) - })?; - if split_url.scheme != "ws" || !url.starts_with("ws://") { - return Err(VeilidAPIError::parse_error( - "incorrect scheme for WS dialinfo", - url, - )); - } - let url_port = split_url.port.unwrap_or(80u16); - if url_port != socket_address.port() { - return Err(VeilidAPIError::parse_error( - "socket address port doesn't match url port", - url, - )); - } - if let SplitUrlHost::IpAddr(a) = split_url.host { - if socket_address.to_ip_addr() != a { - return Err(VeilidAPIError::parse_error( - format!("request address does not match socket address: {}", a), - socket_address, - )); - } - } - Ok(Self::WS(DialInfoWS { - socket_address: socket_address.to_canonical(), - request: url[5..].to_string(), - })) - } - pub fn try_wss(socket_address: SocketAddress, url: String) -> Result { - let split_url = SplitUrl::from_str(&url).map_err(|e| { - VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) - })?; - if split_url.scheme != "wss" || !url.starts_with("wss://") { - return Err(VeilidAPIError::parse_error( - "incorrect scheme for WSS dialinfo", - url, - )); - } - let url_port = split_url.port.unwrap_or(443u16); - if url_port != socket_address.port() { - return Err(VeilidAPIError::parse_error( - "socket address port doesn't match url port", - url, - )); - } - if !matches!(split_url.host, SplitUrlHost::Hostname(_)) { - return Err(VeilidAPIError::parse_error( - "WSS url can not use address format, only hostname format", - url, - )); - } - Ok(Self::WSS(DialInfoWSS { - socket_address: socket_address.to_canonical(), - request: url[6..].to_string(), - })) - } - pub fn protocol_type(&self) -> ProtocolType { - match self { - Self::UDP(_) => ProtocolType::UDP, - Self::TCP(_) => ProtocolType::TCP, - Self::WS(_) => ProtocolType::WS, - Self::WSS(_) => ProtocolType::WSS, - } - } - pub fn address_type(&self) -> AddressType { - self.socket_address().address_type() - } - pub fn address(&self) -> Address { - match self { - Self::UDP(di) => di.socket_address.address, - Self::TCP(di) => di.socket_address.address, - Self::WS(di) => di.socket_address.address, - Self::WSS(di) => di.socket_address.address, - } - } - pub fn socket_address(&self) -> SocketAddress { - match self { - Self::UDP(di) => di.socket_address, - Self::TCP(di) => di.socket_address, - Self::WS(di) => di.socket_address, - Self::WSS(di) => di.socket_address, - } - } - pub fn to_ip_addr(&self) -> IpAddr { - match self { - Self::UDP(di) => di.socket_address.to_ip_addr(), - Self::TCP(di) => di.socket_address.to_ip_addr(), - Self::WS(di) => di.socket_address.to_ip_addr(), - Self::WSS(di) => di.socket_address.to_ip_addr(), - } - } - pub fn port(&self) -> u16 { - match self { - Self::UDP(di) => di.socket_address.port, - Self::TCP(di) => di.socket_address.port, - Self::WS(di) => di.socket_address.port, - Self::WSS(di) => di.socket_address.port, - } - } - pub fn set_port(&mut self, port: u16) { - match self { - Self::UDP(di) => di.socket_address.port = port, - Self::TCP(di) => di.socket_address.port = port, - Self::WS(di) => di.socket_address.port = port, - Self::WSS(di) => di.socket_address.port = port, - } - } - pub fn to_socket_addr(&self) -> SocketAddr { - match self { - Self::UDP(di) => di.socket_address.to_socket_addr(), - Self::TCP(di) => di.socket_address.to_socket_addr(), - Self::WS(di) => di.socket_address.to_socket_addr(), - Self::WSS(di) => di.socket_address.to_socket_addr(), - } - } - pub fn to_peer_address(&self) -> PeerAddress { - match self { - Self::UDP(di) => PeerAddress::new(di.socket_address, ProtocolType::UDP), - Self::TCP(di) => PeerAddress::new(di.socket_address, ProtocolType::TCP), - Self::WS(di) => PeerAddress::new(di.socket_address, ProtocolType::WS), - Self::WSS(di) => PeerAddress::new(di.socket_address, ProtocolType::WSS), - } - } - pub fn request(&self) -> Option { - match self { - Self::UDP(_) => None, - Self::TCP(_) => None, - Self::WS(di) => Some(format!("ws://{}", di.request)), - Self::WSS(di) => Some(format!("wss://{}", di.request)), - } - } - pub fn is_valid(&self) -> bool { - let socket_address = self.socket_address(); - let address = socket_address.address(); - let port = socket_address.port(); - (address.is_global() || address.is_local()) && port > 0 - } - - pub fn make_filter(&self) -> DialInfoFilter { - DialInfoFilter { - protocol_type_set: ProtocolTypeSet::only(self.protocol_type()), - address_type_set: AddressTypeSet::only(self.address_type()), - } - } - - pub fn try_vec_from_short, H: AsRef>( - short: S, - hostname: H, - ) -> Result, VeilidAPIError> { - let short = short.as_ref(); - let hostname = hostname.as_ref(); - - if short.len() < 2 { - return Err(VeilidAPIError::parse_error( - "invalid short url length", - short, - )); - } - let url = match &short[0..1] { - "U" => { - format!("udp://{}:{}", hostname, &short[1..]) - } - "T" => { - format!("tcp://{}:{}", hostname, &short[1..]) - } - "W" => { - format!("ws://{}:{}", hostname, &short[1..]) - } - "S" => { - format!("wss://{}:{}", hostname, &short[1..]) - } - _ => { - return Err(VeilidAPIError::parse_error("invalid short url type", short)); - } - }; - Self::try_vec_from_url(url) - } - - pub fn try_vec_from_url>(url: S) -> Result, VeilidAPIError> { - let url = url.as_ref(); - let split_url = SplitUrl::from_str(url) - .map_err(|e| VeilidAPIError::parse_error(format!("unable to split url: {}", e), url))?; - - let port = match split_url.scheme.as_str() { - "udp" | "tcp" => split_url - .port - .ok_or_else(|| VeilidAPIError::parse_error("Missing port in udp url", url))?, - "ws" => split_url.port.unwrap_or(80u16), - "wss" => split_url.port.unwrap_or(443u16), - _ => { - return Err(VeilidAPIError::parse_error( - "Invalid dial info url scheme", - split_url.scheme, - )); - } - }; - - let socket_addrs = { - // Resolve if possible, WASM doesn't support resolution and doesn't need it to connect to the dialinfo - // This will not be used on signed dialinfo, only for bootstrapping, so we don't need to worry about - // the '0.0.0.0' address being propagated across the routing table - cfg_if::cfg_if! { - if #[cfg(target_arch = "wasm32")] { - vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), port)] - } else { - match split_url.host { - SplitUrlHost::Hostname(_) => split_url - .host_port(port) - .to_socket_addrs() - .map_err(|_| VeilidAPIError::parse_error("couldn't resolve hostname in url", url))? - .collect(), - SplitUrlHost::IpAddr(a) => vec![SocketAddr::new(a, port)], - } - } - } - }; - - let mut out = Vec::new(); - for sa in socket_addrs { - out.push(match split_url.scheme.as_str() { - "udp" => Self::udp_from_socketaddr(sa), - "tcp" => Self::tcp_from_socketaddr(sa), - "ws" => Self::try_ws( - SocketAddress::from_socket_addr(sa).to_canonical(), - url.to_string(), - )?, - "wss" => Self::try_wss( - SocketAddress::from_socket_addr(sa).to_canonical(), - url.to_string(), - )?, - _ => { - unreachable!("Invalid dial info url scheme") - } - }); - } - Ok(out) - } - - pub async fn to_short(&self) -> (String, String) { - match self { - DialInfo::UDP(di) => ( - format!("U{}", di.socket_address.port()), - intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .unwrap_or_else(|_| di.socket_address.to_string()), - ), - DialInfo::TCP(di) => ( - format!("T{}", di.socket_address.port()), - intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .unwrap_or_else(|_| di.socket_address.to_string()), - ), - DialInfo::WS(di) => { - let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - ( - format!( - "W{}{}", - split_url.port.unwrap_or(80), - split_url - .path - .map(|p| format!("/{}", p)) - .unwrap_or_default() - ), - split_url.host.to_string(), - ) - } - DialInfo::WSS(di) => { - let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - ( - format!( - "S{}{}", - split_url.port.unwrap_or(443), - split_url - .path - .map(|p| format!("/{}", p)) - .unwrap_or_default() - ), - split_url.host.to_string(), - ) - } - } - } - pub async fn to_url(&self) -> String { - match self { - DialInfo::UDP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .map(|h| format!("udp://{}:{}", h, di.socket_address.port())) - .unwrap_or_else(|_| format!("udp://{}", di.socket_address)), - DialInfo::TCP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) - .await - .map(|h| format!("tcp://{}:{}", h, di.socket_address.port())) - .unwrap_or_else(|_| format!("tcp://{}", di.socket_address)), - DialInfo::WS(di) => { - let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - split_url.to_string() - } - DialInfo::WSS(di) => { - let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); - if let SplitUrlHost::IpAddr(a) = split_url.host { - if let Ok(host) = intf::ptr_lookup(a).await { - split_url.host = SplitUrlHost::Hostname(host); - } - } - split_url.to_string() - } - } - } - - pub fn ordered_sequencing_sort(a: &DialInfo, b: &DialInfo) -> core::cmp::Ordering { - let ca = a.protocol_type().sort_order(Sequencing::EnsureOrdered); - let cb = b.protocol_type().sort_order(Sequencing::EnsureOrdered); - if ca < cb { - return core::cmp::Ordering::Less; - } - if ca > cb { - return core::cmp::Ordering::Greater; - } - match (a, b) { - (DialInfo::UDP(a), DialInfo::UDP(b)) => a.cmp(b), - (DialInfo::TCP(a), DialInfo::TCP(b)) => a.cmp(b), - (DialInfo::WS(a), DialInfo::WS(b)) => a.cmp(b), - (DialInfo::WSS(a), DialInfo::WSS(b)) => a.cmp(b), - _ => unreachable!(), - } - } -} - -impl MatchesDialInfoFilter for DialInfo { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool { - if !filter.protocol_type_set.contains(self.protocol_type()) { - return false; - } - if !filter.address_type_set.contains(self.address_type()) { - return false; - } - true - } -} - -////////////////////////////////////////////////////////////////////////// - -// Signed NodeInfo that can be passed around amongst peers and verifiable -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SignedDirectNodeInfo { - pub node_info: NodeInfo, - pub timestamp: u64, - pub signature: Option, -} - -impl SignedDirectNodeInfo { - pub fn new( - node_id: NodeId, - node_info: NodeInfo, - timestamp: u64, - signature: DHTSignature, - ) -> Result { - let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; - verify(&node_id.key, &node_info_bytes, &signature)?; - Ok(Self { - node_info, - timestamp, - signature: Some(signature), - }) - } - - pub fn with_secret( - node_id: NodeId, - node_info: NodeInfo, - secret: &DHTKeySecret, - ) -> Result { - let timestamp = intf::get_timestamp(); - let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; - let signature = sign(&node_id.key, secret, &node_info_bytes)?; - Ok(Self { - node_info, - timestamp, - signature: Some(signature), - }) - } - - fn make_signature_bytes( - node_info: &NodeInfo, - timestamp: u64, - ) -> Result, VeilidAPIError> { - let mut node_info_bytes = Vec::new(); - - // Add nodeinfo to signature - let mut ni_msg = ::capnp::message::Builder::new_default(); - let mut ni_builder = ni_msg.init_root::(); - encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; - node_info_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); - - // Add timestamp to signature - node_info_bytes.append(&mut timestamp.to_le_bytes().to_vec()); - - Ok(node_info_bytes) - } - - pub fn with_no_signature(node_info: NodeInfo) -> Self { - Self { - node_info, - signature: None, - timestamp: intf::get_timestamp(), - } - } - - pub fn has_valid_signature(&self) -> bool { - self.signature.is_some() - } -} - -/// Signed NodeInfo with a relay that can be passed around amongst peers and verifiable -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct SignedRelayedNodeInfo { - pub node_info: NodeInfo, - pub relay_id: NodeId, - pub relay_info: SignedDirectNodeInfo, - pub timestamp: u64, - pub signature: DHTSignature, -} - -impl SignedRelayedNodeInfo { - pub fn new( - node_id: NodeId, - node_info: NodeInfo, - relay_id: NodeId, - relay_info: SignedDirectNodeInfo, - timestamp: u64, - signature: DHTSignature, - ) -> Result { - let node_info_bytes = - Self::make_signature_bytes(&node_info, &relay_id, &relay_info, timestamp)?; - verify(&node_id.key, &node_info_bytes, &signature)?; - Ok(Self { - node_info, - relay_id, - relay_info, - signature, - timestamp, - }) - } - - pub fn with_secret( - node_id: NodeId, - node_info: NodeInfo, - relay_id: NodeId, - relay_info: SignedDirectNodeInfo, - secret: &DHTKeySecret, - ) -> Result { - let timestamp = intf::get_timestamp(); - let node_info_bytes = - Self::make_signature_bytes(&node_info, &relay_id, &relay_info, timestamp)?; - let signature = sign(&node_id.key, secret, &node_info_bytes)?; - Ok(Self { - node_info, - relay_id, - relay_info, - signature, - timestamp, - }) - } - - fn make_signature_bytes( - node_info: &NodeInfo, - relay_id: &NodeId, - relay_info: &SignedDirectNodeInfo, - timestamp: u64, - ) -> Result, VeilidAPIError> { - let mut sig_bytes = Vec::new(); - - // Add nodeinfo to signature - let mut ni_msg = ::capnp::message::Builder::new_default(); - let mut ni_builder = ni_msg.init_root::(); - encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; - sig_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); - - // Add relay id to signature - let mut rid_msg = ::capnp::message::Builder::new_default(); - let mut rid_builder = rid_msg.init_root::(); - encode_dht_key(&relay_id.key, &mut rid_builder).map_err(VeilidAPIError::internal)?; - sig_bytes.append(&mut builder_to_vec(rid_msg).map_err(VeilidAPIError::internal)?); - - // Add relay info to signature - let mut ri_msg = ::capnp::message::Builder::new_default(); - let mut ri_builder = ri_msg.init_root::(); - encode_signed_direct_node_info(relay_info, &mut ri_builder) - .map_err(VeilidAPIError::internal)?; - sig_bytes.append(&mut builder_to_vec(ri_msg).map_err(VeilidAPIError::internal)?); - - // Add timestamp to signature - sig_bytes.append(&mut timestamp.to_le_bytes().to_vec()); - - Ok(sig_bytes) - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum SignedNodeInfo { - Direct(SignedDirectNodeInfo), - Relayed(SignedRelayedNodeInfo), -} - -impl SignedNodeInfo { - pub fn has_valid_signature(&self) -> bool { - match self { - SignedNodeInfo::Direct(d) => d.has_valid_signature(), - SignedNodeInfo::Relayed(_) => true, - } - } - - pub fn timestamp(&self) -> u64 { - match self { - SignedNodeInfo::Direct(d) => d.timestamp, - SignedNodeInfo::Relayed(r) => r.timestamp, - } - } - - pub fn node_info(&self) -> &NodeInfo { - match self { - SignedNodeInfo::Direct(d) => &d.node_info, - SignedNodeInfo::Relayed(r) => &r.node_info, - } - } - pub fn relay_id(&self) -> Option { - match self { - SignedNodeInfo::Direct(_) => None, - SignedNodeInfo::Relayed(r) => Some(r.relay_id.clone()), - } - } - pub fn relay_info(&self) -> Option<&NodeInfo> { - match self { - SignedNodeInfo::Direct(_) => None, - SignedNodeInfo::Relayed(r) => Some(&r.relay_info.node_info), - } - } - pub fn relay_peer_info(&self) -> Option { - match self { - SignedNodeInfo::Direct(_) => None, - SignedNodeInfo::Relayed(r) => Some(PeerInfo::new( - r.relay_id.clone(), - SignedNodeInfo::Direct(r.relay_info.clone()), - )), - } - } - pub fn has_any_dial_info(&self) -> bool { - self.node_info().has_dial_info() - || self - .relay_info() - .map(|relay_ni| relay_ni.has_dial_info()) - .unwrap_or_default() - } - - pub fn has_sequencing_matched_dial_info(&self, sequencing: Sequencing) -> bool { - // Check our dial info - for did in &self.node_info().dial_info_detail_list { - match sequencing { - Sequencing::NoPreference | Sequencing::PreferOrdered => return true, - Sequencing::EnsureOrdered => { - if did.dial_info.protocol_type().is_connection_oriented() { - return true; - } - } - } - } - // Check our relay if we have one - return self - .relay_info() - .map(|relay_ni| { - for did in &relay_ni.dial_info_detail_list { - match sequencing { - Sequencing::NoPreference | Sequencing::PreferOrdered => return true, - Sequencing::EnsureOrdered => { - if did.dial_info.protocol_type().is_connection_oriented() { - return true; - } - } - } - } - false - }) - .unwrap_or_default(); - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerInfo { - pub node_id: NodeId, - pub signed_node_info: SignedNodeInfo, -} - -impl PeerInfo { - pub fn new(node_id: NodeId, signed_node_info: SignedNodeInfo) -> Self { - Self { - node_id, - signed_node_info, - } - } -} - -#[derive( - Copy, - Clone, - Debug, - PartialEq, - PartialOrd, - Eq, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerAddress { - protocol_type: ProtocolType, - #[serde(with = "json_as_string")] - socket_address: SocketAddress, -} - -impl PeerAddress { - pub fn new(socket_address: SocketAddress, protocol_type: ProtocolType) -> Self { - Self { - socket_address: socket_address.to_canonical(), - protocol_type, - } - } - - pub fn socket_address(&self) -> &SocketAddress { - &self.socket_address - } - - pub fn protocol_type(&self) -> ProtocolType { - self.protocol_type - } - - pub fn to_socket_addr(&self) -> SocketAddr { - self.socket_address.to_socket_addr() - } - - pub fn address_type(&self) -> AddressType { - self.socket_address.address_type() - } -} - -/// Represents the 5-tuple of an established connection -/// Not used to specify connections to create, that is reserved for DialInfo -/// -/// ConnectionDescriptors should never be from unspecified local addresses for connection oriented protocols -/// If the medium does not allow local addresses, None should have been used or 'new_no_local' -/// If we are specifying only a port, then the socket's 'local_address()' should have been used, since an -/// established connection is always from a real address to another real address. -#[derive( - Copy, - Clone, - Debug, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct ConnectionDescriptor { - remote: PeerAddress, - local: Option, -} - -impl ConnectionDescriptor { - pub fn new(remote: PeerAddress, local: SocketAddress) -> Self { - assert!( - !remote.protocol_type().is_connection_oriented() || !local.address().is_unspecified() - ); - - Self { - remote, - local: Some(local), - } - } - pub fn new_no_local(remote: PeerAddress) -> Self { - Self { - remote, - local: None, - } - } - pub fn remote(&self) -> PeerAddress { - self.remote - } - pub fn remote_address(&self) -> &SocketAddress { - self.remote.socket_address() - } - pub fn local(&self) -> Option { - self.local - } - pub fn protocol_type(&self) -> ProtocolType { - self.remote.protocol_type - } - pub fn address_type(&self) -> AddressType { - self.remote.address_type() - } - pub fn make_dial_info_filter(&self) -> DialInfoFilter { - DialInfoFilter::all() - .with_protocol_type(self.protocol_type()) - .with_address_type(self.address_type()) - } -} - -impl MatchesDialInfoFilter for ConnectionDescriptor { - fn matches_filter(&self, filter: &DialInfoFilter) -> bool { - if !filter.protocol_type_set.contains(self.protocol_type()) { - return false; - } - if !filter.address_type_set.contains(self.address_type()) { - return false; - } - true - } -} - -////////////////////////////////////////////////////////////////////////// - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct LatencyStats { - #[serde(with = "json_as_string")] - pub fastest: u64, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies - #[serde(with = "json_as_string")] - pub average: u64, // average latency over the ROLLING_LATENCIES_SIZE last latencies - #[serde(with = "json_as_string")] - pub slowest: u64, // slowest latency in the ROLLING_LATENCIES_SIZE last latencies -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct TransferStats { - #[serde(with = "json_as_string")] - pub total: u64, // total amount transferred ever - #[serde(with = "json_as_string")] - pub maximum: u64, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts - #[serde(with = "json_as_string")] - pub average: u64, // average rate over the ROLLING_TRANSFERS_SIZE last amounts - #[serde(with = "json_as_string")] - pub minimum: u64, // minimum rate over the ROLLING_TRANSFERS_SIZE last amounts -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct TransferStatsDownUp { - pub down: TransferStats, - pub up: TransferStats, -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct RPCStats { - pub messages_sent: u32, // number of rpcs that have been sent in the total_time range - pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range - pub questions_in_flight: u32, // number of questions issued that have yet to be answered - #[serde(with = "opt_json_as_string")] - pub last_question: Option, // when the peer was last questioned (either successfully or not) and we wanted an answer - #[serde(with = "opt_json_as_string")] - pub last_seen_ts: Option, // when the peer was last seen for any reason, including when we first attempted to reach out to it - #[serde(with = "opt_json_as_string")] - pub first_consecutive_seen_ts: Option, // the timestamp of the first consecutive proof-of-life for this node (an answer or received question) - pub recent_lost_answers: u32, // number of answers that have been lost since we lost reliability - pub failed_to_send: u32, // number of messages that have failed to send since we last successfully sent one -} - -#[derive( - Clone, - Debug, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PeerStats { - #[serde(with = "json_as_string")] - pub time_added: u64, // when the peer was added to the routing table - pub rpc_stats: RPCStats, // information about RPCs - pub latency: Option, // latencies for communications with the peer - pub transfer: TransferStatsDownUp, // Stats for communications with the peer -} - -pub type ValueChangeCallback = - Arc) -> SendPinBoxFuture<()> + Send + Sync + 'static>; - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum SignalInfo { - HolePunch { - // UDP Hole Punch Request - receipt: Vec, // Receipt to be returned after the hole punch - peer_info: PeerInfo, // Sender's peer info - }, - ReverseConnect { - // Reverse Connection Request - receipt: Vec, // Receipt to be returned by the reverse connection - peer_info: PeerInfo, // Sender's peer info - }, - // XXX: WebRTC - // XXX: App-level signalling -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// -#[derive( - Copy, - Clone, - Debug, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum TunnelMode { - Raw, - Turn, -} - -#[derive( - Copy, - Clone, - Debug, - PartialOrd, - PartialEq, - Eq, - Ord, - Serialize, - Deserialize, - RkyvArchive, - RkyvSerialize, - RkyvDeserialize, -)] -#[archive_attr(repr(u8), derive(CheckBytes))] -pub enum TunnelError { - BadId, // Tunnel ID was rejected - NoEndpoint, // Endpoint was unreachable - RejectedMode, // Endpoint couldn't provide mode - NoCapacity, // Endpoint is full -} - -pub type TunnelId = u64; - -#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct TunnelEndpoint { - pub mode: TunnelMode, - pub description: String, // XXX: TODO -} - -impl Default for TunnelEndpoint { - fn default() -> Self { - Self { - mode: TunnelMode::Raw, - description: "".to_string(), - } - } -} - -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct FullTunnel { - pub id: TunnelId, - pub timeout: u64, - pub local: TunnelEndpoint, - pub remote: TunnelEndpoint, -} - -#[derive( - Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, -)] -#[archive_attr(repr(C), derive(CheckBytes))] -pub struct PartialTunnel { - pub id: TunnelId, - pub timeout: u64, - pub local: TunnelEndpoint, -} - -///////////////////////////////////////////////////////////////////////////////////////////////////// - -struct VeilidAPIInner { - context: Option, -} - -impl fmt::Debug for VeilidAPIInner { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "VeilidAPIInner") - } -} - -impl Drop for VeilidAPIInner { - fn drop(&mut self) { - if let Some(context) = self.context.take() { - intf::spawn_detached(api_shutdown(context)); - } - } -} - -#[derive(Clone, Debug)] -pub struct VeilidAPI { - inner: Arc>, -} - -impl VeilidAPI { - #[instrument(skip_all)] - pub(crate) fn new(context: VeilidCoreContext) -> Self { - Self { - inner: Arc::new(Mutex::new(VeilidAPIInner { - context: Some(context), - })), - } - } - - #[instrument(skip_all)] - pub async fn shutdown(self) { - let context = { self.inner.lock().context.take() }; - if let Some(context) = context { - api_shutdown(context).await; - } - } - - pub fn is_shutdown(&self) -> bool { - self.inner.lock().context.is_none() - } - - //////////////////////////////////////////////////////////////// - // Accessors - pub fn config(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.config.clone()); - } - Err(VeilidAPIError::NotInitialized) - } - pub fn crypto(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.crypto.clone()); - } - Err(VeilidAPIError::NotInitialized) - } - pub fn table_store(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.table_store.clone()); - } - Err(VeilidAPIError::not_initialized()) - } - pub fn block_store(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.block_store.clone()); - } - Err(VeilidAPIError::not_initialized()) - } - pub fn protected_store(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.protected_store.clone()); - } - Err(VeilidAPIError::not_initialized()) - } - pub fn attachment_manager(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.attachment_manager.clone()); - } - Err(VeilidAPIError::not_initialized()) - } - pub fn network_manager(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.attachment_manager.network_manager()); - } - Err(VeilidAPIError::not_initialized()) - } - pub fn rpc_processor(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.attachment_manager.network_manager().rpc_processor()); - } - Err(VeilidAPIError::NotInitialized) - } - pub fn routing_table(&self) -> Result { - let inner = self.inner.lock(); - if let Some(context) = &inner.context { - return Ok(context.attachment_manager.network_manager().routing_table()); - } - Err(VeilidAPIError::NotInitialized) - } - - //////////////////////////////////////////////////////////////// - // Attach/Detach - - // get a full copy of the current state - pub async fn get_state(&self) -> Result { - let attachment_manager = self.attachment_manager()?; - let network_manager = attachment_manager.network_manager(); - let config = self.config()?; - - let attachment = attachment_manager.get_veilid_state(); - let network = network_manager.get_veilid_state(); - let config = config.get_veilid_state(); - - Ok(VeilidState { - attachment, - network, - config, - }) - } - - // get network connectedness - - // connect to the network - #[instrument(level = "debug", err, skip_all)] - pub async fn attach(&self) -> Result<(), VeilidAPIError> { - let attachment_manager = self.attachment_manager()?; - attachment_manager - .request_attach() - .await - .map_err(|e| VeilidAPIError::internal(e)) - } - - // disconnect from the network - #[instrument(level = "debug", err, skip_all)] - pub async fn detach(&self) -> Result<(), VeilidAPIError> { - let attachment_manager = self.attachment_manager()?; - attachment_manager - .request_detach() - .await - .map_err(|e| VeilidAPIError::internal(e)) - } - - //////////////////////////////////////////////////////////////// - // Routing Context - - #[instrument(level = "debug", skip(self))] - pub fn routing_context(&self) -> RoutingContext { - RoutingContext::new(self.clone()) - } - - //////////////////////////////////////////////////////////////// - // Private route allocation - - #[instrument(level = "debug", skip(self))] - pub async fn new_default_private_route(&self) -> Result<(DHTKey, Vec), VeilidAPIError> { - let config = self.config()?; - let c = config.get(); - self.new_private_route( - Stability::LowLatency, - Sequencing::NoPreference, - c.network.rpc.default_route_hop_count.into(), - ) - .await - } - - #[instrument(level = "debug", skip(self))] - pub async fn new_private_route( - &self, - stability: Stability, - sequencing: Sequencing, - hop_count: usize, - ) -> Result<(DHTKey, Vec), VeilidAPIError> { - let rss = self.routing_table()?.route_spec_store(); - let r = rss - .allocate_route( - stability, - sequencing, - hop_count, - Direction::Inbound.into(), - &[], - ) - .map_err(VeilidAPIError::internal)?; - let Some(pr_pubkey) = r else { - return Err(VeilidAPIError::generic("unable to allocate route")); - }; - if !rss - .test_route(&pr_pubkey) - .await - .map_err(VeilidAPIError::no_connection)? - { - rss.release_route(pr_pubkey) - .map_err(VeilidAPIError::generic)?; - return Err(VeilidAPIError::generic("allocated route failed to test")); - } - let private_route = rss - .assemble_private_route(&pr_pubkey, Some(true)) - .map_err(VeilidAPIError::generic)?; - let blob = match RouteSpecStore::private_route_to_blob(&private_route) { - Ok(v) => v, - Err(e) => { - rss.release_route(pr_pubkey) - .map_err(VeilidAPIError::generic)?; - return Err(VeilidAPIError::internal(e)); - } - }; - Ok((pr_pubkey, blob)) - } - - //////////////////////////////////////////////////////////////// - // App Calls - - #[instrument(level = "debug", skip(self))] - pub async fn app_call_reply(&self, id: u64, message: Vec) -> Result<(), VeilidAPIError> { - let rpc_processor = self.rpc_processor()?; - rpc_processor - .app_call_reply(id, message) - .await - .map_err(|e| e.into()) - } - - //////////////////////////////////////////////////////////////// - // Tunnel Building - - #[instrument(level = "debug", err, skip(self))] - pub async fn start_tunnel( - &self, - _endpoint_mode: TunnelMode, - _depth: u8, - ) -> Result { - panic!("unimplemented"); - } - - #[instrument(level = "debug", err, skip(self))] - pub async fn complete_tunnel( - &self, - _endpoint_mode: TunnelMode, - _depth: u8, - _partial_tunnel: PartialTunnel, - ) -> Result { - panic!("unimplemented"); - } - - #[instrument(level = "debug", err, skip(self))] - pub async fn cancel_tunnel(&self, _tunnel_id: TunnelId) -> Result { - panic!("unimplemented"); - } -} diff --git a/veilid-core/src/veilid_api/routing_context.rs b/veilid-core/src/veilid_api/routing_context.rs index 8e17b7de..3cffd867 100644 --- a/veilid-core/src/veilid_api/routing_context.rs +++ b/veilid-core/src/veilid_api/routing_context.rs @@ -1,4 +1,5 @@ use super::*; + /////////////////////////////////////////////////////////////////////////////////////// #[derive(Clone, Debug)] @@ -39,14 +40,19 @@ impl RoutingContext { api, inner: Arc::new(Mutex::new(RoutingContextInner {})), unlocked_inner: Arc::new(RoutingContextUnlockedInner { - safety_selection: SafetySelection::Unsafe(Sequencing::NoPreference), + safety_selection: SafetySelection::Unsafe(Sequencing::default()), }), } } - pub fn with_default_privacy(self) -> Result { + pub fn with_privacy(self) -> Result { + self.with_custom_privacy(Stability::default()) + } + + pub fn with_custom_privacy(self, stability: Stability) -> Result { let config = self.api.config()?; let c = config.get(); + Ok(Self { api: self.api.clone(), inner: Arc::new(Mutex::new(RoutingContextInner {})), @@ -54,22 +60,13 @@ impl RoutingContext { safety_selection: SafetySelection::Safe(SafetySpec { preferred_route: None, hop_count: c.network.rpc.default_route_hop_count as usize, - stability: Stability::LowLatency, - sequencing: Sequencing::NoPreference, + stability, + sequencing: self.sequencing(), }), }), }) } - pub fn with_privacy(self, safety_spec: SafetySpec) -> Result { - Ok(Self { - api: self.api.clone(), - inner: Arc::new(Mutex::new(RoutingContextInner {})), - unlocked_inner: Arc::new(RoutingContextUnlockedInner { - safety_selection: SafetySelection::Safe(safety_spec), - }), - }) - } - + pub fn with_sequencing(self, sequencing: Sequencing) -> Self { Self { api: self.api.clone(), @@ -87,18 +84,13 @@ impl RoutingContext { }), } } - pub fn sequencing(&self) -> Sequencing { + + fn sequencing(&self) -> Sequencing { match self.unlocked_inner.safety_selection { SafetySelection::Unsafe(sequencing) => sequencing, SafetySelection::Safe(safety_spec) => safety_spec.sequencing, } } - pub fn safety_spec(&self) -> Option { - match self.unlocked_inner.safety_selection { - SafetySelection::Unsafe(_) => None, - SafetySelection::Safe(safety_spec) => Some(safety_spec.clone()), - } - } pub fn api(&self) -> VeilidAPI { self.api.clone() @@ -115,7 +107,7 @@ impl RoutingContext { // Resolve node let mut nr = match rpc_processor.resolve_node(node_id.key).await { Ok(Some(nr)) => nr, - Ok(None) => return Err(VeilidAPIError::KeyNotFound { key: node_id.key }), + Ok(None) => apibail_key_not_found!(node_id.key), Err(e) => return Err(e.into()), }; // Apply sequencing to match safety selection @@ -129,9 +121,12 @@ impl RoutingContext { Target::PrivateRoute(pr) => { // Get remote private route let rss = self.api.routing_table()?.route_spec_store(); - let private_route = rss - .get_remote_private_route(&pr) - .map_err(|_| VeilidAPIError::KeyNotFound { key: pr })?; + let Some(private_route) = rss + .get_remote_private_route(&pr) + else { + apibail_key_not_found!(pr); + }; + Ok(rpc_processor::Destination::PrivateRoute { private_route, safety_selection: self.unlocked_inner.safety_selection, @@ -157,15 +152,14 @@ impl RoutingContext { // Send app message let answer = match rpc_processor.rpc_call_app_call(dest, request).await { Ok(NetworkResult::Value(v)) => v, - Ok(NetworkResult::Timeout) => return Err(VeilidAPIError::Timeout), + Ok(NetworkResult::Timeout) => apibail_timeout!(), + Ok(NetworkResult::ServiceUnavailable) => apibail_try_again!(), Ok(NetworkResult::NoConnection(e)) | Ok(NetworkResult::AlreadyExists(e)) => { - return Err(VeilidAPIError::NoConnection { - message: e.to_string(), - }) + apibail_no_connection!(e); } Ok(NetworkResult::InvalidMessage(message)) => { - return Err(VeilidAPIError::Generic { message }) + apibail_generic!(message); } Err(e) => return Err(e.into()), }; @@ -187,14 +181,13 @@ impl RoutingContext { // Send app message match rpc_processor.rpc_call_app_message(dest, message).await { Ok(NetworkResult::Value(())) => {} - Ok(NetworkResult::Timeout) => return Err(VeilidAPIError::Timeout), + Ok(NetworkResult::Timeout) => apibail_timeout!(), + Ok(NetworkResult::ServiceUnavailable) => apibail_try_again!(), Ok(NetworkResult::NoConnection(e)) | Ok(NetworkResult::AlreadyExists(e)) => { - return Err(VeilidAPIError::NoConnection { - message: e.to_string(), - }) + apibail_no_connection!(e); } Ok(NetworkResult::InvalidMessage(message)) => { - return Err(VeilidAPIError::Generic { message }) + apibail_generic!(message); } Err(e) => return Err(e.into()), }; diff --git a/veilid-core/src/veilid_api/serialize_helpers.rs b/veilid-core/src/veilid_api/serialize_helpers.rs index 680e3a48..91a3b283 100644 --- a/veilid-core/src/veilid_api/serialize_helpers.rs +++ b/veilid-core/src/veilid_api/serialize_helpers.rs @@ -5,7 +5,7 @@ use rkyv::Archive as RkyvArchive; use rkyv::Deserialize as RkyvDeserialize; use rkyv::Serialize as RkyvSerialize; -// XXX: Don't trace these functions as they are used in the transfer of API logs, which will recurse! +// Don't trace these functions as they are used in the transfer of API logs, which will recurse! // #[instrument(level = "trace", ret, err)] pub fn deserialize_json<'a, T: de::Deserialize<'a> + Debug>( diff --git a/veilid-core/src/veilid_api/types.rs b/veilid-core/src/veilid_api/types.rs new file mode 100644 index 00000000..72f9ba7d --- /dev/null +++ b/veilid-core/src/veilid_api/types.rs @@ -0,0 +1,2482 @@ +use super::*; + +///////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Microseconds since epoch +pub type Timestamp = AlignedU64; +pub fn get_aligned_timestamp() -> Timestamp { + get_timestamp().into() +} +/// Microseconds duration +pub type TimestampDuration = AlignedU64; +/// Request/Response matching id +pub type OperationId = AlignedU64; +/// Number of bytes +pub type ByteCount = AlignedU64; +/// Tunnel identifier +pub type TunnelId = AlignedU64; + +#[derive( + Debug, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Copy, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum VeilidLogLevel { + Error = 1, + Warn, + Info, + Debug, + Trace, +} + +impl VeilidLogLevel { + pub fn from_tracing_level(level: tracing::Level) -> VeilidLogLevel { + match level { + tracing::Level::ERROR => VeilidLogLevel::Error, + tracing::Level::WARN => VeilidLogLevel::Warn, + tracing::Level::INFO => VeilidLogLevel::Info, + tracing::Level::DEBUG => VeilidLogLevel::Debug, + tracing::Level::TRACE => VeilidLogLevel::Trace, + } + } + pub fn from_log_level(level: log::Level) -> VeilidLogLevel { + match level { + log::Level::Error => VeilidLogLevel::Error, + log::Level::Warn => VeilidLogLevel::Warn, + log::Level::Info => VeilidLogLevel::Info, + log::Level::Debug => VeilidLogLevel::Debug, + log::Level::Trace => VeilidLogLevel::Trace, + } + } + pub fn to_tracing_level(&self) -> tracing::Level { + match self { + Self::Error => tracing::Level::ERROR, + Self::Warn => tracing::Level::WARN, + Self::Info => tracing::Level::INFO, + Self::Debug => tracing::Level::DEBUG, + Self::Trace => tracing::Level::TRACE, + } + } + pub fn to_log_level(&self) -> log::Level { + match self { + Self::Error => log::Level::Error, + Self::Warn => log::Level::Warn, + Self::Info => log::Level::Info, + Self::Debug => log::Level::Debug, + Self::Trace => log::Level::Trace, + } + } +} + +impl fmt::Display for VeilidLogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let text = match self { + Self::Error => "ERROR", + Self::Warn => "WARN", + Self::Info => "INFO", + Self::Debug => "DEBUG", + Self::Trace => "TRACE", + }; + write!(f, "{}", text) + } +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidLog { + pub log_level: VeilidLogLevel, + pub message: String, + pub backtrace: Option, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +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")] + pub sender: Option, + /// The content of the message to deliver to the application + #[serde(with = "json_as_base64")] + pub message: Vec, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +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")] + pub sender: Option, + /// The content of the request to deliver to the application + #[serde(with = "json_as_base64")] + pub message: Vec, + /// The id to reply to + #[serde(with = "json_as_string")] + pub id: OperationId, +} + +#[derive( + Debug, + PartialEq, + Eq, + Clone, + Copy, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum AttachmentState { + Detached, + Attaching, + AttachedWeak, + AttachedGood, + AttachedStrong, + FullyAttached, + OverAttached, + Detaching, +} + +impl fmt::Display for AttachmentState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let out = match self { + AttachmentState::Attaching => "attaching".to_owned(), + AttachmentState::AttachedWeak => "attached_weak".to_owned(), + AttachmentState::AttachedGood => "attached_good".to_owned(), + AttachmentState::AttachedStrong => "attached_strong".to_owned(), + AttachmentState::FullyAttached => "fully_attached".to_owned(), + AttachmentState::OverAttached => "over_attached".to_owned(), + AttachmentState::Detaching => "detaching".to_owned(), + AttachmentState::Detached => "detached".to_owned(), + }; + write!(f, "{}", out) + } +} + +impl TryFrom for AttachmentState { + type Error = (); + + fn try_from(s: String) -> Result { + Ok(match s.as_str() { + "attaching" => AttachmentState::Attaching, + "attached_weak" => AttachmentState::AttachedWeak, + "attached_good" => AttachmentState::AttachedGood, + "attached_strong" => AttachmentState::AttachedStrong, + "fully_attached" => AttachmentState::FullyAttached, + "over_attached" => AttachmentState::OverAttached, + "detaching" => AttachmentState::Detaching, + "detached" => AttachmentState::Detached, + _ => return Err(()), + }) + } +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateAttachment { + pub state: AttachmentState, + pub public_internet_ready: bool, + pub local_network_ready: bool, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerTableData { + pub node_id: DHTKey, + pub peer_address: PeerAddress, + pub peer_stats: PeerStats, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateNetwork { + pub started: bool, + #[serde(with = "json_as_string")] + pub bps_down: ByteCount, + #[serde(with = "json_as_string")] + pub bps_up: ByteCount, + pub peers: Vec, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateRoute { + pub dead_routes: Vec, + pub dead_remote_routes: Vec, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidStateConfig { + pub config: VeilidConfigInner, +} + +#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +#[serde(tag = "kind")] +pub enum VeilidUpdate { + Log(VeilidLog), + AppMessage(VeilidAppMessage), + AppCall(VeilidAppCall), + Attachment(VeilidStateAttachment), + Network(VeilidStateNetwork), + Config(VeilidStateConfig), + Route(VeilidStateRoute), + Shutdown, +} + +#[derive(Debug, Clone, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct VeilidState { + pub attachment: VeilidStateAttachment, + pub network: VeilidStateNetwork, + pub config: VeilidStateConfig, +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +/// +#[derive( + Clone, + Debug, + Default, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct NodeId { + pub key: DHTKey, +} +impl NodeId { + pub fn new(key: DHTKey) -> Self { + Self { key } + } +} +impl fmt::Display for NodeId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", self.key.encode()) + } +} +impl FromStr for NodeId { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + Ok(Self { + key: DHTKey::try_decode(s)?, + }) + } +} + +#[derive( + Clone, + Debug, + Default, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct ValueKey { + pub key: DHTKey, + pub subkey: Option, +} +impl ValueKey { + pub fn new(key: DHTKey) -> Self { + Self { key, subkey: None } + } + pub fn new_subkey(key: DHTKey, subkey: String) -> Self { + Self { + key, + subkey: if subkey.is_empty() { + None + } else { + Some(subkey) + }, + } + } +} + +#[derive( + Clone, + Debug, + Default, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct ValueData { + pub data: Vec, + pub seq: u32, +} +impl ValueData { + pub fn new(data: Vec) -> Self { + Self { data, seq: 0 } + } + pub fn new_with_seq(data: Vec, seq: u32) -> Self { + Self { data, seq } + } + pub fn change(&mut self, data: Vec) { + self.data = data; + self.seq += 1; + } +} + +#[derive( + Clone, + Debug, + Default, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct BlockId { + pub key: DHTKey, +} +impl BlockId { + pub fn new(key: DHTKey) -> Self { + Self { key } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// + +// Keep member order appropriate for sorting < preference +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum DialInfoClass { + Direct = 0, // D = Directly reachable with public IP and no firewall, with statically configured port + Mapped = 1, // M = Directly reachable with via portmap behind any NAT or firewalled with dynamically negotiated port + FullConeNAT = 2, // F = Directly reachable device without portmap behind full-cone NAT + Blocked = 3, // B = Inbound blocked at firewall but may hole punch with public address + AddressRestrictedNAT = 4, // A = Device without portmap behind address-only restricted NAT + PortRestrictedNAT = 5, // P = Device without portmap behind address-and-port restricted NAT +} + +impl DialInfoClass { + // Is a signal required to do an inbound hole-punch? + pub fn requires_signal(&self) -> bool { + matches!( + self, + Self::Blocked | Self::AddressRestrictedNAT | Self::PortRestrictedNAT + ) + } + + // Does a relay node need to be allocated for this dial info? + // For full cone NAT, the relay itself may not be used but the keepalive sent to it + // is required to keep the NAT mapping valid in the router state table + pub fn requires_relay(&self) -> bool { + matches!( + self, + Self::FullConeNAT + | Self::Blocked + | Self::AddressRestrictedNAT + | Self::PortRestrictedNAT + ) + } +} + +// Ordering here matters, >= is used to check strength of sequencing requirement +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Sequencing { + NoPreference, + PreferOrdered, + EnsureOrdered, +} + +impl Default for Sequencing { + fn default() -> Self { + Self::NoPreference + } +} + +// Ordering here matters, >= is used to check strength of stability requirement +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Stability { + LowLatency, + Reliable, +} + +impl Default for Stability { + fn default() -> Self { + Self::LowLatency + } +} + +/// The choice of safety route to include in compiled routes +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum SafetySelection { + /// Don't use a safety route, only specify the sequencing preference + Unsafe(Sequencing), + /// Use a safety route and parameters specified by a SafetySpec + Safe(SafetySpec), +} + +impl SafetySelection { + pub fn get_sequencing(&self) -> Sequencing { + match self { + SafetySelection::Unsafe(seq) => *seq, + SafetySelection::Safe(ss) => ss.sequencing, + } + } +} + +/// Options for safety routes (sender privacy) +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SafetySpec { + /// preferred safety route if it still exists + pub preferred_route: Option, + /// must be greater than 0 + pub hop_count: usize, + /// prefer reliability over speed + pub stability: Stability, + /// prefer connection-oriented sequenced protocols + pub sequencing: Sequencing, +} + +// Keep member order appropriate for sorting < preference +#[derive( + Debug, + Clone, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoDetail { + pub class: DialInfoClass, + pub dial_info: DialInfo, +} + +impl MatchesDialInfoFilter for DialInfoDetail { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool { + self.dial_info.matches_filter(filter) + } +} + +impl DialInfoDetail { + pub fn ordered_sequencing_sort(a: &DialInfoDetail, b: &DialInfoDetail) -> core::cmp::Ordering { + if a.class < b.class { + return core::cmp::Ordering::Less; + } + if a.class > b.class { + return core::cmp::Ordering::Greater; + } + DialInfo::ordered_sequencing_sort(&a.dial_info, &b.dial_info) + } + pub const NO_SORT: std::option::Option< + for<'r, 's> fn( + &'r veilid_api::DialInfoDetail, + &'s veilid_api::DialInfoDetail, + ) -> std::cmp::Ordering, + > = None:: core::cmp::Ordering>; +} + +#[derive( + Copy, + Clone, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum NetworkClass { + InboundCapable = 0, // I = Inbound capable without relay, may require signal + OutboundOnly = 1, // O = Outbound only, inbound relay required except with reverse connect signal + WebApp = 2, // W = PWA, outbound relay is required in most cases + Invalid = 3, // X = Invalid network class, we don't know how to reach this node +} + +impl Default for NetworkClass { + fn default() -> Self { + Self::Invalid + } +} + +impl NetworkClass { + // Should an outbound relay be kept available? + pub fn outbound_wants_relay(&self) -> bool { + matches!(self, Self::WebApp) + } +} + +/// RoutingDomain-specific status for each node +/// is returned by the StatusA call + +/// PublicInternet RoutingDomain Status +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PublicInternetNodeStatus { + pub will_route: bool, + pub will_tunnel: bool, + pub will_signal: bool, + pub will_relay: bool, + pub will_validate_dial_info: bool, +} + +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct LocalNetworkNodeStatus { + pub will_relay: bool, + pub will_validate_dial_info: bool, +} + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum NodeStatus { + PublicInternet(PublicInternetNodeStatus), + LocalNetwork(LocalNetworkNodeStatus), +} + +impl NodeStatus { + pub fn will_route(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_route, + NodeStatus::LocalNetwork(_) => false, + } + } + pub fn will_tunnel(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_tunnel, + NodeStatus::LocalNetwork(_) => false, + } + } + pub fn will_signal(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_signal, + NodeStatus::LocalNetwork(_) => false, + } + } + pub fn will_relay(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_relay, + NodeStatus::LocalNetwork(ln) => ln.will_relay, + } + } + pub fn will_validate_dial_info(&self) -> bool { + match self { + NodeStatus::PublicInternet(pi) => pi.will_validate_dial_info, + NodeStatus::LocalNetwork(ln) => ln.will_validate_dial_info, + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct NodeInfo { + pub network_class: NetworkClass, + #[with(RkyvEnumSet)] + pub outbound_protocols: ProtocolTypeSet, + #[with(RkyvEnumSet)] + pub address_types: AddressTypeSet, + pub min_version: u8, + pub max_version: u8, + pub dial_info_detail_list: Vec, +} + +impl NodeInfo { + pub fn first_filtered_dial_info_detail( + &self, + sort: Option, + filter: F, + ) -> Option + where + S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, + F: Fn(&DialInfoDetail) -> bool, + { + if let Some(sort) = sort { + let mut dids = self.dial_info_detail_list.clone(); + dids.sort_by(sort); + for did in dids { + if filter(&did) { + return Some(did); + } + } + } else { + for did in &self.dial_info_detail_list { + if filter(did) { + return Some(did.clone()); + } + } + }; + None + } + + pub fn all_filtered_dial_info_details( + &self, + sort: Option, + filter: F, + ) -> Vec + where + S: Fn(&DialInfoDetail, &DialInfoDetail) -> std::cmp::Ordering, + F: Fn(&DialInfoDetail) -> bool, + { + let mut dial_info_detail_list = Vec::new(); + + if let Some(sort) = sort { + let mut dids = self.dial_info_detail_list.clone(); + dids.sort_by(sort); + for did in dids { + if filter(&did) { + dial_info_detail_list.push(did); + } + } + } else { + for did in &self.dial_info_detail_list { + if filter(did) { + dial_info_detail_list.push(did.clone()); + } + } + }; + dial_info_detail_list + } + + /// Does this node has some dial info + pub fn has_dial_info(&self) -> bool { + !self.dial_info_detail_list.is_empty() + } + + /// Is some relay required either for signal or inbound relay or outbound relay? + pub fn requires_relay(&self) -> bool { + match self.network_class { + NetworkClass::InboundCapable => { + for did in &self.dial_info_detail_list { + if did.class.requires_relay() { + return true; + } + } + } + NetworkClass::OutboundOnly => { + return true; + } + NetworkClass::WebApp => { + return true; + } + NetworkClass::Invalid => {} + } + false + } + + /// Can this node assist with signalling? Yes but only if it doesn't require signalling, itself. + pub fn can_signal(&self) -> bool { + // Must be inbound capable + if !matches!(self.network_class, NetworkClass::InboundCapable) { + return false; + } + // Do any of our dial info require signalling? if so, we can't offer signalling + for did in &self.dial_info_detail_list { + if did.class.requires_signal() { + return false; + } + } + true + } + + /// Can this node relay be an inbound relay? + pub fn can_inbound_relay(&self) -> bool { + // For now this is the same + self.can_signal() + } + + /// Is this node capable of validating dial info + pub fn can_validate_dial_info(&self) -> bool { + // For now this is the same + self.can_signal() + } +} + +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Direction { + Inbound, + Outbound, +} +pub type DirectionSet = EnumSet; + +// Keep member order appropriate for sorting < preference +// Must match DialInfo order +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum LowLevelProtocolType { + UDP, + TCP, +} + +impl LowLevelProtocolType { + pub fn is_connection_oriented(&self) -> bool { + matches!(self, LowLevelProtocolType::TCP) + } +} +pub type LowLevelProtocolTypeSet = EnumSet; + +// Keep member order appropriate for sorting < preference +// Must match DialInfo order +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum ProtocolType { + UDP, + TCP, + WS, + WSS, +} + +impl ProtocolType { + pub fn is_connection_oriented(&self) -> bool { + matches!( + self, + ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS + ) + } + pub fn low_level_protocol_type(&self) -> LowLevelProtocolType { + match self { + ProtocolType::UDP => LowLevelProtocolType::UDP, + ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS => LowLevelProtocolType::TCP, + } + } + pub fn sort_order(&self, sequencing: Sequencing) -> usize { + match self { + ProtocolType::UDP => { + if sequencing != Sequencing::NoPreference { + 3 + } else { + 0 + } + } + ProtocolType::TCP => { + if sequencing != Sequencing::NoPreference { + 0 + } else { + 1 + } + } + ProtocolType::WS => { + if sequencing != Sequencing::NoPreference { + 1 + } else { + 2 + } + } + ProtocolType::WSS => { + if sequencing != Sequencing::NoPreference { + 2 + } else { + 3 + } + } + } + } + pub fn all_ordered_set() -> ProtocolTypeSet { + ProtocolType::TCP | ProtocolType::WS | ProtocolType::WSS + } +} + +pub type ProtocolTypeSet = EnumSet; + +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, + EnumSetType, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum AddressType { + IPV4, + IPV6, +} +pub type AddressTypeSet = EnumSet; + +// Routing domain here is listed in order of preference, keep in order +#[allow(clippy::derive_hash_xor_eq)] +#[derive( + Debug, + Ord, + PartialOrd, + Hash, + EnumSetType, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[enumset(repr = "u8")] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum RoutingDomain { + LocalNetwork = 0, + PublicInternet = 1, +} +impl RoutingDomain { + pub const fn count() -> usize { + 2 + } + pub const fn all() -> [RoutingDomain; RoutingDomain::count()] { + // Routing domain here is listed in order of preference, keep in order + [RoutingDomain::LocalNetwork, RoutingDomain::PublicInternet] + } +} +pub type RoutingDomainSet = EnumSet; + +#[derive( + Copy, + Clone, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum Address { + IPV4(Ipv4Addr), + IPV6(Ipv6Addr), +} + +impl Default for Address { + fn default() -> Self { + Address::IPV4(Ipv4Addr::new(0, 0, 0, 0)) + } +} + +impl Address { + pub fn from_socket_addr(sa: SocketAddr) -> Address { + match sa { + SocketAddr::V4(v4) => Address::IPV4(*v4.ip()), + SocketAddr::V6(v6) => Address::IPV6(*v6.ip()), + } + } + pub fn from_ip_addr(addr: IpAddr) -> Address { + match addr { + IpAddr::V4(v4) => Address::IPV4(v4), + IpAddr::V6(v6) => Address::IPV6(v6), + } + } + pub fn address_type(&self) -> AddressType { + match self { + Address::IPV4(_) => AddressType::IPV4, + Address::IPV6(_) => AddressType::IPV6, + } + } + pub fn address_string(&self) -> String { + match self { + Address::IPV4(v4) => v4.to_string(), + Address::IPV6(v6) => v6.to_string(), + } + } + pub fn address_string_with_port(&self, port: u16) -> String { + match self { + Address::IPV4(v4) => format!("{}:{}", v4, port), + Address::IPV6(v6) => format!("[{}]:{}", v6, port), + } + } + pub fn is_unspecified(&self) -> bool { + match self { + Address::IPV4(v4) => ipv4addr_is_unspecified(v4), + Address::IPV6(v6) => ipv6addr_is_unspecified(v6), + } + } + pub fn is_global(&self) -> bool { + match self { + Address::IPV4(v4) => ipv4addr_is_global(v4) && !ipv4addr_is_multicast(v4), + Address::IPV6(v6) => ipv6addr_is_unicast_global(v6), + } + } + pub fn is_local(&self) -> bool { + match self { + Address::IPV4(v4) => { + ipv4addr_is_private(v4) + || ipv4addr_is_link_local(v4) + || ipv4addr_is_ietf_protocol_assignment(v4) + } + Address::IPV6(v6) => { + ipv6addr_is_unicast_site_local(v6) + || ipv6addr_is_unicast_link_local(v6) + || ipv6addr_is_unique_local(v6) + } + } + } + pub fn to_ip_addr(&self) -> IpAddr { + match self { + Self::IPV4(a) => IpAddr::V4(*a), + Self::IPV6(a) => IpAddr::V6(*a), + } + } + pub fn to_socket_addr(&self, port: u16) -> SocketAddr { + SocketAddr::new(self.to_ip_addr(), port) + } + pub fn to_canonical(&self) -> Address { + match self { + Address::IPV4(v4) => Address::IPV4(*v4), + Address::IPV6(v6) => match v6.to_ipv4() { + Some(v4) => Address::IPV4(v4), + None => Address::IPV6(*v6), + }, + } + } +} + +impl fmt::Display for Address { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Address::IPV4(v4) => write!(f, "{}", v4), + Address::IPV6(v6) => write!(f, "{}", v6), + } + } +} + +impl FromStr for Address { + type Err = VeilidAPIError; + fn from_str(host: &str) -> Result { + if let Ok(addr) = Ipv4Addr::from_str(host) { + Ok(Address::IPV4(addr)) + } else if let Ok(addr) = Ipv6Addr::from_str(host) { + Ok(Address::IPV6(addr)) + } else { + Err(VeilidAPIError::parse_error( + "Address::from_str failed", + host, + )) + } + } +} + +#[derive( + Copy, + Default, + Clone, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SocketAddress { + address: Address, + port: u16, +} + +impl SocketAddress { + pub fn new(address: Address, port: u16) -> Self { + Self { address, port } + } + pub fn from_socket_addr(sa: SocketAddr) -> SocketAddress { + Self { + address: Address::from_socket_addr(sa), + port: sa.port(), + } + } + pub fn address(&self) -> Address { + self.address + } + pub fn address_type(&self) -> AddressType { + self.address.address_type() + } + pub fn port(&self) -> u16 { + self.port + } + pub fn to_canonical(&self) -> SocketAddress { + SocketAddress { + address: self.address.to_canonical(), + port: self.port, + } + } + pub fn to_ip_addr(&self) -> IpAddr { + self.address.to_ip_addr() + } + pub fn to_socket_addr(&self) -> SocketAddr { + self.address.to_socket_addr(self.port) + } +} + +impl fmt::Display for SocketAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(f, "{}", self.to_socket_addr()) + } +} + +impl FromStr for SocketAddress { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + let sa = SocketAddr::from_str(s) + .map_err(|e| VeilidAPIError::parse_error("Failed to parse SocketAddress", e))?; + Ok(SocketAddress::from_socket_addr(sa)) + } +} + +////////////////////////////////////////////////////////////////// + +#[derive( + Copy, + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoFilter { + #[with(RkyvEnumSet)] + pub protocol_type_set: ProtocolTypeSet, + #[with(RkyvEnumSet)] + pub address_type_set: AddressTypeSet, +} + +impl Default for DialInfoFilter { + fn default() -> Self { + Self { + protocol_type_set: ProtocolTypeSet::all(), + address_type_set: AddressTypeSet::all(), + } + } +} + +impl DialInfoFilter { + pub fn all() -> Self { + Self { + protocol_type_set: ProtocolTypeSet::all(), + address_type_set: AddressTypeSet::all(), + } + } + pub fn with_protocol_type(mut self, protocol_type: ProtocolType) -> Self { + self.protocol_type_set = ProtocolTypeSet::only(protocol_type); + self + } + pub fn with_protocol_type_set(mut self, protocol_set: ProtocolTypeSet) -> Self { + self.protocol_type_set = protocol_set; + self + } + pub fn with_address_type(mut self, address_type: AddressType) -> Self { + self.address_type_set = AddressTypeSet::only(address_type); + self + } + pub fn with_address_type_set(mut self, address_set: AddressTypeSet) -> Self { + self.address_type_set = address_set; + self + } + pub fn filtered(mut self, other_dif: &DialInfoFilter) -> Self { + self.protocol_type_set &= other_dif.protocol_type_set; + self.address_type_set &= other_dif.address_type_set; + self + } + pub fn is_dead(&self) -> bool { + self.protocol_type_set.is_empty() || self.address_type_set.is_empty() + } +} + +impl fmt::Debug for DialInfoFilter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + let mut out = String::new(); + if self.protocol_type_set != ProtocolTypeSet::all() { + out += &format!("+{:?}", self.protocol_type_set); + } else { + out += "*"; + } + if self.address_type_set != AddressTypeSet::all() { + out += &format!("+{:?}", self.address_type_set); + } else { + out += "*"; + } + write!(f, "[{}]", out) + } +} + +pub trait MatchesDialInfoFilter { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool; +} + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoUDP { + pub socket_address: SocketAddress, +} + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoTCP { + pub socket_address: SocketAddress, +} + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoWS { + pub socket_address: SocketAddress, + pub request: String, +} + +#[derive( + Clone, + Default, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct DialInfoWSS { + pub socket_address: SocketAddress, + pub request: String, +} + +// Keep member order appropriate for sorting < preference +// Must match ProtocolType order +#[derive( + Clone, + Debug, + PartialEq, + PartialOrd, + Ord, + Eq, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +#[serde(tag = "kind")] +pub enum DialInfo { + UDP(DialInfoUDP), + TCP(DialInfoTCP), + WS(DialInfoWS), + WSS(DialInfoWSS), +} +impl Default for DialInfo { + fn default() -> Self { + DialInfo::UDP(DialInfoUDP::default()) + } +} + +impl fmt::Display for DialInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + DialInfo::UDP(di) => write!(f, "udp|{}", di.socket_address), + DialInfo::TCP(di) => write!(f, "tcp|{}", di.socket_address), + DialInfo::WS(di) => { + let url = format!("ws://{}", di.request); + let split_url = SplitUrl::from_str(&url).unwrap(); + match split_url.host { + SplitUrlHost::Hostname(_) => { + write!(f, "ws|{}|{}", di.socket_address.to_ip_addr(), di.request) + } + SplitUrlHost::IpAddr(a) => { + if di.socket_address.to_ip_addr() == a { + write!(f, "ws|{}", di.request) + } else { + panic!("resolved address does not match url: {}", di.request); + } + } + } + } + DialInfo::WSS(di) => { + let url = format!("wss://{}", di.request); + let split_url = SplitUrl::from_str(&url).unwrap(); + match split_url.host { + SplitUrlHost::Hostname(_) => { + write!(f, "wss|{}|{}", di.socket_address.to_ip_addr(), di.request) + } + SplitUrlHost::IpAddr(_) => { + panic!( + "secure websockets can not use ip address in request: {}", + di.request + ); + } + } + } + } + } +} + +impl FromStr for DialInfo { + type Err = VeilidAPIError; + fn from_str(s: &str) -> Result { + let (proto, rest) = s.split_once('|').ok_or_else(|| { + VeilidAPIError::parse_error("DialInfo::from_str missing protocol '|' separator", s) + })?; + match proto { + "udp" => { + let socket_address = SocketAddress::from_str(rest)?; + Ok(DialInfo::udp(socket_address)) + } + "tcp" => { + let socket_address = SocketAddress::from_str(rest)?; + Ok(DialInfo::tcp(socket_address)) + } + "ws" => { + let url = format!("ws://{}", rest); + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) + })?; + if split_url.scheme != "ws" || !url.starts_with("ws://") { + apibail_parse_error!("incorrect scheme for WS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(80u16); + + match rest.split_once('|') { + Some((sa, rest)) => { + let address = Address::from_str(sa)?; + + DialInfo::try_ws( + SocketAddress::new(address, url_port), + format!("ws://{}", rest), + ) + } + None => { + let address = Address::from_str(&split_url.host.to_string())?; + DialInfo::try_ws( + SocketAddress::new(address, url_port), + format!("ws://{}", rest), + ) + } + } + } + "wss" => { + let url = format!("wss://{}", rest); + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) + })?; + if split_url.scheme != "wss" || !url.starts_with("wss://") { + apibail_parse_error!("incorrect scheme for WSS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(443u16); + + let (a, rest) = rest.split_once('|').ok_or_else(|| { + VeilidAPIError::parse_error( + "DialInfo::from_str missing socket address '|' separator", + s, + ) + })?; + + let address = Address::from_str(a)?; + DialInfo::try_wss( + SocketAddress::new(address, url_port), + format!("wss://{}", rest), + ) + } + _ => Err(VeilidAPIError::parse_error( + "DialInfo::from_str has invalid scheme", + s, + )), + } + } +} + +impl DialInfo { + pub fn udp_from_socketaddr(socket_addr: SocketAddr) -> Self { + Self::UDP(DialInfoUDP { + socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), + }) + } + pub fn tcp_from_socketaddr(socket_addr: SocketAddr) -> Self { + Self::TCP(DialInfoTCP { + socket_address: SocketAddress::from_socket_addr(socket_addr).to_canonical(), + }) + } + pub fn udp(socket_address: SocketAddress) -> Self { + Self::UDP(DialInfoUDP { + socket_address: socket_address.to_canonical(), + }) + } + pub fn tcp(socket_address: SocketAddress) -> Self { + Self::TCP(DialInfoTCP { + socket_address: socket_address.to_canonical(), + }) + } + pub fn try_ws(socket_address: SocketAddress, url: String) -> Result { + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WS url: {}", e), &url) + })?; + if split_url.scheme != "ws" || !url.starts_with("ws://") { + apibail_parse_error!("incorrect scheme for WS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(80u16); + if url_port != socket_address.port() { + apibail_parse_error!("socket address port doesn't match url port", url); + } + if let SplitUrlHost::IpAddr(a) = split_url.host { + if socket_address.to_ip_addr() != a { + apibail_parse_error!( + format!("request address does not match socket address: {}", a), + socket_address + ); + } + } + Ok(Self::WS(DialInfoWS { + socket_address: socket_address.to_canonical(), + request: url[5..].to_string(), + })) + } + pub fn try_wss(socket_address: SocketAddress, url: String) -> Result { + let split_url = SplitUrl::from_str(&url).map_err(|e| { + VeilidAPIError::parse_error(format!("unable to split WSS url: {}", e), &url) + })?; + if split_url.scheme != "wss" || !url.starts_with("wss://") { + apibail_parse_error!("incorrect scheme for WSS dialinfo", url); + } + let url_port = split_url.port.unwrap_or(443u16); + if url_port != socket_address.port() { + apibail_parse_error!("socket address port doesn't match url port", url); + } + if !matches!(split_url.host, SplitUrlHost::Hostname(_)) { + apibail_parse_error!( + "WSS url can not use address format, only hostname format", + url + ); + } + Ok(Self::WSS(DialInfoWSS { + socket_address: socket_address.to_canonical(), + request: url[6..].to_string(), + })) + } + pub fn protocol_type(&self) -> ProtocolType { + match self { + Self::UDP(_) => ProtocolType::UDP, + Self::TCP(_) => ProtocolType::TCP, + Self::WS(_) => ProtocolType::WS, + Self::WSS(_) => ProtocolType::WSS, + } + } + pub fn address_type(&self) -> AddressType { + self.socket_address().address_type() + } + pub fn address(&self) -> Address { + match self { + Self::UDP(di) => di.socket_address.address, + Self::TCP(di) => di.socket_address.address, + Self::WS(di) => di.socket_address.address, + Self::WSS(di) => di.socket_address.address, + } + } + pub fn socket_address(&self) -> SocketAddress { + match self { + Self::UDP(di) => di.socket_address, + Self::TCP(di) => di.socket_address, + Self::WS(di) => di.socket_address, + Self::WSS(di) => di.socket_address, + } + } + pub fn to_ip_addr(&self) -> IpAddr { + match self { + Self::UDP(di) => di.socket_address.to_ip_addr(), + Self::TCP(di) => di.socket_address.to_ip_addr(), + Self::WS(di) => di.socket_address.to_ip_addr(), + Self::WSS(di) => di.socket_address.to_ip_addr(), + } + } + pub fn port(&self) -> u16 { + match self { + Self::UDP(di) => di.socket_address.port, + Self::TCP(di) => di.socket_address.port, + Self::WS(di) => di.socket_address.port, + Self::WSS(di) => di.socket_address.port, + } + } + pub fn set_port(&mut self, port: u16) { + match self { + Self::UDP(di) => di.socket_address.port = port, + Self::TCP(di) => di.socket_address.port = port, + Self::WS(di) => di.socket_address.port = port, + Self::WSS(di) => di.socket_address.port = port, + } + } + pub fn to_socket_addr(&self) -> SocketAddr { + match self { + Self::UDP(di) => di.socket_address.to_socket_addr(), + Self::TCP(di) => di.socket_address.to_socket_addr(), + Self::WS(di) => di.socket_address.to_socket_addr(), + Self::WSS(di) => di.socket_address.to_socket_addr(), + } + } + pub fn to_peer_address(&self) -> PeerAddress { + match self { + Self::UDP(di) => PeerAddress::new(di.socket_address, ProtocolType::UDP), + Self::TCP(di) => PeerAddress::new(di.socket_address, ProtocolType::TCP), + Self::WS(di) => PeerAddress::new(di.socket_address, ProtocolType::WS), + Self::WSS(di) => PeerAddress::new(di.socket_address, ProtocolType::WSS), + } + } + pub fn request(&self) -> Option { + match self { + Self::UDP(_) => None, + Self::TCP(_) => None, + Self::WS(di) => Some(format!("ws://{}", di.request)), + Self::WSS(di) => Some(format!("wss://{}", di.request)), + } + } + pub fn is_valid(&self) -> bool { + let socket_address = self.socket_address(); + let address = socket_address.address(); + let port = socket_address.port(); + (address.is_global() || address.is_local()) && port > 0 + } + + pub fn make_filter(&self) -> DialInfoFilter { + DialInfoFilter { + protocol_type_set: ProtocolTypeSet::only(self.protocol_type()), + address_type_set: AddressTypeSet::only(self.address_type()), + } + } + + pub fn try_vec_from_short, H: AsRef>( + short: S, + hostname: H, + ) -> Result, VeilidAPIError> { + let short = short.as_ref(); + let hostname = hostname.as_ref(); + + if short.len() < 2 { + apibail_parse_error!("invalid short url length", short); + } + let url = match &short[0..1] { + "U" => { + format!("udp://{}:{}", hostname, &short[1..]) + } + "T" => { + format!("tcp://{}:{}", hostname, &short[1..]) + } + "W" => { + format!("ws://{}:{}", hostname, &short[1..]) + } + "S" => { + format!("wss://{}:{}", hostname, &short[1..]) + } + _ => { + apibail_parse_error!("invalid short url type", short); + } + }; + Self::try_vec_from_url(url) + } + + pub fn try_vec_from_url>(url: S) -> Result, VeilidAPIError> { + let url = url.as_ref(); + let split_url = SplitUrl::from_str(url) + .map_err(|e| VeilidAPIError::parse_error(format!("unable to split url: {}", e), url))?; + + let port = match split_url.scheme.as_str() { + "udp" | "tcp" => split_url + .port + .ok_or_else(|| VeilidAPIError::parse_error("Missing port in udp url", url))?, + "ws" => split_url.port.unwrap_or(80u16), + "wss" => split_url.port.unwrap_or(443u16), + _ => { + apibail_parse_error!("Invalid dial info url scheme", split_url.scheme); + } + }; + + let socket_addrs = { + // Resolve if possible, WASM doesn't support resolution and doesn't need it to connect to the dialinfo + // This will not be used on signed dialinfo, only for bootstrapping, so we don't need to worry about + // the '0.0.0.0' address being propagated across the routing table + cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + vec![SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0,0,0,0)), port)] + } else { + match split_url.host { + SplitUrlHost::Hostname(_) => split_url + .host_port(port) + .to_socket_addrs() + .map_err(|_| VeilidAPIError::parse_error("couldn't resolve hostname in url", url))? + .collect(), + SplitUrlHost::IpAddr(a) => vec![SocketAddr::new(a, port)], + } + } + } + }; + + let mut out = Vec::new(); + for sa in socket_addrs { + out.push(match split_url.scheme.as_str() { + "udp" => Self::udp_from_socketaddr(sa), + "tcp" => Self::tcp_from_socketaddr(sa), + "ws" => Self::try_ws( + SocketAddress::from_socket_addr(sa).to_canonical(), + url.to_string(), + )?, + "wss" => Self::try_wss( + SocketAddress::from_socket_addr(sa).to_canonical(), + url.to_string(), + )?, + _ => { + unreachable!("Invalid dial info url scheme") + } + }); + } + Ok(out) + } + + pub async fn to_short(&self) -> (String, String) { + match self { + DialInfo::UDP(di) => ( + format!("U{}", di.socket_address.port()), + intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .unwrap_or_else(|_| di.socket_address.to_string()), + ), + DialInfo::TCP(di) => ( + format!("T{}", di.socket_address.port()), + intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .unwrap_or_else(|_| di.socket_address.to_string()), + ), + DialInfo::WS(di) => { + let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + ( + format!( + "W{}{}", + split_url.port.unwrap_or(80), + split_url + .path + .map(|p| format!("/{}", p)) + .unwrap_or_default() + ), + split_url.host.to_string(), + ) + } + DialInfo::WSS(di) => { + let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + ( + format!( + "S{}{}", + split_url.port.unwrap_or(443), + split_url + .path + .map(|p| format!("/{}", p)) + .unwrap_or_default() + ), + split_url.host.to_string(), + ) + } + } + } + pub async fn to_url(&self) -> String { + match self { + DialInfo::UDP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .map(|h| format!("udp://{}:{}", h, di.socket_address.port())) + .unwrap_or_else(|_| format!("udp://{}", di.socket_address)), + DialInfo::TCP(di) => intf::ptr_lookup(di.socket_address.to_ip_addr()) + .await + .map(|h| format!("tcp://{}:{}", h, di.socket_address.port())) + .unwrap_or_else(|_| format!("tcp://{}", di.socket_address)), + DialInfo::WS(di) => { + let mut split_url = SplitUrl::from_str(&format!("ws://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + split_url.to_string() + } + DialInfo::WSS(di) => { + let mut split_url = SplitUrl::from_str(&format!("wss://{}", di.request)).unwrap(); + if let SplitUrlHost::IpAddr(a) = split_url.host { + if let Ok(host) = intf::ptr_lookup(a).await { + split_url.host = SplitUrlHost::Hostname(host); + } + } + split_url.to_string() + } + } + } + + pub fn ordered_sequencing_sort(a: &DialInfo, b: &DialInfo) -> core::cmp::Ordering { + let ca = a.protocol_type().sort_order(Sequencing::EnsureOrdered); + let cb = b.protocol_type().sort_order(Sequencing::EnsureOrdered); + if ca < cb { + return core::cmp::Ordering::Less; + } + if ca > cb { + return core::cmp::Ordering::Greater; + } + match (a, b) { + (DialInfo::UDP(a), DialInfo::UDP(b)) => a.cmp(b), + (DialInfo::TCP(a), DialInfo::TCP(b)) => a.cmp(b), + (DialInfo::WS(a), DialInfo::WS(b)) => a.cmp(b), + (DialInfo::WSS(a), DialInfo::WSS(b)) => a.cmp(b), + _ => unreachable!(), + } + } +} + +impl MatchesDialInfoFilter for DialInfo { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool { + if !filter.protocol_type_set.contains(self.protocol_type()) { + return false; + } + if !filter.address_type_set.contains(self.address_type()) { + return false; + } + true + } +} + +////////////////////////////////////////////////////////////////////////// + +// Signed NodeInfo that can be passed around amongst peers and verifiable +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SignedDirectNodeInfo { + pub node_info: NodeInfo, + pub timestamp: Timestamp, + pub signature: Option, +} + +impl SignedDirectNodeInfo { + pub fn new( + node_id: NodeId, + node_info: NodeInfo, + timestamp: Timestamp, + signature: DHTSignature, + ) -> Result { + let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; + verify(&node_id.key, &node_info_bytes, &signature)?; + Ok(Self { + node_info, + timestamp, + signature: Some(signature), + }) + } + + pub fn with_secret( + node_id: NodeId, + node_info: NodeInfo, + secret: &DHTKeySecret, + ) -> Result { + let timestamp = get_aligned_timestamp(); + let node_info_bytes = Self::make_signature_bytes(&node_info, timestamp)?; + let signature = sign(&node_id.key, secret, &node_info_bytes)?; + Ok(Self { + node_info, + timestamp, + signature: Some(signature), + }) + } + + fn make_signature_bytes( + node_info: &NodeInfo, + timestamp: Timestamp, + ) -> Result, VeilidAPIError> { + let mut node_info_bytes = Vec::new(); + + // Add nodeinfo to signature + let mut ni_msg = ::capnp::message::Builder::new_default(); + let mut ni_builder = ni_msg.init_root::(); + encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; + node_info_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); + + // Add timestamp to signature + node_info_bytes.append(&mut timestamp.as_u64().to_le_bytes().to_vec()); + + Ok(node_info_bytes) + } + + pub fn with_no_signature(node_info: NodeInfo) -> Self { + Self { + node_info, + signature: None, + timestamp: get_aligned_timestamp(), + } + } + + pub fn has_valid_signature(&self) -> bool { + self.signature.is_some() + } +} + +/// Signed NodeInfo with a relay that can be passed around amongst peers and verifiable +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct SignedRelayedNodeInfo { + pub node_info: NodeInfo, + pub relay_id: NodeId, + pub relay_info: SignedDirectNodeInfo, + pub timestamp: Timestamp, + pub signature: DHTSignature, +} + +impl SignedRelayedNodeInfo { + pub fn new( + node_id: NodeId, + node_info: NodeInfo, + relay_id: NodeId, + relay_info: SignedDirectNodeInfo, + timestamp: Timestamp, + signature: DHTSignature, + ) -> Result { + let node_info_bytes = + Self::make_signature_bytes(&node_info, &relay_id, &relay_info, timestamp)?; + verify(&node_id.key, &node_info_bytes, &signature)?; + Ok(Self { + node_info, + relay_id, + relay_info, + signature, + timestamp, + }) + } + + pub fn with_secret( + node_id: NodeId, + node_info: NodeInfo, + relay_id: NodeId, + relay_info: SignedDirectNodeInfo, + secret: &DHTKeySecret, + ) -> Result { + let timestamp = get_aligned_timestamp(); + let node_info_bytes = + Self::make_signature_bytes(&node_info, &relay_id, &relay_info, timestamp)?; + let signature = sign(&node_id.key, secret, &node_info_bytes)?; + Ok(Self { + node_info, + relay_id, + relay_info, + signature, + timestamp, + }) + } + + fn make_signature_bytes( + node_info: &NodeInfo, + relay_id: &NodeId, + relay_info: &SignedDirectNodeInfo, + timestamp: Timestamp, + ) -> Result, VeilidAPIError> { + let mut sig_bytes = Vec::new(); + + // Add nodeinfo to signature + let mut ni_msg = ::capnp::message::Builder::new_default(); + let mut ni_builder = ni_msg.init_root::(); + encode_node_info(node_info, &mut ni_builder).map_err(VeilidAPIError::internal)?; + sig_bytes.append(&mut builder_to_vec(ni_msg).map_err(VeilidAPIError::internal)?); + + // Add relay id to signature + let mut rid_msg = ::capnp::message::Builder::new_default(); + let mut rid_builder = rid_msg.init_root::(); + encode_dht_key(&relay_id.key, &mut rid_builder).map_err(VeilidAPIError::internal)?; + sig_bytes.append(&mut builder_to_vec(rid_msg).map_err(VeilidAPIError::internal)?); + + // Add relay info to signature + let mut ri_msg = ::capnp::message::Builder::new_default(); + let mut ri_builder = ri_msg.init_root::(); + encode_signed_direct_node_info(relay_info, &mut ri_builder) + .map_err(VeilidAPIError::internal)?; + sig_bytes.append(&mut builder_to_vec(ri_msg).map_err(VeilidAPIError::internal)?); + + // Add timestamp to signature + sig_bytes.append(&mut timestamp.as_u64().to_le_bytes().to_vec()); + + Ok(sig_bytes) + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum SignedNodeInfo { + Direct(SignedDirectNodeInfo), + Relayed(SignedRelayedNodeInfo), +} + +impl SignedNodeInfo { + pub fn has_valid_signature(&self) -> bool { + match self { + SignedNodeInfo::Direct(d) => d.has_valid_signature(), + SignedNodeInfo::Relayed(_) => true, + } + } + + pub fn timestamp(&self) -> Timestamp { + match self { + SignedNodeInfo::Direct(d) => d.timestamp, + SignedNodeInfo::Relayed(r) => r.timestamp, + } + } + pub fn node_info(&self) -> &NodeInfo { + match self { + SignedNodeInfo::Direct(d) => &d.node_info, + SignedNodeInfo::Relayed(r) => &r.node_info, + } + } + pub fn relay_id(&self) -> Option { + match self { + SignedNodeInfo::Direct(_) => None, + SignedNodeInfo::Relayed(r) => Some(r.relay_id.clone()), + } + } + pub fn relay_info(&self) -> Option<&NodeInfo> { + match self { + SignedNodeInfo::Direct(_) => None, + SignedNodeInfo::Relayed(r) => Some(&r.relay_info.node_info), + } + } + pub fn relay_peer_info(&self) -> Option { + match self { + SignedNodeInfo::Direct(_) => None, + SignedNodeInfo::Relayed(r) => Some(PeerInfo::new( + r.relay_id.clone(), + SignedNodeInfo::Direct(r.relay_info.clone()), + )), + } + } + pub fn has_any_dial_info(&self) -> bool { + self.node_info().has_dial_info() + || self + .relay_info() + .map(|relay_ni| relay_ni.has_dial_info()) + .unwrap_or_default() + } + + pub fn has_sequencing_matched_dial_info(&self, sequencing: Sequencing) -> bool { + // Check our dial info + for did in &self.node_info().dial_info_detail_list { + match sequencing { + Sequencing::NoPreference | Sequencing::PreferOrdered => return true, + Sequencing::EnsureOrdered => { + if did.dial_info.protocol_type().is_connection_oriented() { + return true; + } + } + } + } + // Check our relay if we have one + return self + .relay_info() + .map(|relay_ni| { + for did in &relay_ni.dial_info_detail_list { + match sequencing { + Sequencing::NoPreference | Sequencing::PreferOrdered => return true, + Sequencing::EnsureOrdered => { + if did.dial_info.protocol_type().is_connection_oriented() { + return true; + } + } + } + } + false + }) + .unwrap_or_default(); + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerInfo { + pub node_id: NodeId, + pub signed_node_info: SignedNodeInfo, +} + +impl PeerInfo { + pub fn new(node_id: NodeId, signed_node_info: SignedNodeInfo) -> Self { + Self { + node_id, + signed_node_info, + } + } +} + +#[derive( + Copy, + Clone, + Debug, + PartialEq, + PartialOrd, + Eq, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerAddress { + protocol_type: ProtocolType, + #[serde(with = "json_as_string")] + socket_address: SocketAddress, +} + +impl PeerAddress { + pub fn new(socket_address: SocketAddress, protocol_type: ProtocolType) -> Self { + Self { + socket_address: socket_address.to_canonical(), + protocol_type, + } + } + + pub fn socket_address(&self) -> &SocketAddress { + &self.socket_address + } + + pub fn protocol_type(&self) -> ProtocolType { + self.protocol_type + } + + pub fn to_socket_addr(&self) -> SocketAddr { + self.socket_address.to_socket_addr() + } + + pub fn address_type(&self) -> AddressType { + self.socket_address.address_type() + } +} + +/// Represents the 5-tuple of an established connection +/// Not used to specify connections to create, that is reserved for DialInfo +/// +/// ConnectionDescriptors should never be from unspecified local addresses for connection oriented protocols +/// If the medium does not allow local addresses, None should have been used or 'new_no_local' +/// If we are specifying only a port, then the socket's 'local_address()' should have been used, since an +/// established connection is always from a real address to another real address. +#[derive( + Copy, + Clone, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct ConnectionDescriptor { + remote: PeerAddress, + local: Option, +} + +impl ConnectionDescriptor { + pub fn new(remote: PeerAddress, local: SocketAddress) -> Self { + assert!( + !remote.protocol_type().is_connection_oriented() || !local.address().is_unspecified() + ); + + Self { + remote, + local: Some(local), + } + } + pub fn new_no_local(remote: PeerAddress) -> Self { + Self { + remote, + local: None, + } + } + pub fn remote(&self) -> PeerAddress { + self.remote + } + pub fn remote_address(&self) -> &SocketAddress { + self.remote.socket_address() + } + pub fn local(&self) -> Option { + self.local + } + pub fn protocol_type(&self) -> ProtocolType { + self.remote.protocol_type + } + pub fn address_type(&self) -> AddressType { + self.remote.address_type() + } + pub fn make_dial_info_filter(&self) -> DialInfoFilter { + DialInfoFilter::all() + .with_protocol_type(self.protocol_type()) + .with_address_type(self.address_type()) + } +} + +impl MatchesDialInfoFilter for ConnectionDescriptor { + fn matches_filter(&self, filter: &DialInfoFilter) -> bool { + if !filter.protocol_type_set.contains(self.protocol_type()) { + return false; + } + if !filter.address_type_set.contains(self.address_type()) { + return false; + } + true + } +} + +////////////////////////////////////////////////////////////////////////// + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct LatencyStats { + #[serde(with = "json_as_string")] + pub fastest: TimestampDuration, // fastest latency in the ROLLING_LATENCIES_SIZE last latencies + #[serde(with = "json_as_string")] + pub average: TimestampDuration, // average latency over the ROLLING_LATENCIES_SIZE last latencies + #[serde(with = "json_as_string")] + pub slowest: TimestampDuration, // slowest latency in the ROLLING_LATENCIES_SIZE last latencies +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct TransferStats { + #[serde(with = "json_as_string")] + pub total: ByteCount, // total amount transferred ever + #[serde(with = "json_as_string")] + pub maximum: ByteCount, // maximum rate over the ROLLING_TRANSFERS_SIZE last amounts + #[serde(with = "json_as_string")] + pub average: ByteCount, // average rate over the ROLLING_TRANSFERS_SIZE last amounts + #[serde(with = "json_as_string")] + pub minimum: ByteCount, // minimum rate over the ROLLING_TRANSFERS_SIZE last amounts +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct TransferStatsDownUp { + pub down: TransferStats, + pub up: TransferStats, +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct RPCStats { + pub messages_sent: u32, // number of rpcs that have been sent in the total_time range + pub messages_rcvd: u32, // number of rpcs that have been received in the total_time range + pub questions_in_flight: u32, // number of questions issued that have yet to be answered + #[serde(with = "opt_json_as_string")] + pub last_question_ts: Option, // when the peer was last questioned (either successfully or not) and we wanted an answer + #[serde(with = "opt_json_as_string")] + pub last_seen_ts: Option, // when the peer was last seen for any reason, including when we first attempted to reach out to it + #[serde(with = "opt_json_as_string")] + pub first_consecutive_seen_ts: Option, // the timestamp of the first consecutive proof-of-life for this node (an answer or received question) + pub recent_lost_answers: u32, // number of answers that have been lost since we lost reliability + pub failed_to_send: u32, // number of messages that have failed to send since we last successfully sent one +} + +#[derive( + Clone, + Debug, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PeerStats { + #[serde(with = "json_as_string")] + pub time_added: Timestamp, // when the peer was added to the routing table + pub rpc_stats: RPCStats, // information about RPCs + pub latency: Option, // latencies for communications with the peer + pub transfer: TransferStatsDownUp, // Stats for communications with the peer +} + +pub type ValueChangeCallback = + Arc) -> SendPinBoxFuture<()> + Send + Sync + 'static>; + +///////////////////////////////////////////////////////////////////////////////////////////////////// + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum SignalInfo { + HolePunch { + // UDP Hole Punch Request + receipt: Vec, // Receipt to be returned after the hole punch + peer_info: PeerInfo, // Sender's peer info + }, + ReverseConnect { + // Reverse Connection Request + receipt: Vec, // Receipt to be returned by the reverse connection + peer_info: PeerInfo, // Sender's peer info + }, + // XXX: WebRTC +} + +///////////////////////////////////////////////////////////////////////////////////////////////////// +#[derive( + Copy, + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum TunnelMode { + Raw, + Turn, +} + +#[derive( + Copy, + Clone, + Debug, + PartialOrd, + PartialEq, + Eq, + Ord, + Serialize, + Deserialize, + RkyvArchive, + RkyvSerialize, + RkyvDeserialize, +)] +#[archive_attr(repr(u8), derive(CheckBytes))] +pub enum TunnelError { + BadId, // Tunnel ID was rejected + NoEndpoint, // Endpoint was unreachable + RejectedMode, // Endpoint couldn't provide mode + NoCapacity, // Endpoint is full +} + +#[derive(Clone, Debug, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct TunnelEndpoint { + pub mode: TunnelMode, + pub description: String, // XXX: TODO +} + +impl Default for TunnelEndpoint { + fn default() -> Self { + Self { + mode: TunnelMode::Raw, + description: "".to_string(), + } + } +} + +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct FullTunnel { + pub id: TunnelId, + pub timeout: TimestampDuration, + pub local: TunnelEndpoint, + pub remote: TunnelEndpoint, +} + +#[derive( + Clone, Debug, Default, Serialize, Deserialize, RkyvArchive, RkyvSerialize, RkyvDeserialize, +)] +#[archive_attr(repr(C), derive(CheckBytes))] +pub struct PartialTunnel { + pub id: TunnelId, + pub timeout: TimestampDuration, + pub local: TunnelEndpoint, +} diff --git a/veilid-core/src/veilid_config.rs b/veilid-core/src/veilid_config.rs index 79c37d22..8b207370 100644 --- a/veilid-core/src/veilid_config.rs +++ b/veilid-core/src/veilid_config.rs @@ -1,4 +1,3 @@ -use crate::xx::*; use crate::*; use rkyv::{Archive as RkyvArchive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize}; use serde::*; @@ -337,6 +336,8 @@ pub struct VeilidConfigRoutingTable { pub limit_attached_strong: u32, pub limit_attached_good: u32, pub limit_attached_weak: u32, + // xxx pub enable_public_internet: bool, + // xxx pub enable_local_network: bool, } #[derive( @@ -369,7 +370,6 @@ pub struct VeilidConfigNetwork { pub rpc: VeilidConfigRPC, pub dht: VeilidConfigDHT, pub upnp: bool, - pub natpmp: bool, pub detect_address_changes: bool, pub restricted_nat_retries: u32, pub tls: VeilidConfigTLS, @@ -599,11 +599,13 @@ impl VeilidConfig { macro_rules! get_config { ($key:expr) => { let keyname = &stringify!($key)[6..]; - $key = *cb(keyname.to_owned())?.downcast().map_err(|_| { - let err = format!("incorrect type for key {}", keyname); - debug!("{}", err); - VeilidAPIError::generic(err) - })?; + let v = cb(keyname.to_owned())?; + $key = match v.downcast() { + Ok(v) => *v, + Err(_) => { + apibail_generic!(format!("incorrect type for key {}", keyname)) + } + }; }; } @@ -665,7 +667,6 @@ impl VeilidConfig { get_config!(inner.network.rpc.max_route_hop_count); get_config!(inner.network.rpc.default_route_hop_count); get_config!(inner.network.upnp); - get_config!(inner.network.natpmp); get_config!(inner.network.detect_address_changes); get_config!(inner.network.restricted_nat_retries); get_config!(inner.network.tls.certificate_path); @@ -756,7 +757,7 @@ impl VeilidConfig { let mut out = &jvc; for k in keypath { if !out.has_key(k) { - apibail_parse!(format!("invalid subkey in key '{}'", key), k); + apibail_parse_error!(format!("invalid subkey in key '{}'", key), k); } out = &out[k]; } @@ -781,12 +782,12 @@ impl VeilidConfig { let mut out = &mut jvc; for k in objkeypath { if !out.has_key(*k) { - apibail_parse!(format!("invalid subkey in key '{}'", key), k); + apibail_parse_error!(format!("invalid subkey in key '{}'", key), k); } out = &mut out[*k]; } if !out.has_key(objkeyname) { - apibail_parse!(format!("invalid subkey in key '{}'", key), objkeyname); + apibail_parse_error!(format!("invalid subkey in key '{}'", key), objkeyname); } out[*objkeyname] = newval; jvc.to_string() diff --git a/veilid-core/src/veilid_layer_filter.rs b/veilid-core/src/veilid_layer_filter.rs index a40fbc7a..73e02a85 100644 --- a/veilid-core/src/veilid_layer_filter.rs +++ b/veilid-core/src/veilid_layer_filter.rs @@ -1,5 +1,4 @@ use super::*; -use crate::xx::*; use tracing::level_filters::LevelFilter; use tracing::subscriber::Interest; use tracing_subscriber::layer; diff --git a/veilid-core/src/veilid_rng.rs b/veilid-core/src/veilid_rng.rs deleted file mode 100644 index 529d5550..00000000 --- a/veilid-core/src/veilid_rng.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::*; - -use rand::{CryptoRng, Error, RngCore}; - -#[derive(Clone, Copy, Debug, Default)] -pub struct VeilidRng; - -impl CryptoRng for VeilidRng {} - -impl RngCore for VeilidRng { - fn next_u32(&mut self) -> u32 { - intf::get_random_u32() - } - - fn next_u64(&mut self) -> u64 { - intf::get_random_u64() - } - - fn fill_bytes(&mut self, dest: &mut [u8]) { - if let Err(e) = self.try_fill_bytes(dest) { - panic!("Error: {}", e); - } - } - - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - intf::random_bytes(dest).map_err(Error::new) - } -} diff --git a/veilid-core/tests/node.rs b/veilid-core/tests/node.rs deleted file mode 100644 index da301eeb..00000000 --- a/veilid-core/tests/node.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! Test suite for NodeJS -#![cfg(target_arch = "wasm32")] - -use veilid_core::tests::common::*; -use veilid_core::xx::*; -use wasm_bindgen_test::*; - -wasm_bindgen_test_configure!(); - -extern crate wee_alloc; -#[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; - -static SETUP_ONCE: Once = Once::new(); -pub fn setup() -> () { - SETUP_ONCE.call_once(|| { - console_error_panic_hook::set_once(); - wasm_logger::init(wasm_logger::Config::new(log::Level::Trace)); - }); -} - -#[wasm_bindgen_test] -async fn run_test_dht_key() { - setup(); - - test_dht_key::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_host_interface() { - setup(); - - test_host_interface::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_veilid_core() { - setup(); - - test_veilid_core::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_config() { - setup(); - - test_veilid_config::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_connection_table() { - setup(); - - test_connection_table::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_table_store() { - setup(); - - test_table_store::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_crypto() { - setup(); - - test_crypto::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_envelope_receipt() { - setup(); - - test_envelope_receipt::test_all().await; -} - -#[wasm_bindgen_test] -async fn run_test_async_tag_lock() { - setup(); - - test_async_tag_lock::test_all().await; -} diff --git a/veilid-core/tests/web.rs b/veilid-core/tests/web.rs index a5ac14b7..5024dfd8 100644 --- a/veilid-core/tests/web.rs +++ b/veilid-core/tests/web.rs @@ -1,8 +1,9 @@ //! Test suite for the Web and headless browsers. #![cfg(target_arch = "wasm32")] +#![recursion_limit = "256"] -use veilid_core::tests::common::*; -use veilid_core::xx::*; +use veilid_core::tests::*; +use veilid_core::tools::*; use wasm_bindgen_test::*; wasm_bindgen_test_configure!(run_in_browser); @@ -15,73 +16,70 @@ static SETUP_ONCE: Once = Once::new(); pub fn setup() -> () { SETUP_ONCE.call_once(|| { console_error_panic_hook::set_once(); - let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); - builder.set_report_logs_in_timings(false); - builder.set_max_level(Level::TRACE); - builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithConsoleColor); - tracing_wasm::set_as_global_default_with_config(builder.build()); + cfg_if! { + if #[cfg(feature = "tracing")] { + let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); + builder.set_report_logs_in_timings(false); + builder.set_max_level(Level::TRACE); + builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithConsoleColor); + tracing_wasm::set_as_global_default_with_config(builder.build()); + } else { + wasm_logger::init(wasm_logger::Config::default()); + } + } }); } -#[wasm_bindgen_test] -async fn run_test_dht_key() { - setup(); - - test_dht_key::test_all().await; -} - #[wasm_bindgen_test] async fn run_test_host_interface() { setup(); - test_host_interface::test_all().await; } +#[wasm_bindgen_test] +async fn run_test_dht_key() { + setup(); + test_dht_key::test_all().await; +} + #[wasm_bindgen_test] async fn run_test_veilid_core() { setup(); - test_veilid_core::test_all().await; } #[wasm_bindgen_test] -async fn run_test_config() { +async fn test_veilid_config() { setup(); - test_veilid_config::test_all().await; } #[wasm_bindgen_test] async fn run_test_connection_table() { setup(); - test_connection_table::test_all().await; } #[wasm_bindgen_test] -async fn run_test_table_store() { +async fn exec_test_table_store() { setup(); - test_table_store::test_all().await; } #[wasm_bindgen_test] -async fn run_test_crypto() { +async fn exec_test_protected_store() { setup(); + test_protected_store::test_all().await; +} +#[wasm_bindgen_test] +async fn exec_test_crypto() { + setup(); test_crypto::test_all().await; } #[wasm_bindgen_test] -async fn run_test_envelope_receipt() { +async fn exec_test_envelope_receipt() { setup(); - test_envelope_receipt::test_all().await; } - -#[wasm_bindgen_test] -async fn run_test_async_tag_lock() { - setup(); - - test_async_tag_lock::test_all().await; -} diff --git a/veilid-core/webdriver.json b/veilid-core/webdriver.json new file mode 100644 index 00000000..c2d6865e --- /dev/null +++ b/veilid-core/webdriver.json @@ -0,0 +1,15 @@ +{ + "moz:firefoxOptions": { + "prefs": { + "media.navigator.streams.fake": true, + "media.navigator.permission.disabled": true + }, + "args": [] + }, + "goog:chromeOptions": { + "args": [ + "--use-fake-device-for-media-stream", + "--use-fake-ui-for-media-stream" + ] + } +} diff --git a/veilid-flutter/android/build.gradle b/veilid-flutter/android/build.gradle index e16cf76c..a49c617a 100644 --- a/veilid-flutter/android/build.gradle +++ b/veilid-flutter/android/build.gradle @@ -6,13 +6,13 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:7.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } plugins { - id "org.mozilla.rust-android-gradle.rust-android" version "0.9.0" + id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" } group 'com.veilid.veilid' @@ -63,7 +63,7 @@ android { } } - ndkVersion '22.0.7026061' + ndkVersion '25.1.8937393' // Required to copy libc++_shared.so externalNativeBuild { @@ -85,12 +85,8 @@ cargo { libname = "veilid_flutter" targets = ["arm", "arm64", "x86", "x86_64"] targetDirectory = "../../target" - prebuiltToolchains = true pythonCommand = "python3" profile = gradle.startParameter.taskNames.any{it.toLowerCase().contains("debug")} ? "debug" : "release" - // features { - // defaultAnd("android_tests") - // } } afterEvaluate { diff --git a/veilid-flutter/example/.metadata b/veilid-flutter/example/.metadata index fd70cabc..32f17cc0 100644 --- a/veilid-flutter/example/.metadata +++ b/veilid-flutter/example/.metadata @@ -1,10 +1,30 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b + revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + - platform: macos + create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/veilid-flutter/example/android/app/build.gradle b/veilid-flutter/example/android/app/build.gradle index 3d452b27..870fc29d 100644 --- a/veilid-flutter/example/android/app/build.gradle +++ b/veilid-flutter/example/android/app/build.gradle @@ -27,7 +27,7 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { compileSdkVersion flutter.compileSdkVersion - ndkVersion '22.0.7026061' + ndkVersion '25.1.8937393' compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 diff --git a/veilid-flutter/example/fonts/CascadiaMonoPL.ttf b/veilid-flutter/example/fonts/CascadiaMonoPL.ttf new file mode 100644 index 00000000..801448e7 Binary files /dev/null and b/veilid-flutter/example/fonts/CascadiaMonoPL.ttf differ diff --git a/veilid-flutter/example/fonts/FiraCode-VF.ttf b/veilid-flutter/example/fonts/FiraCode-VF.ttf new file mode 100644 index 00000000..fd594139 Binary files /dev/null and b/veilid-flutter/example/fonts/FiraCode-VF.ttf differ diff --git a/veilid-flutter/example/fonts/Fraunces-Italic-VariableFont_SOFT,WONK,opsz,wght.ttf b/veilid-flutter/example/fonts/Fraunces-Italic-VariableFont_SOFT,WONK,opsz,wght.ttf new file mode 100644 index 00000000..970ddc39 Binary files /dev/null and b/veilid-flutter/example/fonts/Fraunces-Italic-VariableFont_SOFT,WONK,opsz,wght.ttf differ diff --git a/veilid-flutter/example/fonts/Fraunces-VariableFont_SOFT,WONK,opsz,wght.ttf b/veilid-flutter/example/fonts/Fraunces-VariableFont_SOFT,WONK,opsz,wght.ttf new file mode 100644 index 00000000..112bfa8a Binary files /dev/null and b/veilid-flutter/example/fonts/Fraunces-VariableFont_SOFT,WONK,opsz,wght.ttf differ diff --git a/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist b/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f9..9625e105 100644 --- a/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist +++ b/veilid-flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/veilid-flutter/example/ios/Podfile b/veilid-flutter/example/ios/Podfile index 1e8c3c90..88359b22 100644 --- a/veilid-flutter/example/ios/Podfile +++ b/veilid-flutter/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/veilid-flutter/example/ios/Podfile.lock b/veilid-flutter/example/ios/Podfile.lock index cded9b52..e4487a88 100644 --- a/veilid-flutter/example/ios/Podfile.lock +++ b/veilid-flutter/example/ios/Podfile.lock @@ -19,10 +19,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/veilid/ios" SPEC CHECKSUMS: - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - veilid: 804173397bd9d07c5a70ac6933cc2afbe54afc82 + veilid: f5c2e662f91907b30cf95762619526ac3e4512fd -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 -COCOAPODS: 1.11.2 +COCOAPODS: 1.11.3 diff --git a/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj b/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj index 6187169d..5a156bd7 100644 --- a/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj +++ b/veilid-flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -342,7 +342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -353,7 +353,7 @@ }; 249021D4217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = D12CAD1E1213967B2B34ABF5 /* Pods-Runner.profile.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -420,7 +420,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -470,7 +470,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -483,7 +483,7 @@ }; 97C147061CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + baseConfigurationReference = 0DEA6A7039338BC067F4FB23 /* Pods-Runner.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -505,7 +505,7 @@ }; 97C147071CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + baseConfigurationReference = 41720C3D885A5FD597C42EA7 /* Pods-Runner.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; diff --git a/veilid-flutter/example/lib/app.dart b/veilid-flutter/example/lib/app.dart new file mode 100644 index 00000000..4cb9dfbc --- /dev/null +++ b/veilid-flutter/example/lib/app.dart @@ -0,0 +1,222 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:veilid/veilid.dart'; +import 'package:loggy/loggy.dart'; +import 'package:veilid_example/veilid_theme.dart'; + +import 'log_terminal.dart'; +import 'log.dart'; +import 'history_wrapper.dart'; + +// Main App +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State with UiLoggy { + String _veilidVersion = 'Unknown'; + bool _startedUp = false; + Stream? _updateStream; + Future? _updateProcessor; + final _debugHistoryWrapper = HistoryWrapper(); + String? _errorText; + + @override + void initState() { + super.initState(); + + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String veilidVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + // We also handle the message potentially returning null. + try { + veilidVersion = Veilid.instance.veilidVersionString(); + } on Exception { + veilidVersion = 'Failed to get veilid version.'; + } + + // In case of hot restart shut down first + try { + await Veilid.instance.shutdownVeilidCore(); + } on Exception { + // + } + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _veilidVersion = veilidVersion; + }); + } + + Future processLog(VeilidLog log) async { + StackTrace? stackTrace; + Object? error; + final backtrace = log.backtrace; + if (backtrace != null) { + stackTrace = + StackTrace.fromString("$backtrace\n${StackTrace.current.toString()}"); + error = 'embedded stack trace for ${log.logLevel} ${log.message}'; + } + + switch (log.logLevel) { + case VeilidLogLevel.error: + loggy.error(log.message, error, stackTrace); + break; + case VeilidLogLevel.warn: + loggy.warning(log.message, error, stackTrace); + break; + case VeilidLogLevel.info: + loggy.info(log.message, error, stackTrace); + break; + case VeilidLogLevel.debug: + loggy.debug(log.message, error, stackTrace); + break; + case VeilidLogLevel.trace: + loggy.trace(log.message, error, stackTrace); + break; + } + } + + Future processUpdates() async { + var stream = _updateStream; + if (stream != null) { + await for (final update in stream) { + if (update is VeilidLog) { + await processLog(update); + } else if (update is VeilidAppMessage) { + loggy.info("AppMessage: ${update.json}"); + } else if (update is VeilidAppCall) { + loggy.info("AppCall: ${update.json}"); + } else { + loggy.trace("Update: ${update.json}"); + } + } + } + } + + Future toggleStartup(bool startup) async { + if (startup && !_startedUp) { + var updateStream = await Veilid.instance.startupVeilidCore( + await getDefaultVeilidConfig("Veilid Plugin Example")); + setState(() { + _updateStream = updateStream; + _updateProcessor = processUpdates(); + _startedUp = true; + }); + await Veilid.instance.attach(); + } else if (!startup && _startedUp) { + await Veilid.instance.shutdownVeilidCore(); + if (_updateProcessor != null) { + await _updateProcessor; + } + setState(() { + _updateProcessor = null; + _updateStream = null; + _startedUp = false; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Veilid Plugin Version $_veilidVersion'), + ), + body: Column(children: [ + const Expanded(child: LogTerminal()), + Container( + decoration: BoxDecoration( + color: materialBackgroundColor.shade100, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.15), + spreadRadius: 4, + blurRadius: 4, + ) + ]), + padding: const EdgeInsets.all(5.0), + child: Row(children: [ + Expanded( + child: pad(_debugHistoryWrapper.wrap( + setState, + TextField( + controller: _debugHistoryWrapper.controller, + decoration: newInputDecoration( + 'Debug Command', _errorText, _startedUp), + textInputAction: TextInputAction.unspecified, + enabled: _startedUp, + onChanged: (v) { + setState(() { + _errorText = null; + }); + }, + onSubmitted: (String v) async { + try { + if (v.isEmpty) { + return; + } + var res = await Veilid.instance.debug(v); + loggy.info(res); + setState(() { + _debugHistoryWrapper.submit(v); + }); + } on VeilidAPIException catch (e) { + setState(() { + _errorText = e.toDisplayError(); + }); + } + }), + ))), + pad( + Column(children: [ + const Text('Startup'), + Switch( + value: _startedUp, + onChanged: (bool value) async { + await toggleStartup(value); + }), + ]), + ), + pad(Column(children: [ + const Text('Log Level'), + DropdownButton( + value: loggy.level.logLevel, + onChanged: (LogLevel? newLevel) { + setState(() { + setRootLogLevel(newLevel); + }); + }, + items: const [ + DropdownMenuItem( + value: LogLevel.error, child: Text("Error")), + DropdownMenuItem( + value: LogLevel.warning, child: Text("Warning")), + DropdownMenuItem( + value: LogLevel.info, child: Text("Info")), + DropdownMenuItem( + value: LogLevel.debug, child: Text("Debug")), + DropdownMenuItem( + value: traceLevel, child: Text("Trace")), + DropdownMenuItem( + value: LogLevel.all, child: Text("All")), + ]), + ])), + ]), + ), + ])); + } +} diff --git a/veilid-flutter/example/lib/history_wrapper.dart b/veilid-flutter/example/lib/history_wrapper.dart new file mode 100644 index 00000000..da1a283a --- /dev/null +++ b/veilid-flutter/example/lib/history_wrapper.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +// TextField History Wrapper +class HistoryWrapper { + final List _history = []; + int _historyPosition = 0; + final _historyTextEditingController = TextEditingController(); + String _historyCurrentEdit = ""; + + TextEditingController get controller { + return _historyTextEditingController; + } + + void submit(String v) { + // add to history + if (_history.isEmpty || _history.last != v) { + _history.add(v); + if (_history.length > 100) { + _history.removeAt(0); + } + } + _historyPosition = _history.length; + _historyTextEditingController.text = ""; + } + + Widget wrap( + void Function(void Function())? stateSetter, TextField textField) { + void Function(void Function()) setState = stateSetter ?? (x) => x(); + return KeyboardListener( + onKeyEvent: (KeyEvent event) { + setState(() { + if (event.runtimeType == KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.arrowUp) { + if (_historyPosition > 0) { + if (_historyPosition == _history.length) { + _historyCurrentEdit = _historyTextEditingController.text; + } + _historyPosition -= 1; + _historyTextEditingController.text = _history[_historyPosition]; + } + } else if (event.runtimeType == KeyDownEvent && + event.logicalKey == LogicalKeyboardKey.arrowDown) { + if (_historyPosition < _history.length) { + _historyPosition += 1; + if (_historyPosition == _history.length) { + _historyTextEditingController.text = _historyCurrentEdit; + } else { + _historyTextEditingController.text = _history[_historyPosition]; + } + } + } else if (event.runtimeType == KeyDownEvent) { + _historyPosition = _history.length; + _historyCurrentEdit = _historyTextEditingController.text; + } + }); + }, + focusNode: FocusNode(onKey: (FocusNode node, RawKeyEvent event) { + if (event.logicalKey == LogicalKeyboardKey.arrowDown || + event.logicalKey == LogicalKeyboardKey.arrowUp) { + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }), + child: textField, + ); + } +} diff --git a/veilid-flutter/example/lib/log.dart b/veilid-flutter/example/lib/log.dart new file mode 100644 index 00000000..fd305c9a --- /dev/null +++ b/veilid-flutter/example/lib/log.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; +import 'package:veilid/veilid.dart'; +import 'package:loggy/loggy.dart'; +import 'package:ansicolor/ansicolor.dart'; + +// Loggy tools +const LogLevel traceLevel = LogLevel('Trace', 1); + +VeilidConfigLogLevel convertToVeilidConfigLogLevel(LogLevel? level) { + if (level == null) { + return VeilidConfigLogLevel.off; + } + switch (level) { + case LogLevel.error: + return VeilidConfigLogLevel.error; + case LogLevel.warning: + return VeilidConfigLogLevel.warn; + case LogLevel.info: + return VeilidConfigLogLevel.info; + case LogLevel.debug: + return VeilidConfigLogLevel.debug; + case traceLevel: + return VeilidConfigLogLevel.trace; + } + return VeilidConfigLogLevel.off; +} + +String wrapWithLogColor(LogLevel? level, String text) { + if (level == null) { + return text; + } + final pen = AnsiPen(); + ansiColorDisabled = false; + switch (level) { + case LogLevel.error: + pen + ..reset() + ..red(bold: true); + return pen(text); + case LogLevel.warning: + pen + ..reset() + ..yellow(bold: true); + return pen(text); + case LogLevel.info: + pen + ..reset() + ..white(bold: true); + return pen(text); + case LogLevel.debug: + pen + ..reset() + ..green(bold: true); + return pen(text); + case traceLevel: + pen + ..reset() + ..blue(bold: true); + return pen(text); + } + return text; +} + +void setRootLogLevel(LogLevel? level) { + Loggy('').level = getLogOptions(level); + Veilid.instance.changeLogLevel("all", convertToVeilidConfigLogLevel(level)); +} + +extension PrettyPrintLogRecord on LogRecord { + String pretty() { + final lstr = + wrapWithLogColor(level, '[${level.toString().substring(0, 1)}]'); + return '$lstr $message'; + } +} + +class CallbackPrinter extends LoggyPrinter { + CallbackPrinter() : super(); + + void Function(LogRecord)? callback; + + @override + void onLog(LogRecord record) { + debugPrint(record.pretty()); + callback?.call(record); + } + + void setCallback(Function(LogRecord)? cb) { + callback = cb; + } +} + +var globalTerminalPrinter = CallbackPrinter(); + +extension TraceLoggy on Loggy { + void trace(dynamic message, [Object? error, StackTrace? stackTrace]) => + log(traceLevel, message, error, stackTrace); +} + +LogOptions getLogOptions(LogLevel? level) { + return LogOptions( + level ?? LogLevel.all, + stackTraceLevel: LogLevel.error, + ); +} + +void initLoggy() { + Loggy.initLoggy( + logPrinter: globalTerminalPrinter, + logOptions: getLogOptions(null), + ); + + setRootLogLevel(LogLevel.info); +} diff --git a/veilid-flutter/example/lib/log_terminal.dart b/veilid-flutter/example/lib/log_terminal.dart new file mode 100644 index 00000000..b19d4af6 --- /dev/null +++ b/veilid-flutter/example/lib/log_terminal.dart @@ -0,0 +1,62 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:xterm/xterm.dart'; +import 'log.dart'; +import 'veilid_theme.dart'; + +const kDefaultTerminalStyle = TerminalStyle( + fontSize: kDefaultMonoTerminalFontSize, + height: kDefaultMonoTerminalFontHeight, + fontFamily: kDefaultMonoTerminalFontFamily); + +class LogTerminal extends StatefulWidget { + const LogTerminal({Key? key}) : super(key: key); + + @override + // ignore: library_private_types_in_public_api + _LogTerminalState createState() => _LogTerminalState(); +} + +class _LogTerminalState extends State { + final terminal = Terminal( + maxLines: 10000, + ); + + final terminalController = TerminalController(); + + @override + void initState() { + super.initState(); + terminal.setLineFeedMode(true); + globalTerminalPrinter + .setCallback((log) => {terminal.write("${log.pretty()}\n")}); + } + + @override + Widget build(BuildContext context) { + return TerminalView( + terminal, + textStyle: kDefaultTerminalStyle, + controller: terminalController, + autofocus: true, + backgroundOpacity: 0.9, + onSecondaryTapDown: (details, offset) async { + final selection = terminalController.selection; + if (selection != null) { + final text = terminal.buffer.getText(selection); + terminalController.clearSelection(); + await Clipboard.setData(ClipboardData(text: text)); + } else { + final data = await Clipboard.getData('text/plain'); + final text = data?.text; + if (text != null) { + terminal.paste(text); + } + } + }, + ); + } +} diff --git a/veilid-flutter/example/lib/main.dart b/veilid-flutter/example/lib/main.dart index 568b67e5..b93e91bd 100644 --- a/veilid-flutter/example/lib/main.dart +++ b/veilid-flutter/example/lib/main.dart @@ -1,295 +1,47 @@ import 'dart:async'; -import 'dart:typed_data'; -import 'dart:convert'; import 'package:flutter/material.dart'; -import 'package:flutter/foundation.dart' show kIsWeb; +import 'package:flutter/foundation.dart'; import 'package:veilid/veilid.dart'; -import 'package:flutter_loggy/flutter_loggy.dart'; -import 'package:loggy/loggy.dart'; +import 'package:flutter_acrylic/flutter_acrylic.dart'; -import 'config.dart'; +import 'veilid_theme.dart'; +import 'log.dart'; +import 'app.dart'; +import 'veilid_init.dart'; -// Loggy tools -const LogLevel traceLevel = LogLevel('Trace', 1); +/////////////////////////////// Acrylic -class ConsolePrinter extends LoggyPrinter { - ConsolePrinter(this.childPrinter) : super(); - - final LoggyPrinter childPrinter; - - @override - void onLog(LogRecord record) { - debugPrint(record.toString()); - childPrinter.onLog(record); - } +bool get isDesktop { + if (kIsWeb) return false; + return [ + TargetPlatform.windows, + TargetPlatform.linux, + TargetPlatform.macOS, + ].contains(defaultTargetPlatform); } -extension TraceLoggy on Loggy { - void trace(dynamic message, [Object? error, StackTrace? stackTrace]) => - log(traceLevel, message, error, stackTrace); +Future setupAcrylic() async { + await Window.initialize(); + await Window.makeTitlebarTransparent(); + await Window.setEffect( + effect: WindowEffect.aero, color: const Color(0xFFFFFFFF)); + await Window.setBlurViewState(MacOSBlurViewState.active); } -LogOptions getLogOptions(LogLevel? level) { - return LogOptions( - level ?? LogLevel.all, - stackTraceLevel: LogLevel.error, - ); -} - -VeilidConfigLogLevel convertToVeilidConfigLogLevel(LogLevel? level) { - if (level == null) { - return VeilidConfigLogLevel.off; - } - switch (level) { - case LogLevel.error: - return VeilidConfigLogLevel.error; - case LogLevel.warning: - return VeilidConfigLogLevel.warn; - case LogLevel.info: - return VeilidConfigLogLevel.info; - case LogLevel.debug: - return VeilidConfigLogLevel.debug; - case traceLevel: - return VeilidConfigLogLevel.trace; - } - return VeilidConfigLogLevel.off; -} - -void setRootLogLevel(LogLevel? level) { - Loggy('').level = getLogOptions(level); - Veilid.instance.changeLogLevel("all", convertToVeilidConfigLogLevel(level)); -} - -void initLoggy() { - Loggy.initLoggy( - logPrinter: StreamPrinter(ConsolePrinter( - const PrettyDeveloperPrinter(), - )), - logOptions: getLogOptions(null), - ); -} - -// Entrypoint +/////////////////////////////// Entrypoint void main() { WidgetsFlutterBinding.ensureInitialized(); + // Initialize Log initLoggy(); - if (kIsWeb) { - var platformConfig = VeilidWASMConfig( - logging: VeilidWASMConfigLogging( - performance: VeilidWASMConfigLoggingPerformance( - enabled: true, - level: VeilidConfigLogLevel.debug, - logsInTimings: true, - logsInConsole: true), - api: VeilidWASMConfigLoggingApi( - enabled: true, level: VeilidConfigLogLevel.info))); - Veilid.instance.initializeVeilidCore(platformConfig.json); - } else { - var platformConfig = VeilidFFIConfig( - logging: VeilidFFIConfigLogging( - terminal: VeilidFFIConfigLoggingTerminal( - enabled: false, - level: VeilidConfigLogLevel.debug, - ), - otlp: VeilidFFIConfigLoggingOtlp( - enabled: false, - level: VeilidConfigLogLevel.trace, - grpcEndpoint: "localhost:4317", - serviceName: "VeilidExample"), - api: VeilidFFIConfigLoggingApi( - enabled: true, level: VeilidConfigLogLevel.info))); - Veilid.instance.initializeVeilidCore(platformConfig.json); - } + // Initialize Veilid + veilidInit(); + + // Run the app runApp(MaterialApp( title: 'Veilid Plugin Demo', - theme: ThemeData( - primarySwatch: Colors.blue, - visualDensity: VisualDensity.adaptivePlatformDensity, - ), + theme: newVeilidTheme(), home: const MyApp())); } - -// Main App -class MyApp extends StatefulWidget { - const MyApp({Key? key}) : super(key: key); - - @override - State createState() => _MyAppState(); -} - -class _MyAppState extends State with UiLoggy { - String _veilidVersion = 'Unknown'; - Stream? _updateStream; - Future? _updateProcessor; - - @override - void initState() { - super.initState(); - setRootLogLevel(LogLevel.info); - initPlatformState(); - } - - // Platform messages are asynchronous, so we initialize in an async method. - Future initPlatformState() async { - String veilidVersion; - // Platform messages may fail, so we use a try/catch PlatformException. - // We also handle the message potentially returning null. - try { - veilidVersion = Veilid.instance.veilidVersionString(); - } on Exception { - veilidVersion = 'Failed to get veilid version.'; - } - - // In case of hot restart shut down first - try { - await Veilid.instance.shutdownVeilidCore(); - } on Exception { - // - } - - // If the widget was removed from the tree while the asynchronous platform - // message was in flight, we want to discard the reply rather than calling - // setState to update our non-existent appearance. - if (!mounted) return; - - setState(() { - _veilidVersion = veilidVersion; - }); - } - - Future processLog(VeilidLog log) async { - StackTrace? stackTrace; - Object? error; - final backtrace = log.backtrace; - if (backtrace != null) { - stackTrace = - StackTrace.fromString("$backtrace\n${StackTrace.current.toString()}"); - error = 'embedded stack trace for ${log.logLevel} ${log.message}'; - } - - switch (log.logLevel) { - case VeilidLogLevel.error: - loggy.error(log.message, error, stackTrace); - break; - case VeilidLogLevel.warn: - loggy.warning(log.message, error, stackTrace); - break; - case VeilidLogLevel.info: - loggy.info(log.message, error, stackTrace); - break; - case VeilidLogLevel.debug: - loggy.debug(log.message, error, stackTrace); - break; - case VeilidLogLevel.trace: - loggy.trace(log.message, error, stackTrace); - break; - } - } - - Future processUpdates() async { - var stream = _updateStream; - if (stream != null) { - await for (final update in stream) { - if (update is VeilidLog) { - await processLog(update); - } else if (update is VeilidAppMessage) { - loggy.info("AppMessage: ${update.json}"); - } else if (update is VeilidAppCall) { - loggy.info("AppCall: ${update.json}"); - } else { - loggy.trace("Update: ${update.json}"); - } - } - } - } - - @override - Widget build(BuildContext context) { - final ButtonStyle buttonStyle = - ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)); - - return Scaffold( - appBar: AppBar( - title: Text('Veilid Plugin Version $_veilidVersion'), - ), - body: Column(children: [ - Expanded( - child: Container( - color: ThemeData.dark().scaffoldBackgroundColor, - height: MediaQuery.of(context).size.height * 0.4, - child: LoggyStreamWidget(logLevel: loggy.level.logLevel), - )), - Container( - padding: const EdgeInsets.fromLTRB(8, 8, 8, 12), - child: Row(children: [ - ElevatedButton( - style: buttonStyle, - onPressed: _updateStream != null - ? null - : () async { - var updateStream = await Veilid.instance - .startupVeilidCore( - await getDefaultVeilidConfig()); - setState(() { - _updateStream = updateStream; - _updateProcessor = processUpdates(); - }); - await Veilid.instance.attach(); - }, - child: const Text('Startup'), - ), - ElevatedButton( - style: buttonStyle, - onPressed: _updateStream == null - ? null - : () async { - await Veilid.instance.shutdownVeilidCore(); - if (_updateProcessor != null) { - await _updateProcessor; - } - setState(() { - _updateProcessor = null; - _updateStream = null; - }); - }, - child: const Text('Shutdown'), - ), - ])), - Row(children: [ - Expanded( - child: TextField( - decoration: const InputDecoration( - border: OutlineInputBorder(), - labelText: 'Debug Command'), - textInputAction: TextInputAction.send, - onSubmitted: (String v) async { - loggy.info(await Veilid.instance.debug(v)); - })), - DropdownButton( - value: loggy.level.logLevel, - onChanged: (LogLevel? newLevel) { - setState(() { - setRootLogLevel(newLevel); - }); - }, - items: const [ - DropdownMenuItem( - value: LogLevel.error, child: Text("Error")), - DropdownMenuItem( - value: LogLevel.warning, child: Text("Warning")), - DropdownMenuItem( - value: LogLevel.info, child: Text("Info")), - DropdownMenuItem( - value: LogLevel.debug, child: Text("Debug")), - DropdownMenuItem( - value: traceLevel, child: Text("Trace")), - DropdownMenuItem( - value: LogLevel.all, child: Text("All")), - ]) - ]), - ])); - } -} diff --git a/veilid-flutter/example/lib/veilid_init.dart b/veilid-flutter/example/lib/veilid_init.dart new file mode 100644 index 00000000..7d0f8fb3 --- /dev/null +++ b/veilid-flutter/example/lib/veilid_init.dart @@ -0,0 +1,34 @@ +import 'package:flutter/foundation.dart'; +import 'package:veilid/veilid.dart'; + +// Initialize Veilid +// Call only once. +void veilidInit() { + if (kIsWeb) { + var platformConfig = VeilidWASMConfig( + logging: VeilidWASMConfigLogging( + performance: VeilidWASMConfigLoggingPerformance( + enabled: true, + level: VeilidConfigLogLevel.debug, + logsInTimings: true, + logsInConsole: false), + api: VeilidWASMConfigLoggingApi( + enabled: true, level: VeilidConfigLogLevel.info))); + Veilid.instance.initializeVeilidCore(platformConfig.json); + } else { + var platformConfig = VeilidFFIConfig( + logging: VeilidFFIConfigLogging( + terminal: VeilidFFIConfigLoggingTerminal( + enabled: false, + level: VeilidConfigLogLevel.debug, + ), + otlp: VeilidFFIConfigLoggingOtlp( + enabled: false, + level: VeilidConfigLogLevel.trace, + grpcEndpoint: "localhost:4317", + serviceName: "VeilidExample"), + api: VeilidFFIConfigLoggingApi( + enabled: true, level: VeilidConfigLogLevel.info))); + Veilid.instance.initializeVeilidCore(platformConfig.json); + } +} diff --git a/veilid-flutter/example/lib/veilid_theme.dart b/veilid-flutter/example/lib/veilid_theme.dart new file mode 100644 index 00000000..53489d09 --- /dev/null +++ b/veilid-flutter/example/lib/veilid_theme.dart @@ -0,0 +1,302 @@ +// Veilid Colors +// ------------- +// +// Base is origin color as annotated +// +// Shades from material color tool at: +// https://m2.material.io/design/color/the-color-system.html#tools-for-picking-colors +// + +import 'package:flutter/material.dart'; + +///////////////////////////////////////////////////////// +// Colors + +const Map primaryColorSwatch = { + 50: Color(0xffe9e9f3), + 100: Color(0xffc7c8e2), + 200: Color(0xffa2a5ce), + 300: Color(0xff7f82ba), + 400: Color(0xff6667ab), // Base (6667ab) + 500: Color(0xff4f4d9d), + 600: Color(0xff484594), + 700: Color(0xff403b88), + 800: Color(0xff39327c), + 900: Color(0xff2b2068), +}; + +const MaterialColor materialPrimaryColor = + MaterialColor(0xff6667ab, primaryColorSwatch); + +const Map primaryComplementaryColorSwatch = { + 50: Color(0xfffafdee), + 100: Color(0xfff4f9d3), + 200: Color(0xffedf6b8), + 300: Color(0xffe7f29f), + 400: Color(0xffe2ed8d), + 500: Color(0xffdde97d), + 600: Color(0xffd0d776), + 700: Color(0xffbdc16d), + 800: Color(0xffabaa66), // Base (#abaa66) + 900: Color(0xff8b845c), +}; + +const MaterialColor materialPrimaryComplementaryColor = + MaterialColor(0xffabaa66, primaryComplementaryColorSwatch); + +const Map primaryTriadicColorASwatch = { + 50: Color(0xfff0e4f0), + 100: Color(0xffdabcdb), + 200: Color(0xffc290c3), + 300: Color(0xffaa66ab), // Base (#aa66ab) + 400: Color(0xff98489a), + 500: Color(0xff892a8c), + 600: Color(0xff7d2786), + 700: Color(0xff6d217e), + 800: Color(0xff5e1b76), + 900: Color(0xff441168), +}; + +const MaterialColor materialPrimaryTriadicColorA = + MaterialColor(0xffaa66ab, primaryTriadicColorASwatch); + +const Map primaryTriadicColorBSwatch = { + 50: Color(0xffffe3dc), + 100: Color(0xfff7c4c2), + 200: Color(0xffdba2a2), + 300: Color(0xffc08180), + 400: Color(0xffab6667), // Base (#ab6667) + 500: Color(0xff964c4f), + 600: Color(0xff894347), + 700: Color(0xff78373d), + 800: Color(0xff672b35), + 900: Color(0xff551e2a), +}; + +const MaterialColor materialPrimaryTriadicColorB = + MaterialColor(0xffab6667, primaryTriadicColorBSwatch); + +const Map secondaryColorSwatch = { + 50: Color(0xffe3e8f7), + 100: Color(0xffb8c6eb), + 200: Color(0xff87a1dd), // Base (#87a1dd) + 300: Color(0xff527dce), + 400: Color(0xff1a61c1), + 500: Color(0xff0048b5), + 600: Color(0xff0040ab), + 700: Color(0xff0037a0), + 800: Color(0xff002e94), + 900: Color(0xff001d7f), +}; + +const MaterialColor materialSecondaryColor = + MaterialColor(0xff87a1dd, secondaryColorSwatch); + +const Map secondaryComplementaryColorSwatch = { + 50: Color(0xfff6f1e2), + 100: Color(0xffeadbb6), + 200: Color(0xffddc387), // Base (#ddc387) + 300: Color(0xffd2ac55), + 400: Color(0xffcd9c2d), + 500: Color(0xffc88c05), + 600: Color(0xffc58200), + 700: Color(0xffbf7400), + 800: Color(0xffb96700), + 900: Color(0xffb15000), +}; + +const MaterialColor materialSecondaryComplementaryColor = + MaterialColor(0xffddc387, secondaryComplementaryColorSwatch); + +const Map backgroundColorSwatch = { + 50: Color(0xffe3e5eb), + 100: Color(0xffb9bdce), + 200: Color(0xff8c93ac), + 300: Color(0xff626a8c), + 400: Color(0xff454d76), + 500: Color(0xff273263), + 600: Color(0xff222c5b), + 700: Color(0xff1a2451), + 800: Color(0xff131c45), + 900: Color(0xff0b0b2f), // Base (#0b0b2f) +}; + +const MaterialColor materialBackgroundColor = + MaterialColor(0xff0b0b2f, backgroundColorSwatch); + +const Map backgroundComplementaryColorSwatch = { + 50: Color(0xfffffed2), + 100: Color(0xfffdf9cd), + 200: Color(0xfff8f5c8), + 300: Color(0xfff3efc3), + 400: Color(0xffd1cea3), + 500: Color(0xffb4b187), + 600: Color(0xff89865e), + 700: Color(0xff73714a), + 800: Color(0xff53512c), + 900: Color(0xff2f2f0b), // Base (#2f2f0b) +}; + +const MaterialColor materialBackgroundComplementaryColor = + MaterialColor(0xff2f2f0b, backgroundComplementaryColorSwatch); + +const Map desaturatedColorSwatch = { + 50: Color(0xfff7fbff), + 100: Color(0xfff2f6ff), + 200: Color(0xffedf1fd), + 300: Color(0xffe3e7f2), + 400: Color(0xffc1c5d0), // Base (#c1c5d0) + 500: Color(0xffa3a7b2), + 600: Color(0xff797d87), + 700: Color(0xff656973), + 800: Color(0xff464952), + 900: Color(0xff242830), +}; + +const MaterialColor materialDesaturatedColor = + MaterialColor(0xffc1c5d0, desaturatedColorSwatch); + +const Map desaturatedComplementaryColorSwatch = { + 50: Color(0xffecebe5), + 100: Color(0xffd0ccc1), // Base (#d0ccc1) + 200: Color(0xffb0aa9a), + 300: Color(0xff908972), + 400: Color(0xff796f54), + 500: Color(0xff615837), + 600: Color(0xff584e31), + 700: Color(0xff4a4128), + 800: Color(0xff3e341f), + 900: Color(0xff312715), +}; + +const MaterialColor materialDesaturatedComplementaryColor = + MaterialColor(0xffd0ccc1, desaturatedComplementaryColorSwatch); + +const Map auxiliaryColorSwatch = { + 50: Color(0xffe7e4da), // Base (#e7e4da) + 100: Color(0xffc2bbac), + 200: Color(0xff988e7b), + 300: Color(0xff6f634c), + 400: Color(0xff53472b), + 500: Color(0xff372c0a), + 600: Color(0xff302403), + 700: Color(0xff261a00), + 800: Color(0xff1e0c00), + 900: Color(0xff160000), +}; + +const MaterialColor materialAuxiliaryColor = + MaterialColor(0xffe7e4da, auxiliaryColorSwatch); + +const Map auxiliaryComplementaryColorSwatch = { + 50: Color(0xffdadde7), // Base (#dadde7) + 100: Color(0xffa2abc6), + 200: Color(0xff6575a2), + 300: Color(0xff224580), + 400: Color(0xff00266c), + 500: Color(0xff000357), + 600: Color(0xff000051), + 700: Color(0xff000051), + 800: Color(0xff000050), + 900: Color(0xff00004f), +}; + +const MaterialColor materialAuxiliaryComplementaryColor = + MaterialColor(0xffdadde7, auxiliaryComplementaryColorSwatch); + +const Map popColorSwatch = { + 50: Color(0xfffee5f5), + 100: Color(0xfffbbde7), + 200: Color(0xfff88fd9), + 300: Color(0xfff259c9), // Base (#f259c9) + 400: Color(0xffec15bd), + 500: Color(0xffe100b0), + 600: Color(0xffd200ac), + 700: Color(0xffbe00a7), + 800: Color(0xffad00a1), + 900: Color(0xff8e0097), +}; + +const MaterialColor materialPopColor = + MaterialColor(0xfff259c9, popColorSwatch); + +const Map popComplentaryColorSwatch = { + 50: Color(0xffe6fdea), + 100: Color(0xffc2f9cb), + 200: Color(0xff96f6a9), + 300: Color(0xff59f282), // Base (#59f282) + 400: Color(0xff00ec60), + 500: Color(0xff00e446), + 600: Color(0xff00d33b), + 700: Color(0xff00bf2d), + 800: Color(0xff00ad21), + 900: Color(0xff008b05), +}; + +const MaterialColor materialPopComplementaryColor = + MaterialColor(0xff59f282, popComplentaryColorSwatch); + +///////////////////////////////////////////////////////// +// Spacing + +const kDefaultSpacingFactor = 4.0; + +const kDefaultMonoTerminalFontFamily = "Fira Code"; +const kDefaultMonoTerminalFontHeight = 1.2; +const kDefaultMonoTerminalFontSize = 12.0; + +double spacingFactor(double multiplier) { + return multiplier * kDefaultSpacingFactor; +} + +Padding pad(Widget child) { + return Padding( + padding: const EdgeInsets.all(kDefaultSpacingFactor), child: child); +} + +///////////////////////////////////////////////////////// +// Theme + +InputDecoration newInputDecoration( + String labelText, String? errorText, bool enabled) { + return InputDecoration( + labelText: labelText, + errorText: errorText, + filled: !enabled, + fillColor: materialPrimaryColor.shade300.withOpacity(0.1)); +} + +InputDecorationTheme newInputDecorationTheme() { + return InputDecorationTheme( + border: OutlineInputBorder( + borderSide: + BorderSide(color: materialPrimaryColor.shade300, width: 1.0)), + disabledBorder: OutlineInputBorder( + borderSide: + BorderSide(color: materialPrimaryColor.shade600, width: 1.0)), + focusedBorder: OutlineInputBorder( + borderSide: + BorderSide(color: materialPrimaryColor.shade900, width: 1.0)), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: materialPopColor.shade800, width: 1.0)), + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide(color: materialPopColor.shade600, width: 1.0)), + errorStyle: TextStyle( + color: materialPopColor.shade600, + letterSpacing: 1.1, + ), + floatingLabelBehavior: FloatingLabelBehavior.auto, + floatingLabelStyle: TextStyle( + color: materialPrimaryColor.shade900, + letterSpacing: 1.1, + )); +} + +ThemeData newVeilidTheme() { + return ThemeData( + primarySwatch: materialPrimaryColor, + secondaryHeaderColor: materialSecondaryColor, + visualDensity: VisualDensity.adaptivePlatformDensity, + inputDecorationTheme: newInputDecorationTheme(), + ); +} diff --git a/veilid-flutter/example/linux/flutter/generated_plugin_registrant.cc b/veilid-flutter/example/linux/flutter/generated_plugin_registrant.cc index cebc32de..3a10c1bf 100644 --- a/veilid-flutter/example/linux/flutter/generated_plugin_registrant.cc +++ b/veilid-flutter/example/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_acrylic_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterAcrylicPlugin"); + flutter_acrylic_plugin_register_with_registrar(flutter_acrylic_registrar); g_autoptr(FlPluginRegistrar) veilid_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "VeilidPlugin"); veilid_plugin_register_with_registrar(veilid_registrar); diff --git a/veilid-flutter/example/linux/flutter/generated_plugins.cmake b/veilid-flutter/example/linux/flutter/generated_plugins.cmake index 003d7b50..3989c459 100644 --- a/veilid-flutter/example/linux/flutter/generated_plugins.cmake +++ b/veilid-flutter/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_acrylic veilid ) diff --git a/veilid-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift b/veilid-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift index 5c63c74b..298c79e7 100644 --- a/veilid-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/veilid-flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,10 +5,12 @@ import FlutterMacOS import Foundation +import flutter_acrylic import path_provider_macos import veilid func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterAcrylicPlugin.register(with: registry.registrar(forPlugin: "FlutterAcrylicPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) VeilidPlugin.register(with: registry.registrar(forPlugin: "VeilidPlugin")) } diff --git a/veilid-flutter/example/macos/Podfile b/veilid-flutter/example/macos/Podfile index dade8dfa..b5c9c07c 100644 --- a/veilid-flutter/example/macos/Podfile +++ b/veilid-flutter/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.11' +platform :osx, '10.12.2' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -33,8 +33,28 @@ target 'Runner' do flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) end +# require 'json' +# require 'pathname' +# require 'fileutils' +# workspace_dir = File.dirname(JSON.parse(`cargo locate-project --workspace`)['root']) +# cargo_target_dir = File.join(workspace_dir, 'target') +# lipo_dir= File.join(cargo_target_dir, 'lipo-darwin') +# veilid_flutter = File.join(lipo_dir, 'libveilid_flutter.dylib') +# FileUtils.mkdir_p(lipo_dir) +# FileUtils.touch(veilid_flutter) + post_install do |installer| - installer.pods_project.targets.each do |target| + project = installer.pods_project + # reference = project.add_file_reference(veilid_flutter, project.main_group['Frameworks']) + + project.targets.each do |target| flutter_additional_macos_build_settings(target) + + # if (target.is_a? Xcodeproj::Project::Object::PBXNativeTarget) && target.name == 'veilid' + # buildfile = target.resources_build_phase.add_file_reference(reference) + # buildfile.settings = { 'ATTRIBUTES' => ['CodeSignOnCopy'] } + # end + end + end diff --git a/veilid-flutter/example/macos/Podfile.lock b/veilid-flutter/example/macos/Podfile.lock index b63b0c0e..38ac07f6 100644 --- a/veilid-flutter/example/macos/Podfile.lock +++ b/veilid-flutter/example/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - flutter_acrylic (0.1.0): + - FlutterMacOS - FlutterMacOS (1.0.0) - path_provider_macos (0.0.1): - FlutterMacOS @@ -6,11 +8,14 @@ PODS: - FlutterMacOS DEPENDENCIES: + - flutter_acrylic (from `Flutter/ephemeral/.symlinks/plugins/flutter_acrylic/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - veilid (from `Flutter/ephemeral/.symlinks/plugins/veilid/macos`) EXTERNAL SOURCES: + flutter_acrylic: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_acrylic/macos FlutterMacOS: :path: Flutter/ephemeral path_provider_macos: @@ -19,10 +24,11 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/veilid/macos SPEC CHECKSUMS: + flutter_acrylic: c3df24ae52ab6597197837ce59ef2a8542640c17 FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811 path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19 - veilid: 6bed3adec63fd8708a2ace498e0e17941c9fc32b + veilid: ef97d3a2d5fda3b25a4017eae65c37afa8035203 -PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c +PODFILE CHECKSUM: baac1aaddb7c3a00c396723592a2ffb396a7fed7 COCOAPODS: 1.11.3 diff --git a/veilid-flutter/example/macos/Runner.xcodeproj/project.pbxproj b/veilid-flutter/example/macos/Runner.xcodeproj/project.pbxproj index 28de84a7..2211295b 100644 --- a/veilid-flutter/example/macos/Runner.xcodeproj/project.pbxproj +++ b/veilid-flutter/example/macos/Runner.xcodeproj/project.pbxproj @@ -26,8 +26,9 @@ 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; - 400B233427A772C20074EE57 /* libveilid_flutter.dylib in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 400B233327A772C20074EE57 /* libveilid_flutter.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; - 8526E2FC060241577E5281A0 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E89F2D855D7BDA4933E1A2EC /* Pods_Runner.framework */; }; + 43B71B4F293C1CC400EF4986 /* libveilid_flutter.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 43B71B4B293C127700EF4986 /* libveilid_flutter.dylib */; settings = {ATTRIBUTES = (Weak, ); }; }; + 43B71B50293C1CC400EF4986 /* libveilid_flutter.dylib in Embed Libraries */ = {isa = PBXBuildFile; fileRef = 43B71B4B293C127700EF4986 /* libveilid_flutter.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; + 81649851C9DA1DA79054FE06 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52FF39A454BC3E9083B5E1BB /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -41,24 +42,24 @@ /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ - 33CC110E2044A8840003C045 /* Bundle Framework */ = { + 43B71B51293C1CC400EF4986 /* Embed Libraries */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( - 400B233427A772C20074EE57 /* libveilid_flutter.dylib in Bundle Framework */, + 43B71B50293C1CC400EF4986 /* libveilid_flutter.dylib in Embed Libraries */, ); - name = "Bundle Framework"; + name = "Embed Libraries"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 233B6A2BBF071AE96501823B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 269F3F1A4251F82E97C46D1F /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* veilid_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = veilid_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -70,12 +71,12 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 400B233327A772C20074EE57 /* libveilid_flutter.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libveilid_flutter.dylib; path = ../../../target/macos_lib/libveilid_flutter.dylib; sourceTree = ""; }; + 43B71B4B293C127700EF4986 /* libveilid_flutter.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libveilid_flutter.dylib; path = "../../../target/lipo-darwin/libveilid_flutter.dylib"; sourceTree = ""; }; + 52FF39A454BC3E9083B5E1BB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - B8606FB3C4AA619FC22C3115 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - E7AFD58527EF4CB477A3582A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - E89F2D855D7BDA4933E1A2EC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B2D03765D2CC78AE1EEB2F5F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + BC8854C07DAB1433F44BB126 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -83,7 +84,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8526E2FC060241577E5281A0 /* Pods_Runner.framework in Frameworks */, + 43B71B4F293C1CC400EF4986 /* libveilid_flutter.dylib in Frameworks */, + 81649851C9DA1DA79054FE06 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -104,19 +106,18 @@ 33CC10E42044A3C60003C045 = { isa = PBXGroup; children = ( - 400B233327A772C20074EE57 /* libveilid_flutter.dylib */, 33FAB671232836740065AC1E /* Runner */, 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - CB1628CFD484E4786F2B5B24 /* Pods */, + B3779D18CAC0309C4D986240 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* veilid_example.app */, + 33CC10ED2044A3C60003C045 /* example.app */, ); name = Products; sourceTree = ""; @@ -156,12 +157,12 @@ path = Runner; sourceTree = ""; }; - CB1628CFD484E4786F2B5B24 /* Pods */ = { + B3779D18CAC0309C4D986240 /* Pods */ = { isa = PBXGroup; children = ( - E7AFD58527EF4CB477A3582A /* Pods-Runner.debug.xcconfig */, - 233B6A2BBF071AE96501823B /* Pods-Runner.release.xcconfig */, - B8606FB3C4AA619FC22C3115 /* Pods-Runner.profile.xcconfig */, + B2D03765D2CC78AE1EEB2F5F /* Pods-Runner.debug.xcconfig */, + BC8854C07DAB1433F44BB126 /* Pods-Runner.release.xcconfig */, + 269F3F1A4251F82E97C46D1F /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -169,7 +170,8 @@ D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - E89F2D855D7BDA4933E1A2EC /* Pods_Runner.framework */, + 43B71B4B293C127700EF4986 /* libveilid_flutter.dylib */, + 52FF39A454BC3E9083B5E1BB /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -181,13 +183,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 53C43C97D5A63C16E369AAE5 /* [CP] Check Pods Manifest.lock */, + BEF36EDF1EEEA8E0EFCE11A5 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, - 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - 985C53EA7A19701CA562A7E5 /* [CP] Embed Pods Frameworks */, + 6C9005308B324F4C4A5637A3 /* [CP] Embed Pods Frameworks */, + 43B71B51293C1CC400EF4986 /* Embed Libraries */, ); buildRules = ( ); @@ -196,7 +198,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* veilid_example.app */; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -292,9 +294,26 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire\n"; }; - 53C43C97D5A63C16E369AAE5 /* [CP] Check Pods Manifest.lock */ = { + 6C9005308B324F4C4A5637A3 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + BEF36EDF1EEEA8E0EFCE11A5 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -316,23 +335,6 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 985C53EA7A19701CA562A7E5 /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -407,7 +409,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -422,13 +424,21 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + /usr/lib/swift, + "../../../target/lipo-darwin", + ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; @@ -486,7 +496,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12.2; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -533,7 +543,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.11; + MACOSX_DEPLOYMENT_TARGET = 10.12.2; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -548,13 +558,21 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + /usr/lib/swift, + "../../../target/lipo-darwin", + ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -568,13 +586,21 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"${DT_TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + /usr/lib/swift, + "../../../target/lipo-darwin", + ); PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_VERSION = 5.0; }; diff --git a/veilid-flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/veilid-flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index fff882b8..fb7259e1 100644 --- a/veilid-flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/veilid-flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png index 3c4935a7..82b6f9d9 100644 Binary files a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png and b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png index ed4cc164..13b35eba 100644 Binary files a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png and b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png index 483be613..0a3f5fa4 100644 Binary files a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png and b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png index bcbf36df..bdb57226 100644 Binary files a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png and b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png index 9c0a6528..f083318e 100644 Binary files a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png and b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png index e71a7261..326c0e72 100644 Binary files a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png and b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png index 8a31fe2d..2f1632cf 100644 Binary files a/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png and b/veilid-flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/veilid-flutter/example/macos/Runner/Base.lproj/MainMenu.xib b/veilid-flutter/example/macos/Runner/Base.lproj/MainMenu.xib index 537341ab..80e867a4 100644 --- a/veilid-flutter/example/macos/Runner/Base.lproj/MainMenu.xib +++ b/veilid-flutter/example/macos/Runner/Base.lproj/MainMenu.xib @@ -323,6 +323,10 @@ + + + + diff --git a/veilid-flutter/example/macos/Runner/Configs/AppInfo.xcconfig b/veilid-flutter/example/macos/Runner/Configs/AppInfo.xcconfig index 538e53b2..597056ec 100644 --- a/veilid-flutter/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/veilid-flutter/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = veilid_example +PRODUCT_NAME = example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.veilid.veilidExample +PRODUCT_BUNDLE_IDENTIFIER = com.veilid.example // The copyright displayed in application information PRODUCT_COPYRIGHT = Copyright © 2022 com.veilid. All rights reserved. diff --git a/veilid-flutter/example/pubspec.lock b/veilid-flutter/example/pubspec.lock index 2a4e58ce..74aca102 100644 --- a/veilid-flutter/example/pubspec.lock +++ b/veilid-flutter/example/pubspec.lock @@ -1,6 +1,13 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + ansicolor: + dependency: "direct main" + description: + name: ansicolor + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" async: dependency: transitive description: @@ -43,6 +50,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.1" cupertino_icons: dependency: "direct main" description: @@ -50,6 +64,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + equatable: + dependency: transitive + description: + name: equatable + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -76,6 +97,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_acrylic: + dependency: "direct main" + description: + name: flutter_acrylic + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0+2" flutter_lints: dependency: "direct dev" description: @@ -83,13 +111,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" - flutter_loggy: - dependency: "direct main" - description: - name: flutter_loggy - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -113,14 +134,14 @@ packages: name: lints url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.0.1" loggy: dependency: "direct main" description: name: loggy url: "https://pub.dartlang.org" source: hosted - version: "2.0.1+1" + version: "2.0.3" matcher: dependency: transitive description: @@ -162,7 +183,7 @@ packages: name: path_provider_android url: "https://pub.dartlang.org" source: hosted - version: "2.0.20" + version: "2.0.22" path_provider_ios: dependency: transitive description: @@ -190,7 +211,7 @@ packages: name: path_provider_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.5" path_provider_windows: dependency: transitive description: @@ -205,13 +226,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.1.0" + platform_info: + dependency: transitive + description: + name: platform_info + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.1.3" process: dependency: transitive description: @@ -219,13 +247,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.2.4" - rxdart: + quiver: dependency: transitive description: - name: rxdart + name: quiver url: "https://pub.dartlang.org" source: hosted - version: "0.27.5" + version: "3.1.0" sky_engine: dependency: transitive description: flutter @@ -273,6 +301,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.12" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.1" vector_math: dependency: transitive description: @@ -293,7 +328,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.1.2" xdg_directories: dependency: transitive description: @@ -301,6 +336,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0+2" + xterm: + dependency: "direct main" + description: + name: xterm + url: "https://pub.dartlang.org" + source: hosted + version: "3.4.0" sdks: - dart: ">=2.17.0 <3.0.0" + dart: ">=2.18.0 <3.0.0" flutter: ">=3.0.0" diff --git a/veilid-flutter/example/pubspec.yaml b/veilid-flutter/example/pubspec.yaml index 73ee637f..a781e59b 100644 --- a/veilid-flutter/example/pubspec.yaml +++ b/veilid-flutter/example/pubspec.yaml @@ -7,7 +7,7 @@ version: 1.0.0+1 publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.16.1 <3.0.0" + sdk: ">=2.17.0 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -33,9 +33,11 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 loggy: ^2.0.1+1 - flutter_loggy: ^2.0.1 path_provider: ^2.0.11 path: ^1.8.1 + xterm: ^3.4.0 + flutter_acrylic: ^1.0.0+2 + ansicolor: ^2.0.1 dev_dependencies: flutter_test: @@ -88,3 +90,15 @@ flutter: # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages + fonts: + - family: Cascadia Mono + fonts: + - asset: fonts/CascadiaMonoPL.ttf + - family: Fira Code + fonts: + - asset: fonts/FiraCode-VF.ttf + - family: Fraunces + fonts: + - asset: fonts/Fraunces-VariableFont_SOFT,WONK,opsz,wght.ttf + - asset: fonts/Fraunces-Italic-VariableFont_SOFT,WONK,opsz,wght.ttf + style: italic \ No newline at end of file diff --git a/veilid-flutter/example/test/widget_test.dart b/veilid-flutter/example/test/widget_test.dart index a0ef6c08..551377fc 100644 --- a/veilid-flutter/example/test/widget_test.dart +++ b/veilid-flutter/example/test/widget_test.dart @@ -8,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:veilid_example/main.dart'; +import 'package:veilid_example/app.dart'; void main() { testWidgets('Verify Platform version', (WidgetTester tester) async { @@ -18,8 +18,8 @@ void main() { // Verify that platform version is retrieved. expect( find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), + (Widget widget) => + widget is Text && widget.data!.startsWith('Running on:'), ), findsOneWidget, ); diff --git a/veilid-flutter/example/web/index.html b/veilid-flutter/example/web/index.html index fe1c65dc..373dae36 100644 --- a/veilid-flutter/example/web/index.html +++ b/veilid-flutter/example/web/index.html @@ -1,5 +1,6 @@ + - + - veilid_example + Veilid Example + + + + + - + - + - + + \ No newline at end of file diff --git a/veilid-flutter/example/windows/flutter/generated_plugin_registrant.cc b/veilid-flutter/example/windows/flutter/generated_plugin_registrant.cc index 72dbdef2..df015a29 100644 --- a/veilid-flutter/example/windows/flutter/generated_plugin_registrant.cc +++ b/veilid-flutter/example/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterAcrylicPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterAcrylicPlugin")); VeilidPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("VeilidPlugin")); } diff --git a/veilid-flutter/example/windows/flutter/generated_plugins.cmake b/veilid-flutter/example/windows/flutter/generated_plugins.cmake index 658ec856..4c215c3a 100644 --- a/veilid-flutter/example/windows/flutter/generated_plugins.cmake +++ b/veilid-flutter/example/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_acrylic veilid ) diff --git a/veilid-flutter/ios/veilid.podspec b/veilid-flutter/ios/veilid.podspec index 9088b982..422e8889 100644 --- a/veilid-flutter/ios/veilid.podspec +++ b/veilid-flutter/ios/veilid.podspec @@ -23,18 +23,21 @@ Veilid Network Plugin require 'json' require 'pathname' - cargo_target_dir = File.join(File.dirname(JSON.parse(`cargo locate-project`)['root']), 'target') + + script_dir = File.realpath(File.expand_path(__dir__)) + workspace_dir = File.dirname(JSON.parse(`(cd #{script_dir}; cargo locate-project --workspace)`)['root']) + cargo_target_dir = File.join(workspace_dir, 'target') s.xcconfig = { - 'OTHER_LDFLAGS' => "-Wl,-force_load,#{File.join(cargo_target_dir, 'ios_lib', 'libveilid_flutter.a')}", - "LIBRARY_SEARCH_PATHS" => File.join(cargo_target_dir, 'ios_lib') + 'OTHER_LDFLAGS' => "-Wl,-force_load,#{File.join(cargo_target_dir, 'lipo-ios', 'libveilid_flutter.a')}", + "LIBRARY_SEARCH_PATHS" => File.join(cargo_target_dir, 'lipo-ios') } s.script_phase = { :name => 'Cargo Build', - :script => File.join(File.dirname(__dir__), 'rust', 'ios_build.sh'), + :script => File.join(workspace_dir, 'scripts', 'ios_build.sh') + ' veilid_flutter', :execution_position => :before_compile - # :output_files => [ File.join(cargo_target_dir, 'ios_lib', 'libveilid_flutter.a') ] + # :output_files => [ File.join(cargo_target_dir, 'lipo-ios', 'libveilid_flutter.a') ] } end diff --git a/veilid-flutter/lib/base64url_no_pad.dart b/veilid-flutter/lib/base64url_no_pad.dart new file mode 100644 index 00000000..81f97a2d --- /dev/null +++ b/veilid-flutter/lib/base64url_no_pad.dart @@ -0,0 +1,15 @@ +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/example/lib/config.dart b/veilid-flutter/lib/default_config.dart similarity index 95% rename from veilid-flutter/example/lib/config.dart rename to veilid-flutter/lib/default_config.dart index aaeea802..51800a03 100644 --- a/veilid-flutter/example/lib/config.dart +++ b/veilid-flutter/lib/default_config.dart @@ -1,11 +1,11 @@ -import 'package:veilid/veilid.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; +import 'veilid.dart'; -Future getDefaultVeilidConfig() async { +Future getDefaultVeilidConfig(String programName) async { return VeilidConfig( - programName: "Veilid Plugin Test", + programName: programName, namespace: "", capabilities: VeilidConfigCapabilities( protocolUDP: !kIsWeb, @@ -46,8 +46,8 @@ Future getDefaultVeilidConfig() async { clientWhitelistTimeoutMs: 300000, reverseConnectionReceiptTimeMs: 5000, holePunchReceiptTimeMs: 5000, - nodeId: "", - nodeIdSecret: "", + nodeId: null, + nodeIdSecret: null, bootstrap: kIsWeb ? ["ws://bootstrap.dev.veilid.net:5150/ws"] : ["bootstrap.dev.veilid.net"], @@ -84,7 +84,6 @@ Future getDefaultVeilidConfig() async { validateDialInfoReceiptTimeMs: 2000, ), upnp: true, - natpmp: true, detectAddressChanges: true, restrictedNatRetries: 0, tls: VeilidConfigTLS( diff --git a/veilid-flutter/lib/veilid.dart b/veilid-flutter/lib/veilid.dart index 4c539782..3a8baea4 100644 --- a/veilid-flutter/lib/veilid.dart +++ b/veilid-flutter/lib/veilid.dart @@ -8,8 +8,12 @@ import 'veilid_stub.dart' if (dart.library.io) 'veilid_ffi.dart' if (dart.library.js) 'veilid_js.dart'; +import 'base64url_no_pad.dart'; + ////////////////////////////////////////////////////////// +export 'default_config.dart'; + ////////////////////////////////////////////////////////// // FFI Platform-specific config @@ -29,7 +33,7 @@ class VeilidFFIConfigLoggingTerminal { }; } - VeilidFFIConfigLoggingTerminal.fromJson(Map json) + VeilidFFIConfigLoggingTerminal.fromJson(dynamic json) : enabled = json['enabled'], level = veilidConfigLogLevelFromJson(json['level']); } @@ -56,7 +60,7 @@ class VeilidFFIConfigLoggingOtlp { }; } - VeilidFFIConfigLoggingOtlp.fromJson(Map json) + VeilidFFIConfigLoggingOtlp.fromJson(dynamic json) : enabled = json['enabled'], level = veilidConfigLogLevelFromJson(json['level']), grpcEndpoint = json['grpc_endpoint'], @@ -79,7 +83,7 @@ class VeilidFFIConfigLoggingApi { }; } - VeilidFFIConfigLoggingApi.fromJson(Map json) + VeilidFFIConfigLoggingApi.fromJson(dynamic json) : enabled = json['enabled'], level = veilidConfigLogLevelFromJson(json['level']); } @@ -100,7 +104,7 @@ class VeilidFFIConfigLogging { }; } - VeilidFFIConfigLogging.fromJson(Map json) + VeilidFFIConfigLogging.fromJson(dynamic json) : terminal = VeilidFFIConfigLoggingTerminal.fromJson(json['terminal']), otlp = VeilidFFIConfigLoggingOtlp.fromJson(json['otlp']), api = VeilidFFIConfigLoggingApi.fromJson(json['api']); @@ -148,7 +152,7 @@ class VeilidWASMConfigLoggingPerformance { }; } - VeilidWASMConfigLoggingPerformance.fromJson(Map json) + VeilidWASMConfigLoggingPerformance.fromJson(dynamic json) : enabled = json['enabled'], level = veilidConfigLogLevelFromJson(json['level']), logsInTimings = json['logs_in_timings'], @@ -171,7 +175,7 @@ class VeilidWASMConfigLoggingApi { }; } - VeilidWASMConfigLoggingApi.fromJson(Map json) + VeilidWASMConfigLoggingApi.fromJson(dynamic json) : enabled = json['enabled'], level = veilidConfigLogLevelFromJson(json['level']); } @@ -189,7 +193,7 @@ class VeilidWASMConfigLogging { }; } - VeilidWASMConfigLogging.fromJson(Map json) + VeilidWASMConfigLogging.fromJson(dynamic json) : performance = VeilidWASMConfigLoggingPerformance.fromJson(json['performance']), api = VeilidWASMConfigLoggingApi.fromJson(json['api']); @@ -208,7 +212,7 @@ class VeilidWASMConfig { }; } - VeilidWASMConfig.fromJson(Map json) + VeilidWASMConfig.fromJson(dynamic json) : logging = VeilidWASMConfigLogging.fromJson(json['logging']); } @@ -322,7 +326,7 @@ class VeilidConfigHTTPS { }; } - VeilidConfigHTTPS.fromJson(Map json) + VeilidConfigHTTPS.fromJson(dynamic json) : enabled = json['enabled'], listenAddress = json['listen_address'], path = json['path'], @@ -353,7 +357,7 @@ class VeilidConfigHTTP { }; } - VeilidConfigHTTP.fromJson(Map json) + VeilidConfigHTTP.fromJson(dynamic json) : enabled = json['enabled'], listenAddress = json['listen_address'], path = json['path'], @@ -378,7 +382,7 @@ class VeilidConfigApplication { }; } - VeilidConfigApplication.fromJson(Map json) + VeilidConfigApplication.fromJson(dynamic json) : https = VeilidConfigHTTPS.fromJson(json['https']), http = VeilidConfigHTTP.fromJson(json['http']); } @@ -406,7 +410,7 @@ class VeilidConfigUDP { }; } - VeilidConfigUDP.fromJson(Map json) + VeilidConfigUDP.fromJson(dynamic json) : enabled = json['enabled'], socketPoolSize = json['socket_pool_size'], listenAddress = json['listen_address'], @@ -439,7 +443,7 @@ class VeilidConfigTCP { }; } - VeilidConfigTCP.fromJson(Map json) + VeilidConfigTCP.fromJson(dynamic json) : connect = json['connect'], listen = json['listen'], maxConnections = json['max_connections'], @@ -476,7 +480,7 @@ class VeilidConfigWS { }; } - VeilidConfigWS.fromJson(Map json) + VeilidConfigWS.fromJson(dynamic json) : connect = json['connect'], listen = json['listen'], maxConnections = json['max_connections'], @@ -514,7 +518,7 @@ class VeilidConfigWSS { }; } - VeilidConfigWSS.fromJson(Map json) + VeilidConfigWSS.fromJson(dynamic json) : connect = json['connect'], listen = json['listen'], maxConnections = json['max_connections'], @@ -547,7 +551,7 @@ class VeilidConfigProtocol { }; } - VeilidConfigProtocol.fromJson(Map json) + VeilidConfigProtocol.fromJson(dynamic json) : udp = VeilidConfigUDP.fromJson(json['udp']), tcp = VeilidConfigTCP.fromJson(json['tcp']), ws = VeilidConfigWS.fromJson(json['ws']), @@ -575,7 +579,7 @@ class VeilidConfigTLS { }; } - VeilidConfigTLS.fromJson(Map json) + VeilidConfigTLS.fromJson(dynamic json) : certificatePath = json['certificate_path'], privateKeyPath = json['private_key_path'], connectionInitialTimeoutMs = json['connection_initial_timeout_ms']; @@ -631,7 +635,7 @@ class VeilidConfigDHT { }; } - VeilidConfigDHT.fromJson(Map json) + VeilidConfigDHT.fromJson(dynamic json) : resolveNodeTimeoutMs = json['resolve_node_timeout_ms'], resolveNodeCount = json['resolve_node_count'], resolveNodeFanout = json['resolve_node_fanout'], @@ -680,7 +684,7 @@ class VeilidConfigRPC { }; } - VeilidConfigRPC.fromJson(Map json) + VeilidConfigRPC.fromJson(dynamic json) : concurrency = json['concurrency'], queueSize = json['queue_size'], maxTimestampBehindMs = json['max_timestamp_behind_ms'], @@ -717,7 +721,7 @@ class VeilidConfigRoutingTable { }; } - VeilidConfigRoutingTable.fromJson(Map json) + VeilidConfigRoutingTable.fromJson(dynamic json) : limitOverAttached = json['limit_over_attached'], limitFullyAttached = json['limit_fully_attached'], limitAttachedStrong = json['limit_attached_strong'], @@ -745,7 +749,6 @@ class VeilidConfigNetwork { VeilidConfigRPC rpc; VeilidConfigDHT dht; bool upnp; - bool natpmp; bool detectAddressChanges; int restrictedNatRetries; VeilidConfigTLS tls; @@ -770,7 +773,6 @@ class VeilidConfigNetwork { required this.rpc, required this.dht, required this.upnp, - required this.natpmp, required this.detectAddressChanges, required this.restrictedNatRetries, required this.tls, @@ -797,7 +799,6 @@ class VeilidConfigNetwork { 'rpc': rpc.json, 'dht': dht.json, 'upnp': upnp, - 'natpmp': natpmp, 'detect_address_changes': detectAddressChanges, 'restricted_nat_retries': restrictedNatRetries, 'tls': tls.json, @@ -806,7 +807,7 @@ class VeilidConfigNetwork { }; } - VeilidConfigNetwork.fromJson(Map json) + VeilidConfigNetwork.fromJson(dynamic json) : connectionInitialTimeoutMs = json['connection_initial_timeout_ms'], connectionInactivityTimeoutMs = json['connection_inactivity_timeout_ms'], @@ -827,7 +828,6 @@ class VeilidConfigNetwork { rpc = VeilidConfigRPC.fromJson(json['rpc']), dht = VeilidConfigDHT.fromJson(json['dht']), upnp = json['upnp'], - natpmp = json['natpmp'], detectAddressChanges = json['detect_address_changes'], restrictedNatRetries = json['restricted_nat_retries'], tls = VeilidConfigTLS.fromJson(json['tls']), @@ -850,7 +850,7 @@ class VeilidConfigTableStore { return {'directory': directory, 'delete': delete}; } - VeilidConfigTableStore.fromJson(Map json) + VeilidConfigTableStore.fromJson(dynamic json) : directory = json['directory'], delete = json['delete']; } @@ -870,7 +870,7 @@ class VeilidConfigBlockStore { return {'directory': directory, 'delete': delete}; } - VeilidConfigBlockStore.fromJson(Map json) + VeilidConfigBlockStore.fromJson(dynamic json) : directory = json['directory'], delete = json['delete']; } @@ -899,7 +899,7 @@ class VeilidConfigProtectedStore { }; } - VeilidConfigProtectedStore.fromJson(Map json) + VeilidConfigProtectedStore.fromJson(dynamic json) : allowInsecureFallback = json['allow_insecure_fallback'], alwaysUseInsecureStorage = json['always_use_insecure_storage'], insecureFallbackDirectory = json['insecure_fallback_directory'], @@ -939,7 +939,7 @@ class VeilidConfigCapabilities { }; } - VeilidConfigCapabilities.fromJson(Map json) + VeilidConfigCapabilities.fromJson(dynamic json) : protocolUDP = json['protocol_udp'], protocolConnectTCP = json['protocol_connect_tcp'], protocolAcceptTCP = json['protocol_accept_tcp'], @@ -982,7 +982,7 @@ class VeilidConfig { }; } - VeilidConfig.fromJson(Map json) + VeilidConfig.fromJson(dynamic json) : programName = json['program_name'], namespace = json['namespace'], capabilities = VeilidConfigCapabilities.fromJson(json['capabilities']), @@ -1014,7 +1014,7 @@ class LatencyStats { }; } - LatencyStats.fromJson(Map json) + LatencyStats.fromJson(dynamic json) : fastest = BigInt.parse(json['fastest']), average = BigInt.parse(json['average']), slowest = BigInt.parse(json['slowest']); @@ -1044,7 +1044,7 @@ class TransferStats { }; } - TransferStats.fromJson(Map json) + TransferStats.fromJson(dynamic json) : total = BigInt.parse(json['total']), maximum = BigInt.parse(json['maximum']), average = BigInt.parse(json['average']), @@ -1069,7 +1069,7 @@ class TransferStatsDownUp { }; } - TransferStatsDownUp.fromJson(Map json) + TransferStatsDownUp.fromJson(dynamic json) : down = TransferStats.fromJson(json['down']), up = TransferStats.fromJson(json['up']); } @@ -1110,7 +1110,7 @@ class RPCStats { }; } - RPCStats.fromJson(Map json) + RPCStats.fromJson(dynamic json) : messagesSent = json['messages_sent'], messagesRcvd = json['messages_rcvd'], questionsInFlight = json['questions_in_flight'], @@ -1151,7 +1151,7 @@ class PeerStats { }; } - PeerStats.fromJson(Map json) + PeerStats.fromJson(dynamic json) : timeAdded = BigInt.parse(json['time_added']), rpcStats = RPCStats.fromJson(json['rpc_stats']), latency = json['latency'] != null @@ -1181,7 +1181,7 @@ class PeerTableData { }; } - PeerTableData.fromJson(Map json) + PeerTableData.fromJson(dynamic json) : nodeId = json['node_id'], peerAddress = PeerAddress.fromJson(json['peer_address']), peerStats = PeerStats.fromJson(json['peer_stats']); @@ -1225,7 +1225,7 @@ class PeerAddress { }; } - PeerAddress.fromJson(Map json) + PeerAddress.fromJson(dynamic json) : protocolType = protocolTypeFromJson(json['protocol_type']), socketAddress = json['socket_address']; } @@ -1234,7 +1234,7 @@ class PeerAddress { /// VeilidUpdate abstract class VeilidUpdate { - factory VeilidUpdate.fromJson(Map json) { + factory VeilidUpdate.fromJson(dynamic json) { switch (json["kind"]) { case "Log": { @@ -1266,6 +1266,10 @@ abstract class VeilidUpdate { { return VeilidUpdateConfig(state: VeilidStateConfig.fromJson(json)); } + case "Route": + { + return VeilidUpdateRoute(state: VeilidStateRoute.fromJson(json)); + } default: { throw VeilidAPIExceptionInternal( @@ -1313,7 +1317,7 @@ class VeilidAppMessage implements VeilidUpdate { return { 'kind': "AppMessage", 'sender': sender, - 'message': base64UrlEncode(message) + 'message': base64UrlNoPadEncode(message) }; } } @@ -1335,7 +1339,7 @@ class VeilidAppCall implements VeilidUpdate { return { 'kind': "AppMessage", 'sender': sender, - 'message': base64UrlEncode(message), + 'message': base64UrlNoPadEncode(message), 'id': id, }; } @@ -1380,20 +1384,40 @@ class VeilidUpdateConfig implements VeilidUpdate { } } +class VeilidUpdateRoute implements VeilidUpdate { + final VeilidStateRoute state; + // + VeilidUpdateRoute({required this.state}); + + @override + Map get json { + var jsonRep = state.json; + jsonRep['kind'] = "Route"; + return jsonRep; + } +} + ////////////////////////////////////// /// VeilidStateAttachment class VeilidStateAttachment { final AttachmentState state; + final bool publicInternetReady; + final bool localNetworkReady; - VeilidStateAttachment(this.state); + VeilidStateAttachment( + this.state, this.publicInternetReady, this.localNetworkReady); - VeilidStateAttachment.fromJson(Map json) - : state = attachmentStateFromJson(json['state']); + 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, }; } } @@ -1413,7 +1437,7 @@ class VeilidStateNetwork { required this.bpsUp, required this.peers}); - VeilidStateNetwork.fromJson(Map json) + VeilidStateNetwork.fromJson(dynamic json) : started = json['started'], bpsDown = BigInt.parse(json['bps_down']), bpsUp = BigInt.parse(json['bps_up']), @@ -1440,11 +1464,35 @@ class VeilidStateConfig { required this.config, }); - VeilidStateConfig.fromJson(Map json) - : config = jsonDecode(json['config']); + VeilidStateConfig.fromJson(dynamic json) : config = json['config']; Map get json { - return {'config': jsonEncode(config)}; + return {'config': config}; + } +} + +////////////////////////////////////// +/// VeilidStateRoute + +class VeilidStateRoute { + final List deadRoutes; + final List deadRemoteRoutes; + + VeilidStateRoute({ + required this.deadRoutes, + required this.deadRemoteRoutes, + }); + + VeilidStateRoute.fromJson(dynamic json) + : deadRoutes = List.from(json['dead_routes'].map((j) => j)), + deadRemoteRoutes = + List.from(json['dead_remote_routes'].map((j) => j)); + + Map get json { + return { + 'dead_routes': deadRoutes.map((p) => p).toList(), + 'dead_remote_routes': deadRemoteRoutes.map((p) => p).toList() + }; } } @@ -1456,7 +1504,7 @@ class VeilidState { final VeilidStateNetwork network; final VeilidStateConfig config; - VeilidState.fromJson(Map json) + VeilidState.fromJson(dynamic json) : attachment = VeilidStateAttachment.fromJson(json['attachment']), network = VeilidStateNetwork.fromJson(json['network']), config = VeilidStateConfig.fromJson(json['config']); @@ -1474,7 +1522,7 @@ class VeilidState { /// VeilidAPIException abstract class VeilidAPIException implements Exception { - factory VeilidAPIException.fromJson(Map json) { + factory VeilidAPIException.fromJson(dynamic json) { switch (json["kind"]) { case "NotInitialized": { @@ -1522,6 +1570,10 @@ abstract class VeilidAPIException implements Exception { return VeilidAPIExceptionMissingArgument( json["context"], json["argument"]); } + case "Generic": + { + return VeilidAPIExceptionGeneric(json["message"]); + } default: { throw VeilidAPIExceptionInternal( @@ -1529,6 +1581,8 @@ abstract class VeilidAPIException implements Exception { } } } + + String toDisplayError(); } class VeilidAPIExceptionNotInitialized implements VeilidAPIException { @@ -1536,6 +1590,11 @@ class VeilidAPIExceptionNotInitialized implements VeilidAPIException { String toString() { return "VeilidAPIException: NotInitialized"; } + + @override + String toDisplayError() { + return "Not initialized"; + } } class VeilidAPIExceptionAlreadyInitialized implements VeilidAPIException { @@ -1543,6 +1602,11 @@ class VeilidAPIExceptionAlreadyInitialized implements VeilidAPIException { String toString() { return "VeilidAPIException: AlreadyInitialized"; } + + @override + String toDisplayError() { + return "Already initialized"; + } } class VeilidAPIExceptionTimeout implements VeilidAPIException { @@ -1550,6 +1614,11 @@ class VeilidAPIExceptionTimeout implements VeilidAPIException { String toString() { return "VeilidAPIException: Timeout"; } + + @override + String toDisplayError() { + return "Timeout"; + } } class VeilidAPIExceptionShutdown implements VeilidAPIException { @@ -1557,6 +1626,11 @@ class VeilidAPIExceptionShutdown implements VeilidAPIException { String toString() { return "VeilidAPIException: Shutdown"; } + + @override + String toDisplayError() { + return "Currently shut down"; + } } class VeilidAPIExceptionNodeNotFound implements VeilidAPIException { @@ -1567,6 +1641,11 @@ class VeilidAPIExceptionNodeNotFound implements VeilidAPIException { return "VeilidAPIException: NodeNotFound (nodeId: $nodeId)"; } + @override + String toDisplayError() { + return "Node node found: $nodeId"; + } + // VeilidAPIExceptionNodeNotFound(this.nodeId); } @@ -1579,6 +1658,11 @@ class VeilidAPIExceptionNoDialInfo implements VeilidAPIException { return "VeilidAPIException: NoDialInfo (nodeId: $nodeId)"; } + @override + String toDisplayError() { + return "No dial info: $nodeId"; + } + // VeilidAPIExceptionNoDialInfo(this.nodeId); } @@ -1591,6 +1675,11 @@ class VeilidAPIExceptionInternal implements VeilidAPIException { return "VeilidAPIException: Internal ($message)"; } + @override + String toDisplayError() { + return "Internal error: $message"; + } + // VeilidAPIExceptionInternal(this.message); } @@ -1603,6 +1692,11 @@ class VeilidAPIExceptionUnimplemented implements VeilidAPIException { return "VeilidAPIException: Unimplemented ($message)"; } + @override + String toDisplayError() { + return "Unimplemented: $message"; + } + // VeilidAPIExceptionUnimplemented(this.message); } @@ -1616,6 +1710,11 @@ class VeilidAPIExceptionParseError implements VeilidAPIException { return "VeilidAPIException: ParseError ($message)\n value: $value"; } + @override + String toDisplayError() { + return "Parse error: $message"; + } + // VeilidAPIExceptionParseError(this.message, this.value); } @@ -1630,6 +1729,11 @@ class VeilidAPIExceptionInvalidArgument implements VeilidAPIException { return "VeilidAPIException: InvalidArgument ($context:$argument)\n value: $value"; } + @override + String toDisplayError() { + return "Invalid argument for $context: $argument"; + } + // VeilidAPIExceptionInvalidArgument(this.context, this.argument, this.value); } @@ -1643,10 +1747,32 @@ class VeilidAPIExceptionMissingArgument implements VeilidAPIException { 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 @@ -1658,6 +1784,126 @@ class VeilidVersion { VeilidVersion(this.major, this.minor, this.patch); } +////////////////////////////////////// +/// 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()); +} + +////////////////////////////////////// +/// KeyBlob +class KeyBlob { + final String key; + final Uint8List blob; + + KeyBlob(this.key, this.blob); + + KeyBlob.fromJson(dynamic json) + : key = json['key'], + blob = base64UrlNoPadDecode(json['blob']); + + Map get json { + return {'key': key, '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); +} + +///////////////////////////////////// +/// 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); + } +} + ////////////////////////////////////// /// Veilid singleton factory @@ -1671,8 +1917,26 @@ abstract class Veilid { Future attach(); Future detach(); Future shutdownVeilidCore(); - Future debug(String command); + + // Routing context + Future routingContext(); + + // Private route allocation + Future newPrivateRoute(); + Future newCustomPrivateRoute( + Stability stability, Sequencing sequencing); + Future importRemotePrivateRoute(Uint8List blob); + Future releasePrivateRoute(String key); + + // App calls Future appCallReply(String id, Uint8List message); + + // TableStore + Future openTableDB(String name, int columnCount); + Future deleteTableDB(String name); + + // Misc String veilidVersionString(); VeilidVersion veilidVersion(); + Future debug(String command); } diff --git a/veilid-flutter/lib/veilid_ffi.dart b/veilid-flutter/lib/veilid_ffi.dart index f4b87f41..4af42a26 100644 --- a/veilid-flutter/lib/veilid_ffi.dart +++ b/veilid-flutter/lib/veilid_ffi.dart @@ -8,6 +8,7 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart'; import 'veilid.dart'; +import 'base64url_no_pad.dart'; ////////////////////////////////////////////////////////// @@ -48,13 +49,105 @@ typedef _AttachDart = void Function(int); // fn detach(port: i64) typedef _DetachC = Void Function(Int64); typedef _DetachDart = void Function(int); -// fn debug(port: i64, log_level: FfiStr) -typedef _DebugC = Void Function(Int64, Pointer); -typedef _DebugDart = void Function(int, Pointer); + +// fn routing_context(port: i64) +typedef _RoutingContextC = Void Function(Int64); +typedef _RoutingContextDart = void Function(int); +// fn release_routing_context(id: u32) +typedef _ReleaseRoutingContextC = Int32 Function(Uint32); +typedef _ReleaseRoutingContextDart = int Function(int); +// fn routing_context_with_privacy(id: u32) -> u32 +typedef _RoutingContextWithPrivacyC = Uint32 Function(Uint32); +typedef _RoutingContextWithPrivacyDart = int Function(int); +// fn routing_context_with_custom_privacy(id: u32, stability: FfiStr) +typedef _RoutingContextWithCustomPrivacyC = Uint32 Function( + Uint32, Pointer); +typedef _RoutingContextWithCustomPrivacyDart = int Function(int, Pointer); +// fn routing_context_with_sequencing(id: u32, sequencing: FfiStr) +typedef _RoutingContextWithSequencingC = Uint32 Function(Uint32, Pointer); +typedef _RoutingContextWithSequencingDart = int Function(int, Pointer); +// fn routing_context_app_call(port: i64, id: u32, target: FfiStr, request: FfiStr) +typedef _RoutingContextAppCallC = Void Function( + Int64, Uint32, Pointer, Pointer); +typedef _RoutingContextAppCallDart = void Function( + int, int, Pointer, Pointer); +// fn routing_context_app_message(port: i64, id: u32, target: FfiStr, request: FfiStr) +typedef _RoutingContextAppMessageC = Void Function( + Int64, Uint32, Pointer, Pointer); +typedef _RoutingContextAppMessageDart = void Function( + int, int, Pointer, Pointer); + +// fn new_private_route(port: i64) +typedef _NewPrivateRouteC = Void Function(Int64); +typedef _NewPrivateRouteDart = void Function(int); +// fn new_custom_private_route(port: i64, stability: FfiStr, sequencing: FfiStr) +typedef _NewCustomPrivateRouteC = Void Function( + Int64, Pointer, Pointer); +typedef _NewCustomPrivateRouteDart = void Function( + int, Pointer, Pointer); +// fn import_remote_private_route(port: i64, blob: FfiStr) +typedef _ImportRemotePrivateRouteC = Void Function(Int64, Pointer); +typedef _ImportRemotePrivateRouteDart = void Function(int, Pointer); +// fn release_private_route(port:i64, key: FfiStr) +typedef _ReleasePrivateRouteC = Void Function(Int64, Pointer); +typedef _ReleasePrivateRouteDart = void Function(int, Pointer); + // fn app_call_reply(port: i64, id: FfiStr, message: FfiStr) typedef _AppCallReplyC = Void Function(Int64, Pointer, Pointer); typedef _AppCallReplyDart = void Function(int, Pointer, Pointer); +// fn open_table_db(port: i64, name: FfiStr, column_count: u32) +typedef _OpenTableDbC = Void Function(Int64, Pointer, Uint32); +typedef _OpenTableDbDart = void Function(int, Pointer, int); +// fn release_table_db(id: u32) -> i32 +typedef _ReleaseTableDbC = Int32 Function(Uint32); +typedef _ReleaseTableDbDart = int Function(int); +// fn delete_table_db(port: i64, name: FfiStr) +typedef _DeleteTableDbC = Void Function(Int64, Pointer); +typedef _DeleteTableDbDart = void Function(int, Pointer); +// fn table_db_get_column_count(id: u32) -> u32 +typedef _TableDbGetColumnCountC = Uint32 Function(Uint32); +typedef _TableDbGetColumnCountDart = int Function(int); +// fn table_db_get_keys(id: u32, col: u32) -> *mut c_char +typedef _TableDbGetKeysC = Pointer Function(Uint32, Uint32); +typedef _TableDbGetKeysDart = Pointer Function(int, int); +// fn table_db_store(port: i64, id: u32, col: u32, key: FfiStr, value: FfiStr) +typedef _TableDbStoreC = Void Function( + Int64, Uint32, Uint32, Pointer, Pointer); +typedef _TableDbStoreDart = void Function( + int, int, int, Pointer, Pointer); +// fn table_db_load(port: i64, id: u32, col: u32, key: FfiStr) +typedef _TableDbLoadC = Void Function(Int64, Uint32, Uint32, Pointer); +typedef _TableDbLoadDart = void Function(int, int, int, Pointer); +// fn table_db_delete(port: i64, id: u32, col: u32, key: FfiStr) +typedef _TableDbDeleteC = Void Function(Int64, Uint32, Uint32, Pointer); +typedef _TableDbDeleteDart = void Function(int, int, int, Pointer); +// fn table_db_transact(id: u32) -> u32 +typedef _TableDbTransactC = Uint32 Function(Uint32); +typedef _TableDbTransactDart = int Function(int); +// fn release_table_db_transaction(id: u32) -> i32 +typedef _ReleaseTableDbTransactionC = Int32 Function(Uint32); +typedef _ReleaseTableDbTransactionDart = int Function(int); +// fn table_db_transaction_commit(port: i64, id: u32) +typedef _TableDbTransactionCommitC = Void Function(Uint64, Uint32); +typedef _TableDbTransactionCommitDart = void Function(int, int); +// fn table_db_transaction_rollback(port: i64, id: u32) +typedef _TableDbTransactionRollbackC = Void Function(Uint64, Uint32); +typedef _TableDbTransactionRollbackDart = void Function(int, int); +// fn table_db_transaction_store(port: i64, id: u32, col: u32, key: FfiStr, value: FfiStr) +typedef _TableDbTransactionStoreC = Void Function( + Int64, Uint32, Uint32, Pointer, Pointer); +typedef _TableDbTransactionStoreDart = void Function( + int, int, int, Pointer, Pointer); +// fn table_db_transaction_delete(port: i64, id: u32, col: u32, key: FfiStr) +typedef _TableDbTransactionDeleteC = Void Function( + Int64, Uint32, Uint32, Pointer); +typedef _TableDbTransactionDeleteDart = void Function( + int, int, int, Pointer); + +// fn debug(port: i64, log_level: FfiStr) +typedef _DebugC = Void Function(Int64, Pointer); +typedef _DebugDart = void Function(int, Pointer); // fn shutdown_veilid_core(port: i64) typedef _ShutdownVeilidCoreC = Void Function(Int64); typedef _ShutdownVeilidCoreDart = void Function(int); @@ -125,7 +218,7 @@ Future processFuturePlain(Future future) { } Future processFutureJson( - T Function(Map) jsonConstructor, Future future) { + T Function(dynamic) jsonConstructor, Future future) { return future.then((value) { final list = value as List; switch (list[0] as int) { @@ -294,6 +387,232 @@ Stream processStreamJson( } } +class _Ctx { + final int id; + final VeilidFFI ffi; + _Ctx(this.id, this.ffi); +} + +// FFI implementation of VeilidRoutingContext +class VeilidRoutingContextFFI implements VeilidRoutingContext { + final _Ctx _ctx; + static final Finalizer<_Ctx> _finalizer = + Finalizer((ctx) => {ctx.ffi._releaseRoutingContext(ctx.id)}); + + VeilidRoutingContextFFI._(this._ctx) { + _finalizer.attach(this, _ctx, detach: this); + } + + @override + VeilidRoutingContextFFI withPrivacy() { + final newId = _ctx.ffi._routingContextWithPrivacy(_ctx.id); + return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi)); + } + + @override + VeilidRoutingContextFFI withCustomPrivacy(Stability stability) { + final newId = _ctx.ffi._routingContextWithCustomPrivacy( + _ctx.id, stability.json.toNativeUtf8()); + return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi)); + } + + @override + VeilidRoutingContextFFI withSequencing(Sequencing sequencing) { + final newId = _ctx.ffi + ._routingContextWithSequencing(_ctx.id, sequencing.json.toNativeUtf8()); + return VeilidRoutingContextFFI._(_Ctx(newId, _ctx.ffi)); + } + + @override + Future appCall(String target, Uint8List request) async { + var nativeEncodedTarget = target.toNativeUtf8(); + var nativeEncodedRequest = base64UrlNoPadEncode(request).toNativeUtf8(); + + final recvPort = ReceivePort("routing_context_app_call"); + final sendPort = recvPort.sendPort; + _ctx.ffi._routingContextAppCall(sendPort.nativePort, _ctx.id, + nativeEncodedTarget, nativeEncodedRequest); + final out = await processFuturePlain(recvPort.first); + return base64UrlNoPadDecode(out); + } + + @override + Future appMessage(String target, Uint8List message) async { + final nativeEncodedTarget = target.toNativeUtf8(); + final nativeEncodedMessage = base64UrlNoPadEncode(message).toNativeUtf8(); + + final recvPort = ReceivePort("routing_context_app_message"); + final sendPort = recvPort.sendPort; + _ctx.ffi._routingContextAppMessage(sendPort.nativePort, _ctx.id, + nativeEncodedTarget, nativeEncodedMessage); + return processFutureVoid(recvPort.first); + } +} + +class _TDBT { + final int id; + VeilidTableDBFFI tdbffi; + VeilidFFI ffi; + + _TDBT(this.id, this.tdbffi, this.ffi); +} + +// FFI implementation of VeilidTableDBTransaction +class VeilidTableDBTransactionFFI extends VeilidTableDBTransaction { + final _TDBT _tdbt; + static final Finalizer<_TDBT> _finalizer = + Finalizer((tdbt) => {tdbt.ffi._releaseTableDbTransaction(tdbt.id)}); + + VeilidTableDBTransactionFFI._(this._tdbt) { + _finalizer.attach(this, _tdbt, detach: this); + } + + @override + Future commit() { + final recvPort = ReceivePort("veilid_table_db_transaction_commit"); + final sendPort = recvPort.sendPort; + _tdbt.ffi._tableDbTransactionCommit( + sendPort.nativePort, + _tdbt.id, + ); + return processFutureVoid(recvPort.first); + } + + @override + Future rollback() { + final recvPort = ReceivePort("veilid_table_db_transaction_rollback"); + final sendPort = recvPort.sendPort; + _tdbt.ffi._tableDbTransactionRollback( + sendPort.nativePort, + _tdbt.id, + ); + return processFutureVoid(recvPort.first); + } + + @override + Future store(int col, Uint8List key, Uint8List value) { + final nativeEncodedKey = base64UrlNoPadEncode(key).toNativeUtf8(); + final nativeEncodedValue = base64UrlNoPadEncode(value).toNativeUtf8(); + + final recvPort = ReceivePort("veilid_table_db_transaction_store"); + final sendPort = recvPort.sendPort; + _tdbt.ffi._tableDbTransactionStore( + sendPort.nativePort, + _tdbt.id, + col, + nativeEncodedKey, + nativeEncodedValue, + ); + return processFutureVoid(recvPort.first); + } + + @override + Future delete(int col, Uint8List key) { + final nativeEncodedKey = base64UrlNoPadEncode(key).toNativeUtf8(); + + final recvPort = ReceivePort("veilid_table_db_transaction_delete"); + final sendPort = recvPort.sendPort; + _tdbt.ffi._tableDbTransactionDelete( + sendPort.nativePort, + _tdbt.id, + col, + nativeEncodedKey, + ); + return processFuturePlain(recvPort.first); + } +} + +class _TDB { + final int id; + VeilidFFI ffi; + _TDB(this.id, this.ffi); +} + +// FFI implementation of VeilidTableDB +class VeilidTableDBFFI extends VeilidTableDB { + final _TDB _tdb; + static final Finalizer<_TDB> _finalizer = + Finalizer((tdb) => {tdb.ffi._releaseTableDb(tdb.id)}); + + VeilidTableDBFFI._(this._tdb) { + _finalizer.attach(this, _tdb, detach: this); + } + + @override + int getColumnCount() { + return _tdb.ffi._tableDbGetColumnCount(_tdb.id); + } + + @override + List getKeys(int col) { + final s = _tdb.ffi._tableDbGetKeys(_tdb.id, col); + if (s.address == nullptr.address) { + throw VeilidAPIExceptionInternal("No db for id"); + } + String ja = s.toDartString(); + _tdb.ffi._freeString(s); + List jarr = jsonDecode(ja); + return jarr.map((e) => base64UrlNoPadDecode(e)).toList(); + } + + @override + VeilidTableDBTransaction transact() { + final id = _tdb.ffi._tableDbTransact(_tdb.id); + return VeilidTableDBTransactionFFI._(_TDBT(id, this, _tdb.ffi)); + } + + @override + Future store(int col, Uint8List key, Uint8List value) { + final nativeEncodedKey = base64UrlNoPadEncode(key).toNativeUtf8(); + final nativeEncodedValue = base64UrlNoPadEncode(value).toNativeUtf8(); + + final recvPort = ReceivePort("veilid_table_db_store"); + final sendPort = recvPort.sendPort; + _tdb.ffi._tableDbStore( + sendPort.nativePort, + _tdb.id, + col, + nativeEncodedKey, + nativeEncodedValue, + ); + return processFutureVoid(recvPort.first); + } + + @override + Future load(int col, Uint8List key) async { + final nativeEncodedKey = base64UrlNoPadEncode(key).toNativeUtf8(); + + final recvPort = ReceivePort("veilid_table_db_load"); + final sendPort = recvPort.sendPort; + _tdb.ffi._tableDbLoad( + sendPort.nativePort, + _tdb.id, + col, + nativeEncodedKey, + ); + String? out = await processFuturePlain(recvPort.first); + if (out == null) { + return null; + } + return base64UrlNoPadDecode(out); + } + + @override + Future delete(int col, Uint8List key) { + final nativeEncodedKey = base64UrlNoPadEncode(key).toNativeUtf8(); + + final recvPort = ReceivePort("veilid_table_db_delete"); + final sendPort = recvPort.sendPort; + _tdb.ffi._tableDbLoad( + sendPort.nativePort, + _tdb.id, + col, + nativeEncodedKey, + ); + return processFuturePlain(recvPort.first); + } +} + // FFI implementation of high level Veilid API class VeilidFFI implements Veilid { // veilid_core shared library @@ -308,8 +627,38 @@ class VeilidFFI implements Veilid { final _AttachDart _attach; final _DetachDart _detach; final _ShutdownVeilidCoreDart _shutdownVeilidCore; - final _DebugDart _debug; + + final _RoutingContextDart _routingContext; + final _ReleaseRoutingContextDart _releaseRoutingContext; + final _RoutingContextWithPrivacyDart _routingContextWithPrivacy; + final _RoutingContextWithCustomPrivacyDart _routingContextWithCustomPrivacy; + final _RoutingContextWithSequencingDart _routingContextWithSequencing; + final _RoutingContextAppCallDart _routingContextAppCall; + final _RoutingContextAppMessageDart _routingContextAppMessage; + + final _NewPrivateRouteDart _newPrivateRoute; + final _NewCustomPrivateRouteDart _newCustomPrivateRoute; + final _ImportRemotePrivateRouteDart _importRemotePrivateRoute; + final _ReleasePrivateRouteDart _releasePrivateRoute; + final _AppCallReplyDart _appCallReply; + + final _OpenTableDbDart _openTableDb; + final _ReleaseTableDbDart _releaseTableDb; + final _DeleteTableDbDart _deleteTableDb; + final _TableDbGetColumnCountDart _tableDbGetColumnCount; + final _TableDbGetKeysDart _tableDbGetKeys; + final _TableDbStoreDart _tableDbStore; + final _TableDbLoadDart _tableDbLoad; + final _TableDbDeleteDart _tableDbDelete; + final _TableDbTransactDart _tableDbTransact; + final _ReleaseTableDbTransactionDart _releaseTableDbTransaction; + final _TableDbTransactionCommitDart _tableDbTransactionCommit; + final _TableDbTransactionRollbackDart _tableDbTransactionRollback; + final _TableDbTransactionStoreDart _tableDbTransactionStore; + final _TableDbTransactionDeleteDart _tableDbTransactionDelete; + + final _DebugDart _debug; final _VeilidVersionStringDart _veilidVersionString; final _VeilidVersionDart _veilidVersion; @@ -333,9 +682,78 @@ class VeilidFFI implements Veilid { _shutdownVeilidCore = dylib.lookupFunction<_ShutdownVeilidCoreC, _ShutdownVeilidCoreDart>( 'shutdown_veilid_core'), - _debug = dylib.lookupFunction<_DebugC, _DebugDart>('debug'), + _routingContext = + dylib.lookupFunction<_RoutingContextC, _RoutingContextDart>( + 'routing_context'), + _releaseRoutingContext = dylib.lookupFunction<_ReleaseRoutingContextC, + _ReleaseRoutingContextDart>('release_routing_context'), + _routingContextWithPrivacy = dylib.lookupFunction< + _RoutingContextWithPrivacyC, + _RoutingContextWithPrivacyDart>('routing_context_with_privacy'), + _routingContextWithCustomPrivacy = dylib.lookupFunction< + _RoutingContextWithCustomPrivacyC, + _RoutingContextWithCustomPrivacyDart>( + 'routing_context_with_custom_privacy'), + _routingContextWithSequencing = dylib.lookupFunction< + _RoutingContextWithSequencingC, + _RoutingContextWithSequencingDart>( + 'routing_context_with_sequencing'), + _routingContextAppCall = dylib.lookupFunction<_RoutingContextAppCallC, + _RoutingContextAppCallDart>('routing_context_app_call'), + _routingContextAppMessage = dylib.lookupFunction< + _RoutingContextAppMessageC, + _RoutingContextAppMessageDart>('routing_context_app_message'), + _newPrivateRoute = + dylib.lookupFunction<_NewPrivateRouteC, _NewPrivateRouteDart>( + 'new_private_route'), + _newCustomPrivateRoute = dylib.lookupFunction<_NewCustomPrivateRouteC, + _NewCustomPrivateRouteDart>('new_custom_private_route'), + _importRemotePrivateRoute = dylib.lookupFunction< + _ImportRemotePrivateRouteC, + _ImportRemotePrivateRouteDart>('import_remote_private_route'), + _releasePrivateRoute = dylib.lookupFunction<_ReleasePrivateRouteC, + _ReleasePrivateRouteDart>('release_private_route'), _appCallReply = dylib.lookupFunction<_AppCallReplyC, _AppCallReplyDart>( 'app_call_reply'), + _openTableDb = dylib + .lookupFunction<_OpenTableDbC, _OpenTableDbDart>('open_table_db'), + _releaseTableDb = + dylib.lookupFunction<_ReleaseTableDbC, _ReleaseTableDbDart>( + 'release_table_db'), + _deleteTableDb = + dylib.lookupFunction<_DeleteTableDbC, _DeleteTableDbDart>( + 'delete_table_db'), + _tableDbGetColumnCount = dylib.lookupFunction<_TableDbGetColumnCountC, + _TableDbGetColumnCountDart>('table_db_get_column_count'), + _tableDbGetKeys = + dylib.lookupFunction<_TableDbGetKeysC, _TableDbGetKeysDart>( + 'table_db_get_keys'), + _tableDbStore = dylib.lookupFunction<_TableDbStoreC, _TableDbStoreDart>( + 'table_db_store'), + _tableDbLoad = dylib + .lookupFunction<_TableDbLoadC, _TableDbLoadDart>('table_db_load'), + _tableDbDelete = + dylib.lookupFunction<_TableDbDeleteC, _TableDbDeleteDart>( + 'table_db_delete'), + _tableDbTransact = + dylib.lookupFunction<_TableDbTransactC, _TableDbTransactDart>( + 'table_db_transact'), + _releaseTableDbTransaction = dylib.lookupFunction< + _ReleaseTableDbTransactionC, + _ReleaseTableDbTransactionDart>('release_table_db_transaction'), + _tableDbTransactionCommit = dylib.lookupFunction< + _TableDbTransactionCommitC, + _TableDbTransactionCommitDart>('table_db_transaction_commit'), + _tableDbTransactionRollback = dylib.lookupFunction< + _TableDbTransactionRollbackC, + _TableDbTransactionRollbackDart>('table_db_transaction_rollback'), + _tableDbTransactionStore = dylib.lookupFunction< + _TableDbTransactionStoreC, + _TableDbTransactionStoreDart>('table_db_transaction_store'), + _tableDbTransactionDelete = dylib.lookupFunction< + _TableDbTransactionDeleteC, + _TableDbTransactionDeleteDart>('table_db_transaction_delete'), + _debug = dylib.lookupFunction<_DebugC, _DebugDart>('debug'), _veilidVersionString = dylib.lookupFunction<_VeilidVersionStringC, _VeilidVersionStringDart>('veilid_version_string'), _veilidVersion = @@ -388,7 +806,7 @@ class VeilidFFI implements Veilid { } @override - Future getVeilidState() async { + Future getVeilidState() { final recvPort = ReceivePort("get_veilid_state"); final sendPort = recvPort.sendPort; _getVeilidState(sendPort.nativePort); @@ -396,7 +814,7 @@ class VeilidFFI implements Veilid { } @override - Future attach() async { + Future attach() { final recvPort = ReceivePort("attach"); final sendPort = recvPort.sendPort; _attach(sendPort.nativePort); @@ -404,7 +822,7 @@ class VeilidFFI implements Veilid { } @override - Future detach() async { + Future detach() { final recvPort = ReceivePort("detach"); final sendPort = recvPort.sendPort; _detach(sendPort.nativePort); @@ -412,13 +830,89 @@ class VeilidFFI implements Veilid { } @override - Future shutdownVeilidCore() async { + Future shutdownVeilidCore() { final recvPort = ReceivePort("shutdown_veilid_core"); final sendPort = recvPort.sendPort; _shutdownVeilidCore(sendPort.nativePort); return processFutureVoid(recvPort.first); } + @override + Future routingContext() async { + final recvPort = ReceivePort("routing_context"); + final sendPort = recvPort.sendPort; + _routingContext(sendPort.nativePort); + final id = await processFuturePlain(recvPort.first); + return VeilidRoutingContextFFI._(_Ctx(id, this)); + } + + @override + Future newPrivateRoute() { + final recvPort = ReceivePort("new_private_route"); + final sendPort = recvPort.sendPort; + _newPrivateRoute(sendPort.nativePort); + return processFutureJson(KeyBlob.fromJson, recvPort.first); + } + + @override + Future newCustomPrivateRoute( + Stability stability, Sequencing sequencing) async { + final recvPort = ReceivePort("new_custom_private_route"); + final sendPort = recvPort.sendPort; + _newCustomPrivateRoute(sendPort.nativePort, stability.json.toNativeUtf8(), + sequencing.json.toNativeUtf8()); + final keyblob = await processFutureJson(KeyBlob.fromJson, recvPort.first); + return keyblob; + } + + @override + Future importRemotePrivateRoute(Uint8List blob) { + final nativeEncodedBlob = base64UrlNoPadEncode(blob).toNativeUtf8(); + + final recvPort = ReceivePort("import_remote_private_route"); + final sendPort = recvPort.sendPort; + _importRemotePrivateRoute(sendPort.nativePort, nativeEncodedBlob); + return processFuturePlain(recvPort.first); + } + + @override + Future releasePrivateRoute(String key) { + final nativeEncodedKey = key.toNativeUtf8(); + + final recvPort = ReceivePort("release_private_route"); + final sendPort = recvPort.sendPort; + _releasePrivateRoute(sendPort.nativePort, nativeEncodedKey); + return processFutureVoid(recvPort.first); + } + + @override + Future appCallReply(String id, Uint8List message) { + final nativeId = id.toNativeUtf8(); + final nativeEncodedMessage = base64UrlNoPadEncode(message).toNativeUtf8(); + final recvPort = ReceivePort("app_call_reply"); + final sendPort = recvPort.sendPort; + _appCallReply(sendPort.nativePort, nativeId, nativeEncodedMessage); + return processFutureVoid(recvPort.first); + } + + @override + Future openTableDB(String name, int columnCount) async { + final recvPort = ReceivePort("open_table_db"); + final sendPort = recvPort.sendPort; + _openTableDb(sendPort.nativePort, name.toNativeUtf8(), columnCount); + final id = await processFuturePlain(recvPort.first); + return VeilidTableDBFFI._(_TDB(id, this)); + } + + @override + Future deleteTableDB(String name) async { + final recvPort = ReceivePort("delete_table_db"); + final sendPort = recvPort.sendPort; + _deleteTableDb(sendPort.nativePort, name.toNativeUtf8()); + final deleted = await processFuturePlain(recvPort.first); + return deleted; + } + @override Future debug(String command) async { var nativeCommand = command.toNativeUtf8(); @@ -428,16 +922,6 @@ class VeilidFFI implements Veilid { return processFuturePlain(recvPort.first); } - @override - Future appCallReply(String id, Uint8List message) async { - var nativeId = id.toNativeUtf8(); - var nativeEncodedMessage = base64UrlEncode(message).toNativeUtf8(); - final recvPort = ReceivePort("app_call_reply"); - final sendPort = recvPort.sendPort; - _appCallReply(sendPort.nativePort, nativeId, nativeEncodedMessage); - return processFutureVoid(recvPort.first); - } - @override String veilidVersionString() { final versionString = _veilidVersionString(); diff --git a/veilid-flutter/lib/veilid_js.dart b/veilid-flutter/lib/veilid_js.dart index 1d5e907c..fbfcecba 100644 --- a/veilid-flutter/lib/veilid_js.dart +++ b/veilid-flutter/lib/veilid_js.dart @@ -7,6 +7,8 @@ import 'dart:async'; import 'dart:convert'; import 'dart:typed_data'; +import 'base64url_no_pad.dart'; + ////////////////////////////////////////////////////////// Veilid getVeilid() => VeilidJS(); @@ -19,6 +21,185 @@ Future _wrapApiPromise(Object p) { VeilidAPIException.fromJson(jsonDecode(error as String)))); } +class _Ctx { + final int id; + final VeilidJS js; + _Ctx(this.id, this.js); +} + +// JS implementation of VeilidRoutingContext +class VeilidRoutingContextJS implements VeilidRoutingContext { + final _Ctx _ctx; + static final Finalizer<_Ctx> _finalizer = Finalizer((ctx) => { + js_util.callMethod(wasm, "release_routing_context", [ctx.id]) + }); + + VeilidRoutingContextJS._(this._ctx) { + _finalizer.attach(this, _ctx, detach: this); + } + + @override + VeilidRoutingContextJS withPrivacy() { + int newId = + js_util.callMethod(wasm, "routing_context_with_privacy", [_ctx.id]); + return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); + } + + @override + VeilidRoutingContextJS withCustomPrivacy(Stability stability) { + final newId = js_util.callMethod( + wasm, "routing_context_with_custom_privacy", [_ctx.id, stability.json]); + + return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); + } + + @override + VeilidRoutingContextJS withSequencing(Sequencing sequencing) { + final newId = js_util.callMethod( + wasm, "routing_context_with_sequencing", [_ctx.id, sequencing.json]); + return VeilidRoutingContextJS._(_Ctx(newId, _ctx.js)); + } + + @override + Future appCall(String target, Uint8List request) async { + var encodedRequest = base64UrlNoPadEncode(request); + + return base64UrlNoPadDecode(await _wrapApiPromise(js_util.callMethod( + wasm, "routing_context_app_call", [_ctx.id, encodedRequest]))); + } + + @override + Future appMessage(String target, Uint8List message) { + var encodedMessage = base64UrlNoPadEncode(message); + + return _wrapApiPromise(js_util.callMethod( + wasm, "routing_context_app_message", [_ctx.id, encodedMessage])); + } +} + +class _TDBT { + final int id; + VeilidTableDBJS tdbjs; + VeilidJS js; + + _TDBT(this.id, this.tdbjs, this.js); +} + +// JS implementation of VeilidTableDBTransaction +class VeilidTableDBTransactionJS extends VeilidTableDBTransaction { + final _TDBT _tdbt; + static final Finalizer<_TDBT> _finalizer = Finalizer((tdbt) => { + js_util.callMethod(wasm, "release_table_db_transaction", [tdbt.id]) + }); + + VeilidTableDBTransactionJS._(this._tdbt) { + _finalizer.attach(this, _tdbt, detach: this); + } + + @override + Future commit() { + return _wrapApiPromise( + js_util.callMethod(wasm, "table_db_transaction_commit", [_tdbt.id])); + } + + @override + Future rollback() { + return _wrapApiPromise( + js_util.callMethod(wasm, "table_db_transaction_rollback", [_tdbt.id])); + } + + @override + Future store(int col, Uint8List key, Uint8List value) { + final encodedKey = base64UrlNoPadEncode(key); + final encodedValue = base64UrlNoPadEncode(value); + + return _wrapApiPromise(js_util.callMethod( + wasm, + "table_db_transaction_store", + [_tdbt.id, col, encodedKey, encodedValue])); + } + + @override + Future delete(int col, Uint8List key) { + final encodedKey = base64UrlNoPadEncode(key); + + return _wrapApiPromise(js_util.callMethod( + wasm, "table_db_transaction_delete", [_tdbt.id, col, encodedKey])); + } +} + +class _TDB { + final int id; + VeilidJS js; + + _TDB(this.id, this.js); +} + +// JS implementation of VeilidTableDB +class VeilidTableDBJS extends VeilidTableDB { + final _TDB _tdb; + static final Finalizer<_TDB> _finalizer = Finalizer((tdb) => { + js_util.callMethod(wasm, "release_table_db", [tdb.id]) + }); + + VeilidTableDBJS._(this._tdb) { + _finalizer.attach(this, _tdb, detach: this); + } + + @override + int getColumnCount() { + return js_util.callMethod(wasm, "table_db_get_column_count", [_tdb.id]); + } + + @override + List getKeys(int col) { + String? s = js_util.callMethod(wasm, "table_db_get_keys", [_tdb.id, col]); + if (s == null) { + throw VeilidAPIExceptionInternal("No db for id"); + } + List jarr = jsonDecode(s); + return jarr.map((e) => base64UrlNoPadDecode(e)).toList(); + } + + @override + VeilidTableDBTransaction transact() { + final id = js_util.callMethod(wasm, "table_db_transact", [_tdb.id]); + + return VeilidTableDBTransactionJS._(_TDBT(id, this, _tdb.js)); + } + + @override + Future store(int col, Uint8List key, Uint8List value) { + final encodedKey = base64UrlNoPadEncode(key); + final encodedValue = base64UrlNoPadEncode(value); + + return _wrapApiPromise(js_util.callMethod( + wasm, "table_db_store", [_tdb.id, col, encodedKey, encodedValue])); + } + + @override + Future load(int col, Uint8List key) async { + final encodedKey = base64UrlNoPadEncode(key); + + String? out = await _wrapApiPromise( + js_util.callMethod(wasm, "table_db_load", [_tdb.id, col, encodedKey])); + if (out == null) { + return null; + } + return base64UrlNoPadDecode(out); + } + + @override + Future delete(int col, Uint8List key) { + final encodedKey = base64UrlNoPadEncode(key); + + return _wrapApiPromise(js_util + .callMethod(wasm, "table_db_delete", [_tdb.id, col, encodedKey])); + } +} + +// JS implementation of high level Veilid API + class VeilidJS implements Veilid { @override void initializeVeilidCore(Map platformConfigJson) { @@ -63,12 +244,12 @@ class VeilidJS implements Veilid { } @override - Future attach() async { + Future attach() { return _wrapApiPromise(js_util.callMethod(wasm, "attach", [])); } @override - Future detach() async { + Future detach() { return _wrapApiPromise(js_util.callMethod(wasm, "detach", [])); } @@ -79,17 +260,70 @@ class VeilidJS implements Veilid { } @override - Future debug(String command) { - return _wrapApiPromise(js_util.callMethod(wasm, "debug", [command])); + Future routingContext() async { + int id = + await _wrapApiPromise(js_util.callMethod(wasm, "routing_context", [])); + return VeilidRoutingContextJS._(_Ctx(id, this)); + } + + @override + Future newPrivateRoute() async { + Map blobJson = jsonDecode(await _wrapApiPromise( + js_util.callMethod(wasm, "new_private_route", []))); + return KeyBlob.fromJson(blobJson); + } + + @override + Future newCustomPrivateRoute( + Stability stability, Sequencing sequencing) async { + var stabilityString = + jsonEncode(stability, toEncodable: veilidApiToEncodable); + var sequencingString = + jsonEncode(sequencing, toEncodable: veilidApiToEncodable); + + Map blobJson = jsonDecode(await _wrapApiPromise(js_util + .callMethod( + wasm, "new_private_route", [stabilityString, sequencingString]))); + return KeyBlob.fromJson(blobJson); + } + + @override + Future importRemotePrivateRoute(Uint8List blob) { + var encodedBlob = base64UrlNoPadEncode(blob); + return _wrapApiPromise( + js_util.callMethod(wasm, "import_remote_private_route", [encodedBlob])); + } + + @override + Future releasePrivateRoute(String key) { + return _wrapApiPromise( + js_util.callMethod(wasm, "release_private_route", [key])); } @override Future appCallReply(String id, Uint8List message) { - var encodedMessage = base64UrlEncode(message); + var encodedMessage = base64UrlNoPadEncode(message); return _wrapApiPromise( js_util.callMethod(wasm, "app_call_reply", [id, encodedMessage])); } + @override + Future openTableDB(String name, int columnCount) async { + int id = await _wrapApiPromise( + js_util.callMethod(wasm, "open_table_db", [name, columnCount])); + return VeilidTableDBJS._(_TDB(id, this)); + } + + @override + Future deleteTableDB(String name) { + return _wrapApiPromise(js_util.callMethod(wasm, "delete_table_db", [name])); + } + + @override + Future debug(String command) async { + return await _wrapApiPromise(js_util.callMethod(wasm, "debug", [command])); + } + @override String veilidVersionString() { return js_util.callMethod(wasm, "veilid_version_string", []); @@ -97,7 +331,7 @@ class VeilidJS implements Veilid { @override VeilidVersion veilidVersion() { - var jsonVersion = + Map jsonVersion = jsonDecode(js_util.callMethod(wasm, "veilid_version", [])); return VeilidVersion( jsonVersion["major"], jsonVersion["minor"], jsonVersion["patch"]); diff --git a/veilid-flutter/macos/veilid.podspec b/veilid-flutter/macos/veilid.podspec index c4139cd2..3646e82e 100644 --- a/veilid-flutter/macos/veilid.podspec +++ b/veilid-flutter/macos/veilid.podspec @@ -16,19 +16,23 @@ Veilid Network Plugin s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.platform = :osx, '10.11' + s.platform = :osx, '10.12.2' + s.osx.deployment_target = '10.12.2' s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } s.swift_version = '5.0' require 'json' require 'pathname' - cargo_target_dir = File.join(File.dirname(JSON.parse(`cargo locate-project`)['root']), 'target') + + script_dir = File.realpath(File.expand_path(__dir__)) + workspace_dir = File.dirname(JSON.parse(`(cd #{script_dir}; cargo locate-project --workspace)`)['root']) + cargo_target_dir = File.join(workspace_dir, 'target') s.script_phase = { :name => 'Cargo Build', - :script => File.join(File.dirname(__dir__), 'rust', 'macos_build.sh'), + :script => File.join(workspace_dir, 'scripts', 'macos_build.sh') + ' veilid_flutter', :execution_position => :before_compile - #:output_files => [ File.join(cargo_target_dir, 'macos_lib', 'libveilid_flutter.dylib') ] + #:output_files => [ File.join(cargo_target_dir, 'lipo-darwin', 'libveilid_flutter.dylib') ] } end diff --git a/veilid-flutter/rust/Cargo.toml b/veilid-flutter/rust/Cargo.toml index 1967f5e1..933a37df 100644 --- a/veilid-flutter/rust/Cargo.toml +++ b/veilid-flutter/rust/Cargo.toml @@ -12,6 +12,7 @@ rt-async-std = [ "veilid-core/rt-async-std", "async-std", "opentelemetry/rt-asyn rt-tokio = [ "veilid-core/rt-tokio", "tokio", "tokio-stream", "tokio-util", "opentelemetry/rt-tokio"] [dependencies] +veilid-core = { path="../../veilid-core" } tracing = { version = "^0", features = ["log", "attributes"] } tracing-subscriber = "^0" parking_lot = "^0" @@ -25,7 +26,6 @@ data-encoding = { version = "^2" } # Dependencies for native builds only # Linux, Windows, Mac, iOS, Android [target.'cfg(not(target_arch = "wasm32"))'.dependencies] -veilid-core = { path="../../veilid-core" } tracing-opentelemetry = "^0" opentelemetry = { version = "^0" } opentelemetry-otlp = { version = "^0" } @@ -41,7 +41,6 @@ hostname = "^0" # Dependencies for WASM builds only [target.'cfg(target_arch = "wasm32")'.dependencies] -veilid-core = { path="../../veilid-core" } # Dependencies for Android builds only [target.'cfg(target_os = "android")'.dependencies] diff --git a/veilid-flutter/rust/ios_build.sh b/veilid-flutter/rust/ios_build.sh deleted file mode 100755 index 7c05d493..00000000 --- a/veilid-flutter/rust/ios_build.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/bash -set -e -echo Running veilid-flutter rust iOS build script - -# Setup varaiables -SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -FLUTTER_DIR=$(dirname `which flutter`) -HOMEBREW_DIR=$(dirname `which brew`) -CARGO_DIR=$(dirname `which cargo`) -CARGO_MANIFEST_PATH=$(python3 -c "import os; print(os.path.realpath(\"$SCRIPTDIR/Cargo.toml\"))") -TARGET_DIR=$(dirname `cargo locate-project --message-format plain`)/target - -# Configure outputs -OUTPUT_FILENAME=libveilid_flutter.a -OUTPUT_DIR=$TARGET_DIR/ios_lib - -# Get Rust configurations from xcode configurations -if [ "$CONFIGURATION" == "Debug" ]; then - EXTRA_CARGO_OPTIONS="$@" - RUST_CONFIGURATION="debug" -else - EXTRA_CARGO_OPTIONS="$@ --release" - RUST_CONFIGURATION="release" -fi - -# Build all the matching architectures for the xcode configurations -ARCHS=${ARCHS:=arm64} -echo ARCHS: $ARCHS -LIPO_LIST="" -for arch in $ARCHS -do - if [ "$arch" == "arm64" ]; then - echo arm64 - CARGO_TARGET=aarch64-apple-ios - #CARGO_TOOLCHAIN=+ios-arm64-1.57.0 - CARGO_TOOLCHAIN= - elif [ "$arch" == "x86_64" ]; then - echo x86_64 - CARGO_TARGET=x86_64-apple-ios - CARGO_TOOLCHAIN= - else - echo Unsupported ARCH: $arch - continue - fi - - # Cargo build - env -i PATH=/usr/bin:/bin:/usr/local/bin:$HOMEBREW_DIR:$FLUTTER_DIR:$CARGO_DIR HOME="$HOME" USER="$USER" cargo $CARGO_TOOLCHAIN build $EXTRA_CARGO_OPTIONS --target $CARGO_TARGET --manifest-path $CARGO_MANIFEST_PATH - - # Add output to lipo list - LIPO_LIST="$LIPO_LIST $TARGET_DIR/$CARGO_TARGET/$RUST_CONFIGURATION/$OUTPUT_FILENAME" -done - -# Lipo the architectures together -mkdir -p $OUTPUT_DIR -lipo -output "$OUTPUT_DIR/$OUTPUT_FILENAME" -create $LIPO_LIST diff --git a/veilid-flutter/rust/macos_build.sh b/veilid-flutter/rust/macos_build.sh deleted file mode 100755 index 29f180bc..00000000 --- a/veilid-flutter/rust/macos_build.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -set -e -echo Running veilid-flutter rust MacOS build script - -# Setup varaiables -SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" -FLUTTER_DIR=$(dirname `which flutter`) -HOMEBREW_DIR=$(dirname `which brew`) -CARGO_DIR=$(dirname `which cargo`) -CARGO_MANIFEST_PATH=$(python3 -c "import os; print(os.path.realpath(\"$SCRIPTDIR/Cargo.toml\"))") -TARGET_DIR=$(dirname `cargo locate-project --message-format plain`)/target - -# Configure outputs -OUTPUT_FILENAME=libveilid_flutter.dylib -OUTPUT_DIR=$TARGET_DIR/macos_lib - -# Get Rust configurations from xcode configurations -if [ "$CONFIGURATION" == "Debug" ]; then - EXTRA_CARGO_OPTIONS="$@" - RUST_CONFIGURATION="debug" -else - EXTRA_CARGO_OPTIONS="$@ --release" - RUST_CONFIGURATION="release" -fi - -# Build all the matching architectures for the xcode configurations -ARCHS=${ARCHS:=x86_64} -echo ARCHS: $ARCHS -LIPO_LIST="" -for arch in $ARCHS -do - if [ "$arch" == "arm64" ]; then - echo arm64 - CARGO_TARGET=aarch64-apple-darwin - CARGO_TOOLCHAIN= - elif [ "$arch" == "x86_64" ]; then - echo x86_64 - CARGO_TARGET=x86_64-apple-darwin - CARGO_TOOLCHAIN= - else - echo Unsupported ARCH: $arch - continue - fi - - # Cargo build - env -i PATH=/usr/bin:/bin:/usr/local/bin:$HOMEBREW_DIR:$FLUTTER_DIR:$CARGO_DIR HOME="$HOME" USER="$USER" cargo $CARGO_TOOLCHAIN build $EXTRA_CARGO_OPTIONS --target $CARGO_TARGET --manifest-path $CARGO_MANIFEST_PATH - - # Add output to lipo list - LIPO_LIST="$LIPO_LIST $TARGET_DIR/$CARGO_TARGET/$RUST_CONFIGURATION/$OUTPUT_FILENAME" -done - -# Lipo the architectures together -mkdir -p $OUTPUT_DIR -lipo -output "$OUTPUT_DIR/$OUTPUT_FILENAME" -create $LIPO_LIST diff --git a/veilid-flutter/rust/src/dart_ffi.rs b/veilid-flutter/rust/src/dart_ffi.rs index 00080298..3956f11b 100644 --- a/veilid-flutter/rust/src/dart_ffi.rs +++ b/veilid-flutter/rust/src/dart_ffi.rs @@ -2,6 +2,7 @@ use crate::dart_isolate_wrapper::*; use crate::tools::*; use allo_isolate::*; use cfg_if::*; +use data_encoding::BASE64URL_NOPAD; use ffi_support::*; use lazy_static::*; use opentelemetry::sdk::*; @@ -18,9 +19,16 @@ use tracing_subscriber::*; // Globals lazy_static! { + static ref CORE_INITIALIZED: Mutex = Mutex::new(false); static ref VEILID_API: AsyncMutex> = AsyncMutex::new(None); static ref FILTERS: Mutex> = Mutex::new(BTreeMap::new()); + static ref ROUTING_CONTEXTS: Mutex> = + Mutex::new(BTreeMap::new()); + static ref TABLE_DBS: Mutex> = + Mutex::new(BTreeMap::new()); + static ref TABLE_DB_TRANSACTIONS: Mutex> = + Mutex::new(BTreeMap::new()); } async fn get_veilid_api() -> Result { @@ -49,7 +57,7 @@ type APIResult = Result; const APIRESULT_VOID: APIResult<()> = APIResult::Ok(()); ///////////////////////////////////////// -// FFI-specific cofnig +// FFI-specific #[derive(Debug, Deserialize, Serialize)] pub struct VeilidFFIConfigLoggingTerminal { @@ -83,6 +91,13 @@ pub struct VeilidFFIConfig { pub logging: VeilidFFIConfigLogging, } +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidFFIKeyBlob { + pub key: veilid_core::DHTKey, + #[serde(with = "veilid_core::json_as_base64")] + pub blob: Vec, +} + ///////////////////////////////////////// // Initializer #[no_mangle] @@ -130,6 +145,17 @@ pub extern "C" fn initialize_veilid_flutter(dart_post_c_object_ptr: ffi::DartPos #[no_mangle] #[instrument] pub extern "C" fn initialize_veilid_core(platform_config: FfiStr) { + + // Only do this once, ever + // Until we have Dart native finalizers running on hot-restart, this will cause a crash if run more than once + { + let mut core_init = CORE_INITIALIZED.lock(); + if *core_init { + return; + } + *core_init = true; + } + let platform_config = platform_config.into_opt_string(); let platform_config: VeilidFFIConfig = veilid_core::deserialize_opt_json(platform_config) .expect("failed to deserialize plaform config json"); @@ -243,7 +269,7 @@ pub extern "C" fn change_log_level(layer: FfiStr, log_level: FfiStr) { pub extern "C" fn startup_veilid_core(port: i64, stream_port: i64, config: FfiStr) { let config = config.into_opt_string(); let stream = DartIsolateStream::new(stream_port); - DartIsolateWrapper::new(port).spawn_result_json(async move { + DartIsolateWrapper::new(port).spawn_result(async move { let config_json = match config { Some(v) => v, None => { @@ -291,7 +317,7 @@ pub extern "C" fn get_veilid_state(port: i64) { #[no_mangle] pub extern "C" fn attach(port: i64) { - DartIsolateWrapper::new(port).spawn_result_json(async move { + DartIsolateWrapper::new(port).spawn_result(async move { let veilid_api = get_veilid_api().await?; veilid_api.attach().await?; APIRESULT_VOID @@ -300,7 +326,7 @@ pub extern "C" fn attach(port: i64) { #[no_mangle] pub extern "C" fn detach(port: i64) { - DartIsolateWrapper::new(port).spawn_result_json(async move { + DartIsolateWrapper::new(port).spawn_result(async move { let veilid_api = get_veilid_api().await?; veilid_api.detach().await?; APIRESULT_VOID @@ -310,20 +336,215 @@ pub extern "C" fn detach(port: i64) { #[no_mangle] #[instrument] pub extern "C" fn shutdown_veilid_core(port: i64) { - DartIsolateWrapper::new(port).spawn_result_json(async move { + DartIsolateWrapper::new(port).spawn_result(async move { let veilid_api = take_veilid_api().await?; veilid_api.shutdown().await; APIRESULT_VOID }); } +fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 { + let mut next_id: u32 = 1; + let mut rc = ROUTING_CONTEXTS.lock(); + while rc.contains_key(&next_id) { + next_id += 1; + } + rc.insert(next_id, routing_context); + next_id +} + #[no_mangle] -pub extern "C" fn debug(port: i64, command: FfiStr) { - let command = command.into_opt_string().unwrap_or_default(); +pub extern "C" fn routing_context(port: i64) { DartIsolateWrapper::new(port).spawn_result(async move { let veilid_api = get_veilid_api().await?; - let out = veilid_api.debug(command).await?; - APIResult::Ok(out) + let routing_context = veilid_api.routing_context(); + let new_id = add_routing_context(routing_context); + APIResult::Ok(new_id) + }); +} + +#[no_mangle] +pub extern "C" fn release_routing_context(id: u32) -> i32 { + let mut rc = ROUTING_CONTEXTS.lock(); + if rc.remove(&id).is_none() { + return 0; + } + return 1; +} + +#[no_mangle] +pub extern "C" fn routing_context_with_privacy(id: u32) -> u32 { + let rc = ROUTING_CONTEXTS.lock(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let Ok(routing_context) = routing_context.clone().with_privacy() else { + return 0; + }; + let new_id = add_routing_context(routing_context); + new_id +} + +#[no_mangle] +pub extern "C" fn routing_context_with_custom_privacy(id: u32, stability: FfiStr) -> u32 { + let stability: veilid_core::Stability = + veilid_core::deserialize_opt_json(stability.into_opt_string()).unwrap(); + + let rc = ROUTING_CONTEXTS.lock(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let Ok(routing_context) = routing_context.clone().with_custom_privacy(stability) else { + return 0; + }; + let new_id = add_routing_context(routing_context); + new_id +} + +#[no_mangle] +pub extern "C" fn routing_context_with_sequencing(id: u32, sequencing: FfiStr) -> u32 { + let sequencing: veilid_core::Sequencing = + veilid_core::deserialize_opt_json(sequencing.into_opt_string()).unwrap(); + + let rc = ROUTING_CONTEXTS.lock(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let routing_context = routing_context.clone().with_sequencing(sequencing); + let new_id = add_routing_context(routing_context); + new_id +} + +#[no_mangle] +pub extern "C" fn routing_context_app_call(port: i64, id: u32, target: FfiStr, request: FfiStr) { + let target: veilid_core::DHTKey = + veilid_core::deserialize_opt_json(target.into_opt_string()).unwrap(); + let request: Vec = data_encoding::BASE64URL_NOPAD + .decode( + request.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let veilid_api = get_veilid_api().await?; + let routing_table = veilid_api.routing_table()?; + let rss = routing_table.route_spec_store(); + + 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)); + }; + routing_context.clone() + }; + + let target = if rss.get_remote_private_route(&target).is_some() { + veilid_core::Target::PrivateRoute(target) + } else { + veilid_core::Target::NodeId(veilid_core::NodeId::new(target)) + }; + + let answer = routing_context.app_call(target, request).await?; + let answer = data_encoding::BASE64URL_NOPAD.encode(&answer); + APIResult::Ok(answer) + }); +} + +#[no_mangle] +pub extern "C" fn routing_context_app_message(port: i64, id: u32, target: FfiStr, message: FfiStr) { + let target: veilid_core::DHTKey = + veilid_core::deserialize_opt_json(target.into_opt_string()).unwrap(); + let message: Vec = data_encoding::BASE64URL_NOPAD + .decode( + message.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let veilid_api = get_veilid_api().await?; + let routing_table = veilid_api.routing_table()?; + let rss = routing_table.route_spec_store(); + + 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)); + }; + routing_context.clone() + }; + + let target = if rss.get_remote_private_route(&target).is_some() { + veilid_core::Target::PrivateRoute(target) + } else { + veilid_core::Target::NodeId(veilid_core::NodeId::new(target)) + }; + + routing_context.app_message(target, message).await?; + APIRESULT_VOID + }); +} + +#[no_mangle] +pub extern "C" fn new_private_route(port: i64) { + DartIsolateWrapper::new(port).spawn_result_json(async move { + let veilid_api = get_veilid_api().await?; + + let (key, blob) = veilid_api.new_private_route().await?; + + let keyblob = VeilidFFIKeyBlob { key, blob }; + + APIResult::Ok(keyblob) + }); +} + +#[no_mangle] +pub extern "C" fn new_custom_private_route(port: i64, stability: FfiStr, sequencing: FfiStr) { + let stability: veilid_core::Stability = + veilid_core::deserialize_opt_json(stability.into_opt_string()).unwrap(); + let sequencing: veilid_core::Sequencing = + veilid_core::deserialize_opt_json(sequencing.into_opt_string()).unwrap(); + + DartIsolateWrapper::new(port).spawn_result_json(async move { + let veilid_api = get_veilid_api().await?; + + let (key, blob) = veilid_api + .new_custom_private_route(stability, sequencing) + .await?; + + let keyblob = VeilidFFIKeyBlob { key, blob }; + + APIResult::Ok(keyblob) + }); +} + +#[no_mangle] +pub extern "C" fn import_remote_private_route(port: i64, blob: FfiStr) { + let blob: Vec = data_encoding::BASE64URL_NOPAD + .decode( + veilid_core::deserialize_opt_json::(blob.into_opt_string()) + .unwrap() + .as_bytes(), + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let veilid_api = get_veilid_api().await?; + + let key = veilid_api.import_remote_private_route(blob)?; + + APIResult::Ok(key.encode()) + }); +} + +#[no_mangle] +pub extern "C" fn release_private_route(port: i64, key: FfiStr) { + let key: veilid_core::DHTKey = + veilid_core::deserialize_opt_json(key.into_opt_string()).unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let veilid_api = get_veilid_api().await?; + veilid_api.release_private_route(&key)?; + APIRESULT_VOID }); } @@ -347,6 +568,276 @@ pub extern "C" fn app_call_reply(port: i64, id: FfiStr, message: FfiStr) { }); } +fn add_table_db(table_db: veilid_core::TableDB) -> u32 { + let mut next_id: u32 = 1; + let mut rc = TABLE_DBS.lock(); + while rc.contains_key(&next_id) { + next_id += 1; + } + rc.insert(next_id, table_db); + next_id +} + +#[no_mangle] +pub extern "C" fn open_table_db(port: i64, name: FfiStr, column_count: u32) { + let name = name.into_opt_string().unwrap_or_default(); + DartIsolateWrapper::new(port).spawn_result(async move { + let veilid_api = get_veilid_api().await?; + let tstore = veilid_api.table_store()?; + let table_db = tstore.open(&name, column_count).await.map_err(veilid_core::VeilidAPIError::generic)?; + let new_id = add_table_db(table_db); + APIResult::Ok(new_id) + }); +} + +#[no_mangle] +pub extern "C" fn release_table_db(id: u32) -> i32 { + let mut rc = TABLE_DBS.lock(); + if rc.remove(&id).is_none() { + return 0; + } + return 1; +} + +#[no_mangle] +pub extern "C" fn delete_table_db(port: i64, name: FfiStr) { + let name = name.into_opt_string().unwrap_or_default(); + DartIsolateWrapper::new(port).spawn_result(async move { + let veilid_api = get_veilid_api().await?; + let tstore = veilid_api.table_store()?; + let deleted = tstore.delete(&name).await.map_err(veilid_core::VeilidAPIError::generic)?; + APIResult::Ok(deleted) + }); +} + +#[no_mangle] +pub extern "C" fn table_db_get_column_count(id: u32) -> u32 { + let table_dbs = TABLE_DBS.lock(); + let Some(table_db) = table_dbs.get(&id) else { + return 0; + }; + let Ok(cc) = table_db.clone().get_column_count() else { + return 0; + }; + return cc; +} + +#[no_mangle] +pub extern "C" fn table_db_get_keys(id: u32, col: u32) -> *mut c_char { + let table_dbs = TABLE_DBS.lock(); + let Some(table_db) = table_dbs.get(&id) else { + return std::ptr::null_mut(); + }; + let Ok(keys) = table_db.clone().get_keys(col) else { + return std::ptr::null_mut(); + }; + let keys: Vec = keys.into_iter().map(|k| BASE64URL_NOPAD.encode(&k)).collect(); + let out = veilid_core::serialize_json(keys); + out.into_ffi_value() +} + +fn add_table_db_transaction(tdbt: veilid_core::TableDBTransaction) -> u32 { + let mut next_id: u32 = 1; + let mut tdbts = TABLE_DB_TRANSACTIONS.lock(); + while tdbts.contains_key(&next_id) { + next_id += 1; + } + tdbts.insert(next_id, tdbt); + next_id +} + +#[no_mangle] +pub extern "C" fn table_db_transact(id: u32) -> u32 { + let table_dbs = TABLE_DBS.lock(); + let Some(table_db) = table_dbs.get(&id) else { + return 0; + }; + let tdbt = table_db.clone().transact(); + let tdbtid = add_table_db_transaction(tdbt); + return tdbtid; +} + +#[no_mangle] +pub extern "C" fn release_table_db_transaction(id: u32) -> i32 { + let mut tdbts = TABLE_DB_TRANSACTIONS.lock(); + if tdbts.remove(&id).is_none() { + return 0; + } + return 1; +} + + +#[no_mangle] +pub extern "C" fn table_db_transaction_commit(port: i64, id: u32) { + DartIsolateWrapper::new(port).spawn_result(async move { + let tdbt = { + let tdbts = TABLE_DB_TRANSACTIONS.lock(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_commit", "id", id)); + }; + tdbt.clone() + }; + + tdbt.commit().await.map_err(veilid_core::VeilidAPIError::generic)?; + APIRESULT_VOID + }); +} +#[no_mangle] +pub extern "C" fn table_db_transaction_rollback(port: i64, id: u32) { + DartIsolateWrapper::new(port).spawn_result(async move { + let tdbt = { + let tdbts = TABLE_DB_TRANSACTIONS.lock(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_rollback", "id", id)); + }; + tdbt.clone() + }; + + tdbt.rollback(); + APIRESULT_VOID + }); +} + +#[no_mangle] +pub extern "C" fn table_db_transaction_store(port: i64, id: u32, col: u32, key: FfiStr, value: FfiStr) { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode( + key.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + let value: Vec = data_encoding::BASE64URL_NOPAD + .decode( + value.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let tdbt = { + let tdbts = TABLE_DB_TRANSACTIONS.lock(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_store", "id", id)); + }; + tdbt.clone() + }; + + tdbt.store(col, &key, &value); + APIRESULT_VOID + }); +} + + +#[no_mangle] +pub extern "C" fn table_db_transaction_delete(port: i64, id: u32, col: u32, key: FfiStr) { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode( + key.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let tdbt = { + let tdbts = TABLE_DB_TRANSACTIONS.lock(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_delete", "id", id)); + }; + tdbt.clone() + }; + + let out = tdbt.delete(col, &key); + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn table_db_store(port: i64, id: u32, col: u32, key: FfiStr, value: FfiStr) { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode( + key.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + let value: Vec = data_encoding::BASE64URL_NOPAD + .decode( + value.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let table_db = { + let table_dbs = TABLE_DBS.lock(); + let Some(table_db) = table_dbs.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", id)); + }; + table_db.clone() + }; + + table_db.store(col, &key, &value).await.map_err(veilid_core::VeilidAPIError::generic)?; + APIRESULT_VOID + }); +} + +#[no_mangle] +pub extern "C" fn table_db_load(port: i64, id: u32, col: u32, key: FfiStr) { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode(key.into_opt_string() + .unwrap() + .as_bytes() + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let table_db = { + let table_dbs = TABLE_DBS.lock(); + let Some(table_db) = table_dbs.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_load", "id", id)); + }; + table_db.clone() + }; + + let out = table_db.load(col, &key).map_err(veilid_core::VeilidAPIError::generic)?; + let out = out.map(|x| data_encoding::BASE64URL_NOPAD.encode(&x)); + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn table_db_delete(port: i64, id: u32, col: u32, key: FfiStr) { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode( + key.into_opt_string() + .unwrap() + .as_bytes(), + ) + .unwrap(); + DartIsolateWrapper::new(port).spawn_result(async move { + let table_db = { + let table_dbs = TABLE_DBS.lock(); + let Some(table_db) = table_dbs.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_delete", "id", id)); + }; + table_db.clone() + }; + + let out = table_db.delete(col, &key).await.map_err(veilid_core::VeilidAPIError::generic)?; + APIResult::Ok(out) + }); +} + +#[no_mangle] +pub extern "C" fn debug(port: i64, command: FfiStr) { + let command = command.into_opt_string().unwrap_or_default(); + DartIsolateWrapper::new(port).spawn_result(async move { + let veilid_api = get_veilid_api().await?; + let out = veilid_api.debug(command).await?; + APIResult::Ok(out) + }); +} + #[no_mangle] pub extern "C" fn veilid_version_string() -> *mut c_char { veilid_core::veilid_version_string().into_ffi_value() diff --git a/veilid-flutter/rust/src/lib.rs b/veilid-flutter/rust/src/lib.rs index 30a72cc6..2121c5ce 100644 --- a/veilid-flutter/rust/src/lib.rs +++ b/veilid-flutter/rust/src/lib.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "256"] + mod dart_ffi; mod dart_isolate_wrapper; mod tools; @@ -13,5 +15,5 @@ pub extern "system" fn Java_com_veilid_veilid_VeilidPlugin_init_1android( _class: JClass, ctx: JObject, ) { - veilid_core::veilid_core_setup_android_no_log(env, ctx); + veilid_core::veilid_core_setup_android(env, ctx); } diff --git a/veilid-server/src/client_api.rs b/veilid-server/src/client_api.rs index 86d3a645..fac14b9a 100644 --- a/veilid-server/src/client_api.rs +++ b/veilid-server/src/client_api.rs @@ -261,7 +261,7 @@ impl veilid_server::Server for VeilidServerImpl { ) -> Promise<(), ::capnp::Error> { trace!("VeilidServerImpl::app_call_reply"); - let id = pry!(params.get()).get_id(); + let id = OperationId::new(pry!(params.get()).get_id()); let message = pry!(pry!(params.get()).get_message()).to_owned(); let veilid_api = self.veilid_api.clone(); diff --git a/veilid-server/src/main.rs b/veilid-server/src/main.rs index 3856731d..7dd76d54 100644 --- a/veilid-server/src/main.rs +++ b/veilid-server/src/main.rs @@ -1,6 +1,7 @@ #![forbid(unsafe_code)] #![deny(clippy::all)] #![deny(unused_must_use)] +#![recursion_limit = "256"] mod client_api; mod cmdline; diff --git a/veilid-server/src/server.rs b/veilid-server/src/server.rs index ca936a30..45385203 100644 --- a/veilid-server/src/server.rs +++ b/veilid-server/src/server.rs @@ -11,7 +11,7 @@ use parking_lot::Mutex; use std::sync::Arc; use std::time::{Duration, Instant}; use tracing::*; -use veilid_core::xx::SingleShotEventual; +use veilid_core::tools::SingleShotEventual; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum ServerMode { diff --git a/veilid-server/src/settings.rs b/veilid-server/src/settings.rs index 203076cd..9a6e6259 100644 --- a/veilid-server/src/settings.rs +++ b/veilid-server/src/settings.rs @@ -1,16 +1,13 @@ #![allow(clippy::bool_assert_comparison)] use directories::*; -use parking_lot::*; use serde_derive::*; use std::ffi::OsStr; use std::net::SocketAddr; use std::path::{Path, PathBuf}; -use std::str::FromStr; -use std::sync::Arc; use url::Url; -use veilid_core::xx::*; +use veilid_core::tools::*; use veilid_core::*; pub fn load_default_config() -> EyreResult { @@ -100,7 +97,6 @@ core: min_peer_refresh_time_ms: 2000 validate_dial_info_receipt_time_ms: 2000 upnp: true - natpmp: false detect_address_changes: true restricted_nat_retries: 0 tls: @@ -607,7 +603,6 @@ pub struct Network { pub rpc: Rpc, pub dht: Dht, pub upnp: bool, - pub natpmp: bool, pub detect_address_changes: bool, pub restricted_nat_retries: u32, pub tls: Tls, @@ -1005,7 +1000,6 @@ impl Settings { value ); set_config_value!(inner.core.network.upnp, value); - set_config_value!(inner.core.network.natpmp, value); set_config_value!(inner.core.network.detect_address_changes, value); set_config_value!(inner.core.network.restricted_nat_retries, value); set_config_value!(inner.core.network.tls.certificate_path, value); @@ -1206,7 +1200,6 @@ impl Settings { inner.core.network.dht.validate_dial_info_receipt_time_ms, )), "network.upnp" => Ok(Box::new(inner.core.network.upnp)), - "network.natpmp" => Ok(Box::new(inner.core.network.natpmp)), "network.detect_address_changes" => { Ok(Box::new(inner.core.network.detect_address_changes)) } @@ -1530,7 +1523,6 @@ mod tests { ); // assert_eq!(s.core.network.upnp, true); - assert_eq!(s.core.network.natpmp, false); assert_eq!(s.core.network.detect_address_changes, true); assert_eq!(s.core.network.restricted_nat_retries, 0u32); // diff --git a/veilid-tools/Cargo.toml b/veilid-tools/Cargo.toml new file mode 100644 index 00000000..e7f976b6 --- /dev/null +++ b/veilid-tools/Cargo.toml @@ -0,0 +1,101 @@ +[package] +name = "veilid-tools" +version = "0.1.0" +authors = ["John Smith "] +edition = "2021" +license = "LGPL-2.0-or-later OR MPL-2.0 OR (MIT AND BSD-3-Clause)" + +[lib] +# staticlib for iOS tests, cydlib for android tests, rlib for everything else +crate-type = [ "cdylib", "staticlib", "rlib" ] + +[features] +default = [] +rt-async-std = [ "async-std", "async_executors/async_std", ] +rt-tokio = [ "tokio", "tokio-util", "async_executors/tokio_tp", "async_executors/tokio_io", "async_executors/tokio_timer", ] + +veilid_tools_android_tests = [ "dep:paranoid-android" ] +veilid_tools_ios_tests = [ "dep:oslog", "dep:tracing-oslog" ] +tracing = [ "dep:tracing", "dep:tracing-subscriber" ] + +[dependencies] +tracing = { version = "^0", features = ["log", "attributes"], optional = true } +tracing-subscriber = { version = "^0", optional = true } +log = { version = "^0" } +eyre = "^0" +static_assertions = "^1" +cfg-if = "^1" +thiserror = "^1" +futures-util = { version = "^0", default_features = false, features = ["alloc"] } +parking_lot = "^0" +once_cell = "^1" +owo-colors = "^3" +stop-token = { version = "^0", default-features = false } +rand = "^0.7" +rust-fsm = "^0" +backtrace = "^0" + +# Dependencies for native builds only +# Linux, Windows, Mac, iOS, Android +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +async-std = { version = "^1", features = ["unstable"], optional = true} +tokio = { version = "^1", features = ["full"], optional = true} +tokio-util = { version = "^0", features = ["compat"], optional = true} +maplit = "^1" +futures-util = { version = "^0", default-features = false, features = ["async-await", "sink", "std", "io"] } + +libc = "^0" +nix = "^0" + +# Dependencies for WASM builds only +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "^0" +js-sys = "^0" +wasm-bindgen-futures = "^0" +async_executors = { version = "^0", default-features = false, features = [ "bindgen", "timer" ]} +async-lock = "^2" +send_wrapper = { version = "^0.6", features = ["futures"] } + +# Dependencies for Android +[target.'cfg(target_os = "android")'.dependencies] +jni = "^0" +jni-sys = "^0" +ndk = { version = "^0.7" } +ndk-glue = { version = "^0.7", features = ["logger"] } +lazy_static = "^1.4.0" +paranoid-android = { version = "^0", optional = true } +android-logd-logger = "0.2.1" + +# Dependencies for Windows +# [target.'cfg(target_os = "windows")'.dependencies] +# windows = { version = "^0", features = [ "Win32_NetworkManagement_Dns", "Win32_Foundation", "alloc" ]} +# windows-permissions = "^0" + +# Dependencies for iOS +[target.'cfg(target_os = "ios")'.dependencies] +oslog = { version = "^0", optional = true } +tracing-oslog = { version = "^0", optional = true } + +### DEV DEPENDENCIES + +[dev-dependencies] +serial_test = "^0" +simplelog = { version = "^0.12", features = [ "test" ] } + +[target.'cfg(target_arch = "wasm32")'.dev-dependencies] +console_error_panic_hook = "^0" +wasm-bindgen-test = "^0" +wee_alloc = "^0" +wasm-logger = "^0" +tracing-wasm = { version = "^0" } +parking_lot = { version = "^0", features = ["wasm-bindgen"]} + +### BUILD OPTIONS + +[package.metadata.wasm-pack.profile.release] +wasm-opt = ["-O", "--enable-mutable-globals"] + +[package.metadata.ios] +build_targets = ["aarch64-apple-ios", "aarch64-apple-ios-sim", "x86_64-apple-ios"] +deployment_target = "12.0" +build_id_prefix = "com.veilid.veilidtools" diff --git a/veilid-tools/run_tests.sh b/veilid-tools/run_tests.sh new file mode 100755 index 00000000..de450f39 --- /dev/null +++ b/veilid-tools/run_tests.sh @@ -0,0 +1,67 @@ +#!/bin/bash +SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" + +pushd $SCRIPTDIR 2>/dev/null +if [[ "$1" == "wasm" ]]; then + WASM_BINDGEN_TEST_TIMEOUT=120 wasm-pack test --firefox --headless +elif [[ "$1" == "ios" ]]; then + SYMROOT=/tmp/testout + APPNAME=veilidtools-tests + BUNDLENAME=com.veilid.veilidtools-tests + ID="$2" + if [[ "$ID" == "" ]]; then + echo "No emulator ID specified" + exit 1 + fi + + # Build for simulator + xcrun xcodebuild -project src/tests/ios/$APPNAME/$APPNAME.xcodeproj/ -scheme $APPNAME -destination "generic/platform=iOS Simulator" SYMROOT=$SYMROOT + + # Run in temporary simulator + xcrun simctl install $ID $SYMROOT/Debug-iphonesimulator/$APPNAME.app + xcrun simctl spawn $ID log stream --level debug --predicate "subsystem == \"$BUNDLENAME\"" & + xcrun simctl launch --console $ID $BUNDLENAME + sleep 1 # Ensure the last log lines print + kill -INT %1 + + # Clean up build output + rm -rf /tmp/testout + +elif [[ "$1" == "android" ]]; then + ID="$2" + if [[ "$ID" == "" ]]; then + echo "No emulator ID specified" + exit 1 + fi + APPNAME=veilid_tools_android_tests + APPID=com.veilid.veilid_tools_android_tests + ACTIVITYNAME=MainActivity + pushd src/tests/android/$APPNAME >/dev/null + # Build apk + ./gradlew assembleDebug + # Wait for boot + adb -s $ID wait-for-device + # Install app + adb -s $ID install -r ./app/build/outputs/apk/debug/app-debug.apk + # Start activity + adb -s $ID shell am start-activity -W $APPID/.$ACTIVITYNAME + # Get the pid of the program + APP_PID=`adb -s $ID shell pidof -s $APPID` + # Print the logcat + adb -s $ID shell logcat --pid=$APP_PID veilid-tools:V *:S & + # Wait for the pid to be done + while [ "$(adb -s $ID shell pidof -s $APPID)" != "" ]; do + sleep 1 + done + # Terminate logcat + kill %1 + # Finished + popd >/dev/null + +else + cargo test --features=rt-tokio,tracing -- --nocapture + cargo test --features=rt-async-std,tracing -- --nocapture + cargo test --features=rt-tokio -- --nocapture + cargo test --features=rt-async-std -- --nocapture +fi +popd 2>/dev/null \ No newline at end of file diff --git a/veilid-tools/run_windows_tests.bat b/veilid-tools/run_windows_tests.bat new file mode 100644 index 00000000..66998e0d --- /dev/null +++ b/veilid-tools/run_windows_tests.bat @@ -0,0 +1,5 @@ +@echo off +cargo test --features=rt-tokio,tracing -- --nocapture +cargo test --features=rt-async-std,tracing -- --nocapture +cargo test --features=rt-tokio -- --nocapture +cargo test --features=rt-async-std -- --nocapture diff --git a/veilid-core/src/xx/async_peek_stream.rs b/veilid-tools/src/async_peek_stream.rs similarity index 99% rename from veilid-core/src/xx/async_peek_stream.rs rename to veilid-tools/src/async_peek_stream.rs index dd64ea26..26b15962 100644 --- a/veilid-core/src/xx/async_peek_stream.rs +++ b/veilid-tools/src/async_peek_stream.rs @@ -1,4 +1,5 @@ use super::*; + use std::io; use task::{Context, Poll}; diff --git a/veilid-core/src/xx/async_tag_lock.rs b/veilid-tools/src/async_tag_lock.rs similarity index 99% rename from veilid-core/src/xx/async_tag_lock.rs rename to veilid-tools/src/async_tag_lock.rs index 5f0623c1..7dcaec02 100644 --- a/veilid-core/src/xx/async_tag_lock.rs +++ b/veilid-tools/src/async_tag_lock.rs @@ -1,4 +1,5 @@ use super::*; + use core::fmt::Debug; use core::hash::Hash; diff --git a/veilid-core/src/xx/bump_port.rs b/veilid-tools/src/bump_port.rs similarity index 99% rename from veilid-core/src/xx/bump_port.rs rename to veilid-tools/src/bump_port.rs index 487b2d20..ebdd2628 100644 --- a/veilid-core/src/xx/bump_port.rs +++ b/veilid-tools/src/bump_port.rs @@ -1,4 +1,5 @@ use super::*; + cfg_if! { if #[cfg(target_arch = "wasm32")] { diff --git a/veilid-core/src/callback_state_machine.rs b/veilid-tools/src/callback_state_machine.rs similarity index 88% rename from veilid-core/src/callback_state_machine.rs rename to veilid-tools/src/callback_state_machine.rs index 64ab36c1..238ac43f 100644 --- a/veilid-core/src/callback_state_machine.rs +++ b/veilid-tools/src/callback_state_machine.rs @@ -1,4 +1,5 @@ -use crate::xx::*; +use super::*; +pub use rust_fsm; pub use rust_fsm::*; pub type StateChangeCallback = Arc< @@ -53,14 +54,14 @@ where self.inner.lock().callback = Some(callback); } - // pub fn clear_state_change_callback(&self) { - // self.inner.lock().callback = None; - // } + pub fn clear_state_change_callback(&self) { + self.inner.lock().callback = None; + } - // pub fn state_eventual_instance(&self) -> (T::State, EventualValueCloneFuture) { - // let inner = self.inner.lock(); - // (inner.state, inner.eventual.instance()) - // } + pub fn state_eventual_instance(&self) -> (T::State, EventualValueCloneFuture) { + let inner = self.inner.lock(); + (inner.state, inner.eventual.instance()) + } pub async fn consume(&self, input: &T::Input) -> Result, ()> { let current_state = self.inner.lock().state; diff --git a/veilid-core/src/xx/clone_stream.rs b/veilid-tools/src/clone_stream.rs similarity index 99% rename from veilid-core/src/xx/clone_stream.rs rename to veilid-tools/src/clone_stream.rs index 3790966c..18508071 100644 --- a/veilid-core/src/xx/clone_stream.rs +++ b/veilid-tools/src/clone_stream.rs @@ -1,4 +1,5 @@ -use crate::xx::*; +use super::*; + use core::pin::Pin; use core::task::{Context, Poll}; use futures_util::AsyncRead as Read; diff --git a/veilid-core/src/xx/eventual.rs b/veilid-tools/src/eventual.rs similarity index 99% rename from veilid-core/src/xx/eventual.rs rename to veilid-tools/src/eventual.rs index 7883ad70..9ad0f6c0 100644 --- a/veilid-core/src/xx/eventual.rs +++ b/veilid-tools/src/eventual.rs @@ -1,4 +1,5 @@ use super::*; + use eventual_base::*; pub struct Eventual { diff --git a/veilid-core/src/xx/eventual_base.rs b/veilid-tools/src/eventual_base.rs similarity index 100% rename from veilid-core/src/xx/eventual_base.rs rename to veilid-tools/src/eventual_base.rs diff --git a/veilid-core/src/xx/eventual_value.rs b/veilid-tools/src/eventual_value.rs similarity index 99% rename from veilid-core/src/xx/eventual_value.rs rename to veilid-tools/src/eventual_value.rs index 2bdf2a43..16650f31 100644 --- a/veilid-core/src/xx/eventual_value.rs +++ b/veilid-tools/src/eventual_value.rs @@ -1,4 +1,5 @@ use super::*; + use eventual_base::*; pub struct EventualValue { diff --git a/veilid-core/src/xx/eventual_value_clone.rs b/veilid-tools/src/eventual_value_clone.rs similarity index 99% rename from veilid-core/src/xx/eventual_value_clone.rs rename to veilid-tools/src/eventual_value_clone.rs index b18c375c..fdaa9cf8 100644 --- a/veilid-core/src/xx/eventual_value_clone.rs +++ b/veilid-tools/src/eventual_value_clone.rs @@ -1,4 +1,5 @@ use super::*; + use eventual_base::*; pub struct EventualValueClone { diff --git a/veilid-tools/src/interval.rs b/veilid-tools/src/interval.rs new file mode 100644 index 00000000..1d9a0bee --- /dev/null +++ b/veilid-tools/src/interval.rs @@ -0,0 +1,49 @@ +use super::*; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + + pub fn interval(freq_ms: u32, callback: F) -> SendPinBoxFuture<()> + where + F: Fn() -> FUT + Send + Sync + 'static, + FUT: Future + Send, + { + let e = Eventual::new(); + + let ie = e.clone(); + let jh = spawn(Box::pin(async move { + while timeout(freq_ms, ie.instance_clone(())).await.is_err() { + callback().await; + } + })); + + Box::pin(async move { + e.resolve().await; + jh.await; + }) + } + + } else { + + pub fn interval(freq_ms: u32, callback: F) -> SendPinBoxFuture<()> + where + F: Fn() -> FUT + Send + Sync + 'static, + FUT: Future + Send, + { + let e = Eventual::new(); + + let ie = e.clone(); + let jh = spawn(async move { + while timeout(freq_ms, ie.instance_clone(())).await.is_err() { + callback().await; + } + }); + + Box::pin(async move { + e.resolve().await; + jh.await; + }) + } + + } +} diff --git a/veilid-core/src/xx/ip_addr_port.rs b/veilid-tools/src/ip_addr_port.rs similarity index 99% rename from veilid-core/src/xx/ip_addr_port.rs rename to veilid-tools/src/ip_addr_port.rs index e87d1a2e..118c588a 100644 --- a/veilid-core/src/xx/ip_addr_port.rs +++ b/veilid-tools/src/ip_addr_port.rs @@ -1,4 +1,5 @@ use super::*; + use core::fmt; #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] pub struct IpAddrPort { diff --git a/veilid-core/src/xx/ip_extra.rs b/veilid-tools/src/ip_extra.rs similarity index 98% rename from veilid-core/src/xx/ip_extra.rs rename to veilid-tools/src/ip_extra.rs index 8899c719..edbce74b 100644 --- a/veilid-core/src/xx/ip_extra.rs +++ b/veilid-tools/src/ip_extra.rs @@ -1,9 +1,9 @@ // // This file really shouldn't be necessary, but 'ip' isn't a stable feature -// and things may not agree between the no_std_net crate and the stuff in std. // -use crate::xx::*; +use super::*; + use core::hash::*; #[derive(Copy, PartialEq, Eq, Clone, Hash, Debug)] diff --git a/veilid-core/src/xx/mod.rs b/veilid-tools/src/lib.rs similarity index 81% rename from veilid-core/src/xx/mod.rs rename to veilid-tools/src/lib.rs index a4c8e008..ed8e90c8 100644 --- a/veilid-core/src/xx/mod.rs +++ b/veilid-tools/src/lib.rs @@ -1,11 +1,13 @@ // mod bump_port; mod async_peek_stream; mod async_tag_lock; +mod callback_state_machine; mod clone_stream; mod eventual; mod eventual_base; mod eventual_value; mod eventual_value_clone; +mod interval; mod ip_addr_port; mod ip_extra; mod log_thru; @@ -13,11 +15,18 @@ mod must_join_handle; mod must_join_single_future; mod mutable_future; mod network_result; +mod random; mod single_shot_eventual; +mod sleep; +mod spawn; mod split_url; mod tick_task; +mod timeout; mod timeout_or; +mod timestamp; mod tools; +#[cfg(target_arch = "wasm32")] +mod wasm; pub use cfg_if::*; #[allow(unused_imports)] @@ -33,8 +42,13 @@ pub use split_url::*; pub use static_assertions::*; pub use stop_token::*; pub use thiserror::Error as ThisError; -pub use tracing::*; - +cfg_if! { + if #[cfg(feature = "tracing")] { + pub use tracing::*; + } else { + pub use log::*; + } +} pub type PinBox = Pin>; pub type PinBoxFuture = PinBox + 'static>; pub type PinBoxFutureLifetime<'a, T> = PinBox + 'a>; @@ -55,10 +69,14 @@ pub use std::convert::{TryFrom, TryInto}; pub use std::fmt; pub use std::future::Future; pub use std::mem; +pub use std::net::{ + IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, +}; pub use std::ops::{Fn, FnMut, FnOnce}; pub use std::pin::Pin; pub use std::rc::Rc; -pub use std::string::String; +pub use std::str::FromStr; +pub use std::string::{String, ToString}; pub use std::sync::atomic::{AtomicBool, Ordering}; pub use std::sync::{Arc, Weak}; pub use std::task; @@ -67,16 +85,11 @@ pub use std::vec::Vec; cfg_if! { if #[cfg(target_arch = "wasm32")] { - pub use wasm_bindgen::prelude::*; - pub use async_lock::Mutex as AsyncMutex; pub use async_lock::MutexGuard as AsyncMutexGuard; pub use async_lock::MutexGuardArc as AsyncMutexGuardArc; pub use async_executors::JoinHandle as LowLevelJoinHandle; - - pub use no_std_net::{ SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, IpAddr, Ipv4Addr, Ipv6Addr }; } else { - cfg_if! { if #[cfg(feature="rt-async-std")] { pub use async_std::sync::Mutex as AsyncMutex; @@ -92,25 +105,43 @@ cfg_if! { #[compile_error("must use an executor")] } } - pub use std::net::{ SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs, IpAddr, Ipv4Addr, Ipv6Addr }; } } // pub use bump_port::*; pub use async_peek_stream::*; pub use async_tag_lock::*; +pub use callback_state_machine::*; pub use clone_stream::*; pub use eventual::*; pub use eventual_base::{EventualCommon, EventualResolvedFuture}; pub use eventual_value::*; pub use eventual_value_clone::*; +pub use interval::*; pub use ip_addr_port::*; pub use ip_extra::*; pub use must_join_handle::*; pub use must_join_single_future::*; pub use mutable_future::*; pub use network_result::*; +pub use random::*; pub use single_shot_eventual::*; +pub use sleep::*; +pub use spawn::*; pub use tick_task::*; +pub use timeout::*; pub use timeout_or::*; +pub use timestamp::*; pub use tools::*; +#[cfg(target_arch = "wasm32")] +pub use wasm::*; + +// Tests must be public for wasm-pack tests +pub mod tests; + +// For iOS tests + +#[no_mangle] +pub extern "C" fn main_rs() { + // start game code here +} diff --git a/veilid-core/src/xx/log_thru.rs b/veilid-tools/src/log_thru.rs similarity index 99% rename from veilid-core/src/xx/log_thru.rs rename to veilid-tools/src/log_thru.rs index feedfcbf..587a8393 100644 --- a/veilid-core/src/xx/log_thru.rs +++ b/veilid-tools/src/log_thru.rs @@ -2,7 +2,7 @@ // Pass errors through and log them simultaneously via map_err() // Also contains common log facilities (net, rpc, rtab, pstore, crypto, etc ) -pub use alloc::string::{String, ToString}; +use super::*; pub fn map_to_string(arg: X) -> String { arg.to_string() diff --git a/veilid-core/src/xx/must_join_handle.rs b/veilid-tools/src/must_join_handle.rs similarity index 96% rename from veilid-core/src/xx/must_join_handle.rs rename to veilid-tools/src/must_join_handle.rs index 0f90de3a..ff7231f1 100644 --- a/veilid-core/src/xx/must_join_handle.rs +++ b/veilid-tools/src/must_join_handle.rs @@ -1,6 +1,5 @@ use super::*; -use core::future::Future; -use core::pin::Pin; + use core::task::{Context, Poll}; #[derive(Debug)] @@ -78,7 +77,7 @@ impl Future for MustJoinHandle { } } } - }else if #[cfg(target_arch = "wasm32")] { + } else if #[cfg(target_arch = "wasm32")] { Poll::Ready(t) } else { compile_error!("needs executor implementation") diff --git a/veilid-core/src/xx/must_join_single_future.rs b/veilid-tools/src/must_join_single_future.rs similarity index 97% rename from veilid-core/src/xx/must_join_single_future.rs rename to veilid-tools/src/must_join_single_future.rs index ffebeaae..42663ad4 100644 --- a/veilid-core/src/xx/must_join_single_future.rs +++ b/veilid-tools/src/must_join_single_future.rs @@ -1,5 +1,5 @@ use super::*; -use crate::*; + use core::task::Poll; use futures_util::poll; @@ -157,7 +157,7 @@ where // Run if we should do that if run { - self.unlock(Some(intf::spawn_local(future))); + self.unlock(Some(spawn_local(future))); } // Return the prior result if we have one @@ -197,7 +197,7 @@ where } // Run if we should do that if run { - self.unlock(Some(intf::spawn(future))); + self.unlock(Some(spawn(future))); } // Return the prior result if we have one Ok((out, run)) diff --git a/veilid-core/src/xx/mutable_future.rs b/veilid-tools/src/mutable_future.rs similarity index 100% rename from veilid-core/src/xx/mutable_future.rs rename to veilid-tools/src/mutable_future.rs diff --git a/veilid-core/src/xx/network_result.rs b/veilid-tools/src/network_result.rs similarity index 91% rename from veilid-core/src/xx/network_result.rs rename to veilid-tools/src/network_result.rs index dd50d33c..0ddb3973 100644 --- a/veilid-core/src/xx/network_result.rs +++ b/veilid-tools/src/network_result.rs @@ -1,4 +1,5 @@ use super::*; + use core::fmt::{Debug, Display}; use core::result::Result; use std::error::Error; @@ -67,6 +68,7 @@ impl NetworkResultResultExt for NetworkResult> { fn into_result_network_result(self) -> Result, E> { match self { NetworkResult::Timeout => Ok(NetworkResult::::Timeout), + NetworkResult::ServiceUnavailable => Ok(NetworkResult::::ServiceUnavailable), NetworkResult::NoConnection(e) => Ok(NetworkResult::::NoConnection(e)), NetworkResult::AlreadyExists(e) => Ok(NetworkResult::::AlreadyExists(e)), NetworkResult::InvalidMessage(s) => Ok(NetworkResult::::InvalidMessage(s)), @@ -159,6 +161,7 @@ impl FoldedNetworkResultExt for io::Result> { #[must_use] pub enum NetworkResult { Timeout, + ServiceUnavailable, NoConnection(io::Error), AlreadyExists(io::Error), InvalidMessage(String), @@ -169,6 +172,9 @@ impl NetworkResult { pub fn timeout() -> Self { Self::Timeout } + pub fn service_unavailable() -> Self { + Self::ServiceUnavailable + } pub fn no_connection(e: io::Error) -> Self { Self::NoConnection(e) } @@ -200,6 +206,7 @@ impl NetworkResult { pub fn map X>(self, f: F) -> NetworkResult { match self { Self::Timeout => NetworkResult::::Timeout, + Self::ServiceUnavailable => NetworkResult::::ServiceUnavailable, Self::NoConnection(e) => NetworkResult::::NoConnection(e), Self::AlreadyExists(e) => NetworkResult::::AlreadyExists(e), Self::InvalidMessage(s) => NetworkResult::::InvalidMessage(s), @@ -209,6 +216,10 @@ impl NetworkResult { pub fn into_result(self) -> Result { match self { Self::Timeout => Err(io::Error::new(io::ErrorKind::TimedOut, "Timed out")), + Self::ServiceUnavailable => Err(io::Error::new( + io::ErrorKind::NotFound, + "Service unavailable", + )), Self::NoConnection(e) => Err(e), Self::AlreadyExists(e) => Err(e), Self::InvalidMessage(s) => Err(io::Error::new( @@ -229,21 +240,11 @@ impl From> for Option { } } -// impl Clone for NetworkResult { -// fn clone(&self) -> Self { -// match self { -// Self::Timeout => Self::Timeout, -// Self::NoConnection(e) => Self::NoConnection(e.clone()), -// Self::InvalidMessage(s) => Self::InvalidMessage(s.clone()), -// Self::Value(t) => Self::Value(t.clone()), -// } -// } -// } - impl Debug for NetworkResult { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Timeout => write!(f, "Timeout"), + Self::ServiceUnavailable => write!(f, "ServiceUnavailable"), Self::NoConnection(e) => f.debug_tuple("NoConnection").field(e).finish(), Self::AlreadyExists(e) => f.debug_tuple("AlreadyExists").field(e).finish(), Self::InvalidMessage(s) => f.debug_tuple("InvalidMessage").field(s).finish(), @@ -256,6 +257,7 @@ impl Display for NetworkResult { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::Timeout => write!(f, "Timeout"), + Self::ServiceUnavailable => write!(f, "ServiceUnavailable"), Self::NoConnection(e) => write!(f, "NoConnection({})", e.kind()), Self::AlreadyExists(e) => write!(f, "AlreadyExists({})", e.kind()), Self::InvalidMessage(s) => write!(f, "InvalidMessage({})", s), @@ -274,6 +276,7 @@ macro_rules! network_result_try { ($r: expr) => { match $r { NetworkResult::Timeout => return Ok(NetworkResult::Timeout), + NetworkResult::ServiceUnavailable => return Ok(NetworkResult::ServiceUnavailable), NetworkResult::NoConnection(e) => return Ok(NetworkResult::NoConnection(e)), NetworkResult::AlreadyExists(e) => return Ok(NetworkResult::AlreadyExists(e)), NetworkResult::InvalidMessage(s) => return Ok(NetworkResult::InvalidMessage(s)), @@ -286,6 +289,10 @@ macro_rules! network_result_try { $f; return Ok(NetworkResult::Timeout); } + NetworkResult::ServiceUnavailable => { + $f; + return Ok(NetworkResult::ServiceUnavailable); + } NetworkResult::NoConnection(e) => { $f; return Ok(NetworkResult::NoConnection(e)); @@ -327,7 +334,7 @@ macro_rules! log_network_result { #[macro_export] macro_rules! network_result_value_or_log { - ($level: ident $r: expr => $f:tt) => { + ($r: expr => $f:tt) => { match $r { NetworkResult::Timeout => { log_network_result!( @@ -339,6 +346,16 @@ macro_rules! network_result_value_or_log { ); $f } + NetworkResult::ServiceUnavailable => { + log_network_result!( + "{} at {}@{}:{}", + "ServiceUnavailable".cyan(), + file!(), + line!(), + column!() + ); + $f + } NetworkResult::NoConnection(e) => { log_network_result!( "{}({}) at {}@{}:{}", diff --git a/veilid-tools/src/random.rs b/veilid-tools/src/random.rs new file mode 100644 index 00000000..9c3d9fa7 --- /dev/null +++ b/veilid-tools/src/random.rs @@ -0,0 +1,83 @@ +use super::*; +use rand::prelude::*; + +#[derive(Clone, Copy, Debug, Default)] +pub struct VeilidRng; + +impl CryptoRng for VeilidRng {} + +impl RngCore for VeilidRng { + fn next_u32(&mut self) -> u32 { + get_random_u32() + } + + fn next_u64(&mut self) -> u64 { + get_random_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + if let Err(e) = self.try_fill_bytes(dest) { + panic!("Error: {}", e); + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + random_bytes(dest).map_err(rand::Error::new) + } +} + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use js_sys::Math; + + pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> { + let len = dest.len(); + let u32len = len / 4; + let remlen = len % 4; + + for n in 0..u32len { + let r = (Math::random() * (u32::max_value() as f64)) as u32; + + dest[n * 4 + 0] = (r & 0xFF) as u8; + dest[n * 4 + 1] = ((r >> 8) & 0xFF) as u8; + dest[n * 4 + 2] = ((r >> 16) & 0xFF) as u8; + dest[n * 4 + 3] = ((r >> 24) & 0xFF) as u8; + } + if remlen > 0 { + let r = (Math::random() * (u32::max_value() as f64)) as u32; + for n in 0..remlen { + dest[u32len * 4 + n] = ((r >> (n * 8)) & 0xFF) as u8; + } + } + + Ok(()) + } + + pub fn get_random_u32() -> u32 { + (Math::random() * (u32::max_value() as f64)) as u32 + } + + pub fn get_random_u64() -> u64 { + let v1: u32 = get_random_u32(); + let v2: u32 = get_random_u32(); + ((v1 as u64) << 32) | ((v2 as u32) as u64) + } + + } else { + + pub fn random_bytes(dest: &mut [u8]) -> EyreResult<()> { + let mut rng = rand::thread_rng(); + rng.try_fill_bytes(dest).wrap_err("failed to fill bytes") + } + + pub fn get_random_u32() -> u32 { + let mut rng = rand::thread_rng(); + rng.next_u32() + } + + pub fn get_random_u64() -> u64 { + let mut rng = rand::thread_rng(); + rng.next_u64() + } + } +} diff --git a/veilid-core/src/xx/single_shot_eventual.rs b/veilid-tools/src/single_shot_eventual.rs similarity index 100% rename from veilid-core/src/xx/single_shot_eventual.rs rename to veilid-tools/src/single_shot_eventual.rs diff --git a/veilid-tools/src/sleep.rs b/veilid-tools/src/sleep.rs new file mode 100644 index 00000000..31c458c5 --- /dev/null +++ b/veilid-tools/src/sleep.rs @@ -0,0 +1,34 @@ +use super::*; +use std::time::Duration; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use async_executors::{Bindgen, Timer}; + + pub async fn sleep(millis: u32) { + Bindgen.sleep(Duration::from_millis(millis.into())).await + } + + } else { + + pub async fn sleep(millis: u32) { + if millis == 0 { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + async_std::task::yield_now().await; + } else if #[cfg(feature="rt-tokio")] { + tokio::task::yield_now().await; + } + } + } else { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + async_std::task::sleep(Duration::from_millis(u64::from(millis))).await; + } else if #[cfg(feature="rt-tokio")] { + tokio::time::sleep(Duration::from_millis(u64::from(millis))).await; + } + } + } + } + } +} diff --git a/veilid-tools/src/spawn.rs b/veilid-tools/src/spawn.rs new file mode 100644 index 00000000..8d85b01c --- /dev/null +++ b/veilid-tools/src/spawn.rs @@ -0,0 +1,120 @@ +use super::*; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use async_executors::{Bindgen, LocalSpawnHandleExt, SpawnHandleExt}; + + pub fn spawn(future: impl Future + Send + 'static) -> MustJoinHandle + where + Out: Send + 'static, + { + MustJoinHandle::new( + Bindgen + .spawn_handle(future) + .expect("wasm-bindgen-futures spawn_handle_local should never error out"), + ) + } + + pub fn spawn_local(future: impl Future + 'static) -> MustJoinHandle + where + Out: 'static, + { + MustJoinHandle::new( + Bindgen + .spawn_handle_local(future) + .expect("wasm-bindgen-futures spawn_handle_local should never error out"), + ) + } + + pub fn spawn_detached(future: impl Future + Send + 'static) + where + Out: Send + 'static, + { + Bindgen + .spawn_handle_local(future) + .expect("wasm-bindgen-futures spawn_handle_local should never error out") + .detach() + } + pub fn spawn_detached_local(future: impl Future + 'static) + where + Out: 'static, + { + Bindgen + .spawn_handle_local(future) + .expect("wasm-bindgen-futures spawn_handle_local should never error out") + .detach() + } + + } else { + + pub fn spawn(future: impl Future + Send + 'static) -> MustJoinHandle + where + Out: Send + 'static, + { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + MustJoinHandle::new(async_std::task::spawn(future)) + } else if #[cfg(feature="rt-tokio")] { + MustJoinHandle::new(tokio::task::spawn(future)) + } + } + } + + pub fn spawn_local(future: impl Future + 'static) -> MustJoinHandle + where + Out: 'static, + { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + MustJoinHandle::new(async_std::task::spawn_local(future)) + } else if #[cfg(feature="rt-tokio")] { + MustJoinHandle::new(tokio::task::spawn_local(future)) + } + } + } + + pub fn spawn_detached(future: impl Future + Send + 'static) + where + Out: Send + 'static, + { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + drop(async_std::task::spawn(future)); + } else if #[cfg(feature="rt-tokio")] { + drop(tokio::task::spawn(future)); + } + } + } + + pub fn spawn_detached_local(future: impl Future + 'static) + where + Out: 'static, + { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + drop(async_std::task::spawn_local(future)); + } else if #[cfg(feature="rt-tokio")] { + drop(tokio::task::spawn_local(future)); + } + } + } + + #[allow(unused_variables)] + pub async fn blocking_wrapper(blocking_task: F, err_result: R) -> R + where + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, + { + // run blocking stuff in blocking thread + cfg_if! { + if #[cfg(feature="rt-async-std")] { + async_std::task::spawn_blocking(blocking_task).await + } else if #[cfg(feature="rt-tokio")] { + tokio::task::spawn_blocking(blocking_task).await.unwrap_or(err_result) + } else { + #[compile_error("must use an executor")] + } + } + } + } +} diff --git a/veilid-core/src/xx/split_url.rs b/veilid-tools/src/split_url.rs similarity index 99% rename from veilid-core/src/xx/split_url.rs rename to veilid-tools/src/split_url.rs index 076f7212..033ffc96 100644 --- a/veilid-core/src/xx/split_url.rs +++ b/veilid-tools/src/split_url.rs @@ -8,11 +8,6 @@ // Only IP address and DNS hostname host fields are supported use super::*; -use alloc::borrow::ToOwned; -use alloc::string::String; -use alloc::vec::Vec; -use core::fmt; -use core::str::FromStr; fn is_alphanum(c: u8) -> bool { matches!(c, diff --git a/veilid-core/src/tests/.gitignore b/veilid-tools/src/tests/.gitignore similarity index 100% rename from veilid-core/src/tests/.gitignore rename to veilid-tools/src/tests/.gitignore diff --git a/veilid-tools/src/tests/android/mod.rs b/veilid-tools/src/tests/android/mod.rs new file mode 100644 index 00000000..1cba5195 --- /dev/null +++ b/veilid-tools/src/tests/android/mod.rs @@ -0,0 +1,81 @@ +use super::native::*; +use super::*; + +use std::backtrace::Backtrace; +use std::panic; + +use jni::{objects::JClass, objects::JObject, JNIEnv}; + +#[no_mangle] +#[allow(non_snake_case)] +pub extern "system" fn Java_com_veilid_veilid_1tools_1android_1tests_MainActivity_run_1tests( + _env: JNIEnv, + _class: JClass, + _ctx: JObject, +) { + veilid_tools_setup_android_tests(); + block_on(async { + run_all_tests().await; + }) +} + +pub fn veilid_tools_setup_android_tests() { + cfg_if! { + if #[cfg(feature = "tracing")] { + use tracing::*; + use tracing_subscriber::prelude::*; + use tracing_subscriber::*; + + let mut filters = filter::Targets::new(); + for ig in DEFAULT_LOG_IGNORE_LIST { + filters = filters.with_target(ig, filter::LevelFilter::OFF); + } + + // Set up subscriber and layers + let subscriber = Registry::default(); + let mut layers = Vec::new(); + let layer = paranoid_android::layer("veilid-tools") + .with_filter(filter::LevelFilter::TRACE) + .with_filter(filters); + layers.push(layer.boxed()); + + let subscriber = subscriber.with(layers); + subscriber + .try_init() + .expect("failed to init android tracing"); + } else { + let mut builder = android_logd_logger::builder(); + builder.tag("veilid-tools"); + builder.prepend_module(true); + builder.filter_level(LevelFilter::Trace); + for ig in DEFAULT_LOG_IGNORE_LIST { + builder.filter_module(ig, LevelFilter::Off); + } + builder.init(); + } + } + + // Set up panic hook for backtraces + panic::set_hook(Box::new(|panic_info| { + let bt = Backtrace::capture(); + if let Some(location) = panic_info.location() { + error!( + "panic occurred in file '{}' at line {}", + location.file(), + location.line(), + ); + } else { + error!("panic occurred but can't get location information..."); + } + if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + error!("panic payload: {:?}", s); + } else if let Some(s) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", s); + } else if let Some(a) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", a); + } else { + error!("no panic payload"); + } + error!("Backtrace:\n{:?}", bt); + })); +} diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.gitignore b/veilid-tools/src/tests/android/veilid_tools_android_tests/.gitignore new file mode 100644 index 00000000..82e4fd4b --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.gitignore @@ -0,0 +1,16 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +/.idea/deploymentTargetDropDown.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/.gitignore b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/.name b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/.name new file mode 100644 index 00000000..2efbb980 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/.name @@ -0,0 +1 @@ +Veilid-Tools Tests \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/compiler.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/compiler.xml new file mode 100644 index 00000000..fb7f4a8a --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/gradle.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/gradle.xml new file mode 100644 index 00000000..4989a70a --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/gradle.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/jarRepositories.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/jarRepositories.xml new file mode 100644 index 00000000..a5f05cd8 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/jarRepositories.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/misc.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/misc.xml new file mode 100644 index 00000000..ef61796f --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/vcs.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/vcs.xml new file mode 100644 index 00000000..c68f248e --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.project b/veilid-tools/src/tests/android/veilid_tools_android_tests/.project new file mode 100644 index 00000000..cf0850ea --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.project @@ -0,0 +1,28 @@ + + + Veilid Tools Tests + Project android created by Buildship. + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.buildship.core.gradleprojectnature + + + + 0 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/.settings/org.eclipse.buildship.core.prefs b/veilid-tools/src/tests/android/veilid_tools_android_tests/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 00000000..094110d3 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=/opt/android-studio/jre +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/adb+.sh b/veilid-tools/src/tests/android/veilid_tools_android_tests/adb+.sh new file mode 100755 index 00000000..5aa7fbbf --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/adb+.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# Script adb+ +# Usage +# You can run any command adb provide on all your current devices +# ./adb+ is the equivalent of ./adb -s +# +# Examples +# ./adb+ version +# ./adb+ install apidemo.apk +# ./adb+ uninstall com.example.android.apis + +adb devices | while read line +do + if [ ! "$line" = "" ] && [ `echo $line | awk '{print $2}'` = "device" ] + then + device=`echo $line | awk '{print $1}'` + echo "$device $@ ..." + adb -s $device $@ + fi +done diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.classpath b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.classpath new file mode 100644 index 00000000..4a04201c --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.gitignore b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.gitignore new file mode 100644 index 00000000..23c35ec2 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.gitignore @@ -0,0 +1,4 @@ +/build +/.cxx + + diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.project b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.project new file mode 100644 index 00000000..c2e60fbb --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.project @@ -0,0 +1,34 @@ + + + VeilidTools Tests-app + Project VeilidTools Tests-app created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1635633714053 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.settings/org.eclipse.buildship.core.prefs b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 00000000..b1886adb --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,2 @@ +connection.project.dir=.. +eclipse.preferences.version=1 diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/CMakeLists.txt b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/CMakeLists.txt new file mode 100644 index 00000000..57518a5d --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/CMakeLists.txt @@ -0,0 +1,3 @@ +cmake_minimum_required(VERSION 3.1) +project(cpplink CXX) +add_library(cpplink cpplink.cpp) \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/build.gradle b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/build.gradle new file mode 100644 index 00000000..ebcf32e0 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/build.gradle @@ -0,0 +1,85 @@ +plugins { + id 'com.android.application' +} + +android { + compileSdkVersion 33 + buildToolsVersion "33.0.1" + + defaultConfig { + applicationId "com.veilid.veilid_tools_android_tests" + minSdkVersion 24 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + ndk { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + + // Required to copy libc++_shared.so + externalNativeBuild { + cmake { + arguments "-DANDROID_STL=c++_shared" + targets "cpplink" + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + ndkVersion '25.1.8937393' + + // Required to copy libc++_shared.so + externalNativeBuild { + cmake { + version '3.22.1' + path file('CMakeLists.txt') + } + } + namespace 'com.veilid.veilid_tools_android_tests' +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.7.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.security:security-crypto:1.1.0-alpha04' +} + +apply plugin: 'org.mozilla.rust-android-gradle.rust-android' + +cargo { + module = "../../../../../" + libname = "veilid_tools" + targets = ["arm", "arm64", "x86", "x86_64"] + targetDirectory = "../../../../../../target" + prebuiltToolchains = true + profile = gradle.startParameter.taskNames.any{it.toLowerCase().contains("debug")} ? "debug" : "release" + pythonCommand = "python3" + features { + defaultAnd("veilid_tools_android_tests", "rt-tokio") + } +} + +afterEvaluate { + // The `cargoBuild` task isn't available until after evaluation. + android.applicationVariants.all { variant -> + def productFlavor = "" + variant.productFlavors.each { + productFlavor += "${it.name.capitalize()}" + } + def buildType = "${variant.buildType.name.capitalize()}" + tasks["generate${productFlavor}${buildType}Assets"].dependsOn(tasks["cargoBuild"]) + } +} diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/cpplink.cpp b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/cpplink.cpp new file mode 100644 index 00000000..e69de29b diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/proguard-rules.pro b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/AndroidManifest.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..e21e0583 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/java/com/veilid/veilid_tools_android_tests/MainActivity.java b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/java/com/veilid/veilid_tools_android_tests/MainActivity.java new file mode 100644 index 00000000..519bd437 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/java/com/veilid/veilid_tools_android_tests/MainActivity.java @@ -0,0 +1,39 @@ +package com.veilid.veilid_tools_android_tests; + +import androidx.appcompat.app.AppCompatActivity; +import android.content.Context; +import android.os.Bundle; + +public class MainActivity extends AppCompatActivity { + + static { + System.loadLibrary("veilid_tools"); + } + + private static native void run_tests(Context context); + + private Thread testThread; + + class TestThread extends Thread { + private Context context; + + TestThread(Context context) { + this.context = context; + } + + public void run() { + run_tests(this.context); + ((MainActivity)this.context).finish(); + System.exit(0); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + this.testThread = new TestThread(this); + this.testThread.start(); + } +} diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/drawable/ic_launcher_background.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/layout/activity_main.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..4fc24441 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..a571e600 Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..61da551c Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c41dd285 Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..db5080a7 Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..6dba46da Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..da31a871 Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..15ac6817 Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b216f2d3 Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..f25a4197 Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..e96783cc Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values-night/themes.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..5ee570a8 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/colors.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/strings.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..ca58c272 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + VeilidTools Tests + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/themes.xml b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..914f69d4 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/app/src/main/res/values/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/build.gradle b/veilid-tools/src/tests/android/veilid_tools_android_tests/build.gradle new file mode 100644 index 00000000..e49af7c9 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + google() + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.3.1' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +plugins { + id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle.properties b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle.properties new file mode 100644 index 00000000..52f5917c --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle.properties @@ -0,0 +1,19 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true \ No newline at end of file diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle/wrapper/gradle-wrapper.jar b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..f6b961fd Binary files /dev/null and b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle/wrapper/gradle-wrapper.jar differ diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle/wrapper/gradle-wrapper.properties b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..0f75328c --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Nov 28 22:38:53 EST 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/gradlew b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradlew new file mode 100755 index 00000000..cccdd3d5 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/gradlew.bat b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradlew.bat new file mode 100644 index 00000000..e95643d6 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/install_on_all_devices.sh b/veilid-tools/src/tests/android/veilid_tools_android_tests/install_on_all_devices.sh new file mode 100755 index 00000000..f96d53b1 --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/install_on_all_devices.sh @@ -0,0 +1,2 @@ +#!/bin/bash +./gradlew installDebug diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/remove_from_all_devices.sh b/veilid-tools/src/tests/android/veilid_tools_android_tests/remove_from_all_devices.sh new file mode 100755 index 00000000..e76b7cff --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/remove_from_all_devices.sh @@ -0,0 +1,3 @@ +#!/bin/bash +./adb+.sh uninstall com.veilid.veilid_tools_android_tests + diff --git a/veilid-tools/src/tests/android/veilid_tools_android_tests/settings.gradle b/veilid-tools/src/tests/android/veilid_tools_android_tests/settings.gradle new file mode 100644 index 00000000..a391cf6f --- /dev/null +++ b/veilid-tools/src/tests/android/veilid_tools_android_tests/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name = "Veilid-Tools Tests" \ No newline at end of file diff --git a/veilid-tools/src/tests/common/mod.rs b/veilid-tools/src/tests/common/mod.rs new file mode 100644 index 00000000..393bcfa9 --- /dev/null +++ b/veilid-tools/src/tests/common/mod.rs @@ -0,0 +1,27 @@ +pub mod test_async_tag_lock; +pub mod test_host_interface; + +#[allow(dead_code)] +pub static DEFAULT_LOG_IGNORE_LIST: [&str; 21] = [ + "mio", + "h2", + "hyper", + "tower", + "tonic", + "tokio", + "runtime", + "tokio_util", + "want", + "serial_test", + "async_std", + "async_io", + "polling", + "rustls", + "async_tungstenite", + "tungstenite", + "netlink_proto", + "netlink_sys", + "trust_dns_resolver", + "trust_dns_proto", + "attohttpc", +]; diff --git a/veilid-core/src/tests/common/test_async_tag_lock.rs b/veilid-tools/src/tests/common/test_async_tag_lock.rs similarity index 89% rename from veilid-core/src/tests/common/test_async_tag_lock.rs rename to veilid-tools/src/tests/common/test_async_tag_lock.rs index e590d4df..c1b2ecc4 100644 --- a/veilid-core/src/tests/common/test_async_tag_lock.rs +++ b/veilid-tools/src/tests/common/test_async_tag_lock.rs @@ -1,4 +1,3 @@ -use crate::xx::*; use crate::*; pub async fn test_simple_no_contention() { @@ -36,12 +35,12 @@ pub async fn test_simple_single_contention() { let g1 = table.lock_tag(a1).await; info!("locked"); - let t1 = intf::spawn(async move { + let t1 = spawn(async move { // move the guard into the task let _g1_take = g1; // hold the guard for a bit info!("waiting"); - intf::sleep(1000).await; + sleep(1000).await; // release the guard info!("released"); }); @@ -68,21 +67,21 @@ pub async fn test_simple_double_contention() { let g2 = table.lock_tag(a2).await; info!("locked"); - let t1 = intf::spawn(async move { + let t1 = spawn(async move { // move the guard into the tas let _g1_take = g1; // hold the guard for a bit info!("waiting"); - intf::sleep(1000).await; + sleep(1000).await; // release the guard info!("released"); }); - let t2 = intf::spawn(async move { + let t2 = spawn(async move { // move the guard into the task let _g2_take = g2; // hold the guard for a bit info!("waiting"); - intf::sleep(500).await; + sleep(500).await; // release the guard info!("released"); }); @@ -109,37 +108,37 @@ pub async fn test_parallel_single_contention() { let a1 = SocketAddr::new("1.2.3.4".parse().unwrap(), 1234); let table1 = table.clone(); - let t1 = intf::spawn(async move { + let t1 = spawn(async move { // lock the tag let _g = table1.lock_tag(a1).await; info!("locked t1"); // hold the guard for a bit info!("waiting t1"); - intf::sleep(500).await; + sleep(500).await; // release the guard info!("released t1"); }); let table2 = table.clone(); - let t2 = intf::spawn(async move { + let t2 = spawn(async move { // lock the tag let _g = table2.lock_tag(a1).await; info!("locked t2"); // hold the guard for a bit info!("waiting t2"); - intf::sleep(500).await; + sleep(500).await; // release the guard info!("released t2"); }); let table3 = table.clone(); - let t3 = intf::spawn(async move { + let t3 = spawn(async move { // lock the tag let _g = table3.lock_tag(a1).await; info!("locked t3"); // hold the guard for a bit info!("waiting t3"); - intf::sleep(500).await; + sleep(500).await; // release the guard info!("released t3"); }); diff --git a/veilid-tools/src/tests/common/test_host_interface.rs b/veilid-tools/src/tests/common/test_host_interface.rs new file mode 100644 index 00000000..43b9b3a8 --- /dev/null +++ b/veilid-tools/src/tests/common/test_host_interface.rs @@ -0,0 +1,554 @@ +use crate::*; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use js_sys::*; + } else { + use std::time::{Duration, SystemTime}; + } +} + +pub async fn test_log() { + info!("testing log"); +} + +pub async fn test_get_timestamp() { + info!("testing get_timestamp"); + let t1 = get_timestamp(); + let t2 = get_timestamp(); + assert!(t2 >= t1); +} + +pub async fn test_eventual() { + info!("testing Eventual"); + { + let e1 = Eventual::new(); + let i1 = e1.instance_clone(1u32); + let i2 = e1.instance_clone(2u32); + let i3 = e1.instance_clone(3u32); + drop(i3); + let i4 = e1.instance_clone(4u32); + drop(i2); + + let jh = spawn(async move { + sleep(1000).await; + e1.resolve(); + }); + + assert_eq!(i1.await, 1u32); + assert_eq!(i4.await, 4u32); + + jh.await; + } + { + let e1 = Eventual::new(); + let i1 = e1.instance_clone(1u32); + let i2 = e1.instance_clone(2u32); + let i3 = e1.instance_clone(3u32); + let i4 = e1.instance_clone(4u32); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + let i5 = e1.instance_clone(5u32); + let i6 = e1.instance_clone(6u32); + assert_eq!(i1.await, 1u32); + assert_eq!(i5.await, 5u32); + assert_eq!(i6.await, 6u32); + }); + sleep(1000).await; + let resolved = e1_c1.resolve(); + drop(i2); + drop(i3); + assert_eq!(i4.await, 4u32); + resolved.await; + jh.await; + } + { + let e1 = Eventual::new(); + let i1 = e1.instance_clone(1u32); + let i2 = e1.instance_clone(2u32); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + assert_eq!(i1.await, 1u32); + assert_eq!(i2.await, 2u32); + }); + sleep(1000).await; + e1_c1.resolve().await; + + jh.await; + + e1_c1.reset(); + // + let j1 = e1.instance_clone(1u32); + let j2 = e1.instance_clone(2u32); + let jh = spawn(async move { + assert_eq!(j1.await, 1u32); + assert_eq!(j2.await, 2u32); + }); + sleep(1000).await; + e1_c1.resolve().await; + + jh.await; + + e1_c1.reset(); + } +} + +pub async fn test_eventual_value() { + info!("testing Eventual Value"); + { + let e1 = EventualValue::::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + drop(i3); + let i4 = e1.instance(); + drop(i2); + + let e1_c1 = e1.clone(); + let jh = spawn(async move { + sleep(1000).await; + e1_c1.resolve(3u32); + }); + + i1.await; + i4.await; + jh.await; + assert_eq!(e1.take_value(), Some(3u32)); + } + { + let e1 = EventualValue::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + let i4 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + let i5 = e1.instance(); + let i6 = e1.instance(); + i1.await; + i5.await; + i6.await; + }); + sleep(1000).await; + let resolved = e1_c1.resolve(4u16); + drop(i2); + drop(i3); + i4.await; + resolved.await; + jh.await; + assert_eq!(e1_c1.take_value(), Some(4u16)); + } + { + let e1 = EventualValue::new(); + assert_eq!(e1.take_value(), None); + let i1 = e1.instance(); + let i2 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + i1.await; + i2.await; + }); + sleep(1000).await; + e1_c1.resolve(5u32).await; + jh.await; + assert_eq!(e1_c1.take_value(), Some(5u32)); + e1_c1.reset(); + assert_eq!(e1_c1.take_value(), None); + // + let j1 = e1.instance(); + let j2 = e1.instance(); + let jh = spawn(async move { + j1.await; + j2.await; + }); + sleep(1000).await; + e1_c1.resolve(6u32).await; + jh.await; + assert_eq!(e1_c1.take_value(), Some(6u32)); + e1_c1.reset(); + assert_eq!(e1_c1.take_value(), None); + } +} + +pub async fn test_eventual_value_clone() { + info!("testing Eventual Value Clone"); + { + let e1 = EventualValueClone::::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + drop(i3); + let i4 = e1.instance(); + drop(i2); + + let jh = spawn(async move { + sleep(1000).await; + e1.resolve(3u32); + }); + + assert_eq!(i1.await, 3); + assert_eq!(i4.await, 3); + + jh.await; + } + + { + let e1 = EventualValueClone::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let i3 = e1.instance(); + let i4 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + let i5 = e1.instance(); + let i6 = e1.instance(); + assert_eq!(i1.await, 4); + assert_eq!(i5.await, 4); + assert_eq!(i6.await, 4); + }); + sleep(1000).await; + let resolved = e1_c1.resolve(4u16); + drop(i2); + drop(i3); + assert_eq!(i4.await, 4); + resolved.await; + jh.await; + } + + { + let e1 = EventualValueClone::new(); + let i1 = e1.instance(); + let i2 = e1.instance(); + let e1_c1 = e1.clone(); + let jh = spawn(async move { + assert_eq!(i1.await, 5); + assert_eq!(i2.await, 5); + }); + sleep(1000).await; + e1_c1.resolve(5u32).await; + jh.await; + e1_c1.reset(); + // + let j1 = e1.instance(); + let j2 = e1.instance(); + let jh = spawn(async move { + assert_eq!(j1.await, 6); + assert_eq!(j2.await, 6); + }); + sleep(1000).await; + e1_c1.resolve(6u32).await; + jh.await; + e1_c1.reset(); + } +} +pub async fn test_interval() { + info!("testing interval"); + + let tick: Arc> = Arc::new(Mutex::new(0u32)); + let stopper = interval(1000, move || { + let tick = tick.clone(); + async move { + let mut tick = tick.lock(); + trace!("tick {}", tick); + *tick += 1; + } + }); + + sleep(5500).await; + + stopper.await; +} + +pub async fn test_timeout() { + info!("testing timeout"); + + let tick: Arc> = Arc::new(Mutex::new(0u32)); + let tick_1 = tick.clone(); + assert!( + timeout(2500, async move { + let mut tick = tick_1.lock(); + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + trace!("tick {}", tick); + sleep(1000).await; + *tick += 1; + }) + .await + .is_err(), + "should have timed out" + ); + + let ticks = *tick.lock(); + assert!(ticks <= 2); +} + +pub async fn test_sleep() { + info!("testing sleep"); + cfg_if! { + if #[cfg(target_arch = "wasm32")] { + + let t1 = Date::now(); + sleep(1000).await; + let t2 = Date::now(); + assert!((t2-t1) >= 1000.0); + + } else { + + let sys_time = SystemTime::now(); + let one_sec = Duration::from_secs(1); + + sleep(1000).await; + assert!(sys_time.elapsed().unwrap() >= one_sec); + } + } +} + +macro_rules! assert_split_url { + ($url:expr, $scheme:expr, $host:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new($scheme, None, $host, None, None)) + ); + }; + ($url:expr, $scheme:expr, $host:expr, $port:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new($scheme, None, $host, $port, None)) + ); + }; + ($url:expr, $scheme:expr, $host:expr, $port:expr, $path:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new( + $scheme, + None, + $host, + $port, + Some(SplitUrlPath::new( + $path, + Option::::None, + Option::::None + )) + )) + ); + }; + ($url:expr, $scheme:expr, $host:expr, $port:expr, $path:expr, $frag:expr, $query:expr) => { + assert_eq!( + SplitUrl::from_str($url), + Ok(SplitUrl::new( + $scheme, + None, + $host, + $port, + Some(SplitUrlPath::new($path, $frag, $query)) + )) + ); + }; +} + +macro_rules! assert_split_url_parse { + ($url:expr) => { + let url = $url; + let su1 = SplitUrl::from_str(url).expect("should parse"); + assert_eq!(su1.to_string(), url); + }; +} + +fn host>(s: S) -> SplitUrlHost { + SplitUrlHost::Hostname(s.as_ref().to_owned()) +} + +fn ip>(s: S) -> SplitUrlHost { + SplitUrlHost::IpAddr(IpAddr::from_str(s.as_ref()).unwrap()) +} + +pub async fn test_split_url() { + info!("testing split_url"); + + assert_split_url!("http://foo", "http", host("foo")); + assert_split_url!("http://foo:1234", "http", host("foo"), Some(1234)); + assert_split_url!("http://foo:1234/", "http", host("foo"), Some(1234), ""); + assert_split_url!( + "http://foo:1234/asdf/qwer", + "http", + host("foo"), + Some(1234), + "asdf/qwer" + ); + assert_split_url!("http://foo/", "http", host("foo"), None, ""); + assert_split_url!("http://11.2.3.144/", "http", ip("11.2.3.144"), None, ""); + assert_split_url!("http://[1111::2222]/", "http", ip("1111::2222"), None, ""); + assert_split_url!( + "http://[1111::2222]:123/", + "http", + ip("1111::2222"), + Some(123), + "" + ); + + assert_split_url!( + "http://foo/asdf/qwer", + "http", + host("foo"), + None, + "asdf/qwer" + ); + assert_split_url!( + "http://foo/asdf/qwer#3", + "http", + host("foo"), + None, + "asdf/qwer", + Some("3"), + Option::::None + ); + assert_split_url!( + "http://foo/asdf/qwer?xxx", + "http", + host("foo"), + None, + "asdf/qwer", + Option::::None, + Some("xxx") + ); + assert_split_url!( + "http://foo/asdf/qwer#yyy?xxx", + "http", + host("foo"), + None, + "asdf/qwer", + Some("yyy"), + Some("xxx") + ); + assert_err!(SplitUrl::from_str("://asdf")); + assert_err!(SplitUrl::from_str("")); + assert_err!(SplitUrl::from_str("::")); + assert_err!(SplitUrl::from_str("://:")); + assert_err!(SplitUrl::from_str("a://:")); + assert_err!(SplitUrl::from_str("a://:1243")); + assert_err!(SplitUrl::from_str("a://:65536")); + assert_err!(SplitUrl::from_str("a://:-16")); + assert_err!(SplitUrl::from_str("a:///")); + assert_err!(SplitUrl::from_str("a:///qwer:")); + assert_err!(SplitUrl::from_str("a:///qwer://")); + assert_err!(SplitUrl::from_str("a://qwer://")); + assert_err!(SplitUrl::from_str("a://[1111::2222]:/")); + assert_err!(SplitUrl::from_str("a://[1111::2222]:")); + + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord#qux?zuz"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord#qux"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord?zuz"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234/fnord/"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234//"); + assert_split_url_parse!("sch://foo:bar@baz.com:1234"); + assert_split_url_parse!("sch://foo:bar@[1111::2222]:1234"); + assert_split_url_parse!("sch://foo:bar@[::]:1234"); + assert_split_url_parse!("sch://foo:bar@1.2.3.4:1234"); + assert_split_url_parse!("sch://@baz.com:1234"); + assert_split_url_parse!("sch://baz.com/asdf/asdf"); + assert_split_url_parse!("sch://baz.com/"); + assert_split_url_parse!("s://s"); +} + +pub async fn test_get_random_u64() { + info!("testing random number generator for u64"); + let t1 = get_timestamp(); + let count = 10000; + for _ in 0..count { + let _ = get_random_u64(); + } + let t2 = get_timestamp(); + let tdiff = ((t2 - t1) as f64) / 1000000.0f64; + info!( + "running get_random_u64 with {} iterations took {} seconds", + count, tdiff + ); +} + +pub async fn test_get_random_u32() { + info!("testing random number generator for u32"); + let t1 = get_timestamp(); + let count = 10000; + for _ in 0..count { + let _ = get_random_u32(); + } + let t2 = get_timestamp(); + let tdiff = ((t2 - t1) as f64) / 1000000.0f64; + info!( + "running get_random_u32 with {} iterations took {} seconds", + count, tdiff + ); +} + +pub async fn test_must_join_single_future() { + info!("testing must join single future"); + let sf = MustJoinSingleFuture::::new(); + assert_eq!(sf.check().await, Ok(None)); + assert_eq!( + sf.single_spawn(async { + sleep(2000).await; + 69 + }) + .await, + Ok((None, true)) + ); + assert_eq!(sf.check().await, Ok(None)); + assert_eq!(sf.single_spawn(async { panic!() }).await, Ok((None, false))); + assert_eq!(sf.join().await, Ok(Some(69))); + assert_eq!( + sf.single_spawn(async { + sleep(1000).await; + 37 + }) + .await, + Ok((None, true)) + ); + sleep(2000).await; + assert_eq!( + sf.single_spawn(async { + sleep(1000).await; + 27 + }) + .await, + Ok((Some(37), true)) + ); + sleep(2000).await; + assert_eq!(sf.join().await, Ok(Some(27))); + assert_eq!(sf.check().await, Ok(None)); +} + +pub async fn test_tools() { + info!("testing retry_falloff_log"); + let mut last_us = 0u64; + for x in 0..1024 { + let cur_us = x as u64 * 1000000u64; + if retry_falloff_log(last_us, cur_us, 10_000_000u64, 6_000_000_000u64, 2.0f64) { + info!(" retry at {} secs", timestamp_to_secs(cur_us)); + last_us = cur_us; + } + } +} + +pub async fn test_all() { + test_log().await; + test_get_timestamp().await; + test_tools().await; + test_split_url().await; + test_get_random_u64().await; + test_get_random_u32().await; + test_sleep().await; + #[cfg(not(target_arch = "wasm32"))] + test_must_join_single_future().await; + test_eventual().await; + test_eventual_value().await; + test_eventual_value_clone().await; + test_interval().await; + test_timeout().await; +} diff --git a/veilid-tools/src/tests/files/cert.pem b/veilid-tools/src/tests/files/cert.pem new file mode 100644 index 00000000..b86846ed --- /dev/null +++ b/veilid-tools/src/tests/files/cert.pem @@ -0,0 +1,88 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: + 12:ce:63:bd:90:f5:ab:de:6d:7f:d7:3e:f3:e6:bb + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Veilid Test CA + Validity + Not Before: Nov 22 13:52:16 2021 GMT + Not After : Feb 25 13:52:16 2024 GMT + Subject: CN=Veilid Test Certificate + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + RSA Public-Key: (2048 bit) + Modulus: + 00:cb:2e:7a:47:81:be:6f:6b:53:37:51:c1:50:68: + 5a:44:3d:ba:b9:9b:78:40:84:35:d4:0e:e8:41:a6: + 0e:0a:b9:34:ae:97:a3:37:3e:81:ed:6c:0f:f8:8a: + 8b:0b:1a:ed:06:97:57:6d:49:a5:ec:b4:c4:d8:6d: + d2:57:c3:87:89:99:ee:b0:d7:c5:82:a1:dc:d5:98: + b3:ef:10:da:c0:5c:38:a2:bb:15:3e:0e:5e:bc:a0: + cd:a1:f0:07:67:bb:57:3f:89:cc:72:4f:bb:c0:a7: + ed:ad:15:07:61:c2:b4:21:73:39:00:9b:8f:aa:04: + 1b:c4:9d:d4:00:44:87:b1:79:b4:e1:4e:01:3c:ee: + a4:bb:f9:ad:5d:88:41:03:b4:bf:df:bf:71:24:ee: + 0b:69:59:55:dd:43:d1:91:04:de:98:9c:54:f2:ee: + 63:78:fe:76:19:bf:e6:5d:d6:58:81:3c:1b:02:3d: + 5d:cc:70:4a:c1:84:06:f6:1a:db:16:b0:e0:30:b0: + 3a:85:41:48:a1:88:c5:38:04:7b:03:c4:86:f0:da: + 1a:ff:bc:d1:ac:7f:cd:0c:e8:5a:42:5e:43:7f:0d: + 61:5d:41:67:0f:b8:07:47:21:93:44:b2:ab:fa:d8: + 69:bb:b9:6d:a1:56:6d:23:54:aa:49:67:e7:57:c6: + e9:c7 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + X509v3 Subject Key Identifier: + 70:ED:B0:96:71:33:43:16:EF:32:FF:69:11:C9:F0:02:3F:6C:81:88 + X509v3 Authority Key Identifier: + keyid:5D:7F:8D:AF:1A:56:D3:F4:CA:3D:D3:6D:EF:50:11:F7:64:99:6F:02 + DirName:/CN=Veilid Test CA + serial:22:7A:2E:68:7C:DF:7B:81:85:1A:50:98:16:62:22:D0:0B:D6:1C:4A + + X509v3 Extended Key Usage: + TLS Web Server Authentication + X509v3 Key Usage: + Digital Signature, Key Encipherment + X509v3 Subject Alternative Name: + DNS:Veilid Test Certificate + Signature Algorithm: sha256WithRSAEncryption + b8:fc:ac:62:d6:95:af:09:db:24:7d:82:2c:02:e1:d0:7b:f5: + 69:03:a4:42:55:c6:0d:2a:f1:9d:0e:c4:9b:78:40:7d:0d:7d: + ec:66:f6:c4:6d:06:d0:5b:58:de:ba:e6:67:ea:af:41:a3:87: + b4:37:8b:a8:1f:51:ae:70:e0:0d:f5:51:0a:7a:b3:b3:1d:d1: + 77:92:63:35:ae:50:9e:04:3d:04:6e:f1:60:c8:e3:8f:1f:75: + 47:05:27:a0:ff:c5:1b:30:68:b2:f9:5b:e6:f2:81:0f:9b:f2: + e8:8c:9d:b6:57:b2:c1:29:e7:d0:d0:88:b3:ba:8e:78:2e:ef: + ce:03:a3:12:fa:b4:e9:4e:1f:de:1a:cb:77:72:6b:71:98:02: + 37:d2:b4:02:f0:2c:08:67:ca:75:0d:af:81:bf:f8:57:f8:d9: + 4a:93:4f:db:3c:e1:af:3e:ab:9c:fe:87:f0:3a:01:21:6a:5c: + 99:83:e3:03:47:98:15:23:24:b3:ee:29:27:f4:f1:34:c1:e4: + f8:39:5a:92:da:c7:08:dc:71:87:1c:ff:67:e7:ef:24:bc:34: + e3:4e:e0:16:12:84:60:d4:7f:a2:c0:5b:85:a9:c5:ef:78:0b: + c3:64:cb:b4:05:eb:51:e5:c1:0f:60:da:5c:98:08:bf:5d:b9: + 1d:33:a7:26 +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIPEs5jvZD1q95tf9c+8+a7MA0GCSqGSIb3DQEBCwUAMBkx +FzAVBgNVBAMMDlZlaWxpZCBUZXN0IENBMB4XDTIxMTEyMjEzNTIxNloXDTI0MDIy +NTEzNTIxNlowIjEgMB4GA1UEAwwXVmVpbGlkIFRlc3QgQ2VydGlmaWNhdGUwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDLLnpHgb5va1M3UcFQaFpEPbq5 +m3hAhDXUDuhBpg4KuTSul6M3PoHtbA/4iosLGu0Gl1dtSaXstMTYbdJXw4eJme6w +18WCodzVmLPvENrAXDiiuxU+Dl68oM2h8Adnu1c/icxyT7vAp+2tFQdhwrQhczkA +m4+qBBvEndQARIexebThTgE87qS7+a1diEEDtL/fv3Ek7gtpWVXdQ9GRBN6YnFTy +7mN4/nYZv+Zd1liBPBsCPV3McErBhAb2GtsWsOAwsDqFQUihiMU4BHsDxIbw2hr/ +vNGsf80M6FpCXkN/DWFdQWcPuAdHIZNEsqv62Gm7uW2hVm0jVKpJZ+dXxunHAgMB +AAGjgckwgcYwCQYDVR0TBAIwADAdBgNVHQ4EFgQUcO2wlnEzQxbvMv9pEcnwAj9s +gYgwVAYDVR0jBE0wS4AUXX+NrxpW0/TKPdNt71AR92SZbwKhHaQbMBkxFzAVBgNV +BAMMDlZlaWxpZCBUZXN0IENBghQiei5ofN97gYUaUJgWYiLQC9YcSjATBgNVHSUE +DDAKBggrBgEFBQcDATALBgNVHQ8EBAMCBaAwIgYDVR0RBBswGYIXVmVpbGlkIFRl +c3QgQ2VydGlmaWNhdGUwDQYJKoZIhvcNAQELBQADggEBALj8rGLWla8J2yR9giwC +4dB79WkDpEJVxg0q8Z0OxJt4QH0Nfexm9sRtBtBbWN665mfqr0Gjh7Q3i6gfUa5w +4A31UQp6s7Md0XeSYzWuUJ4EPQRu8WDI448fdUcFJ6D/xRswaLL5W+bygQ+b8uiM +nbZXssEp59DQiLO6jngu784DoxL6tOlOH94ay3dya3GYAjfStALwLAhnynUNr4G/ ++Ff42UqTT9s84a8+q5z+h/A6ASFqXJmD4wNHmBUjJLPuKSf08TTB5Pg5WpLaxwjc +cYcc/2fn7yS8NONO4BYShGDUf6LAW4Wpxe94C8Nky7QF61HlwQ9g2lyYCL9duR0z +pyY= +-----END CERTIFICATE----- diff --git a/veilid-tools/src/tests/files/key.pem b/veilid-tools/src/tests/files/key.pem new file mode 100644 index 00000000..38968f47 --- /dev/null +++ b/veilid-tools/src/tests/files/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyy56R4G+b2tTN1HBUGhaRD26uZt4QIQ11A7oQaYOCrk0rpej +Nz6B7WwP+IqLCxrtBpdXbUml7LTE2G3SV8OHiZnusNfFgqHc1Ziz7xDawFw4orsV +Pg5evKDNofAHZ7tXP4nMck+7wKftrRUHYcK0IXM5AJuPqgQbxJ3UAESHsXm04U4B +PO6ku/mtXYhBA7S/379xJO4LaVlV3UPRkQTemJxU8u5jeP52Gb/mXdZYgTwbAj1d +zHBKwYQG9hrbFrDgMLA6hUFIoYjFOAR7A8SG8Noa/7zRrH/NDOhaQl5Dfw1hXUFn +D7gHRyGTRLKr+thpu7ltoVZtI1SqSWfnV8bpxwIDAQABAoIBAQCae5MjbUWC56JU +7EdEQKNpQVoIp2mt/BgFTPRQfdYtVxX0LX0+krss7r3R5lzDq8xN96HUiWur5uHI +APAuJI+YEr8GHHii0zjZ+onMmg8ItNWm/QGwtjJXzxeqKZsnxqwWtkoJHBCP8d5n +fBapwOU+jaHokV6RESCfxLSdI33cdGcOgDAn4/lvcXZ4Pq0qbitFuZwBPpobHbp4 +Mo94K7oh4KCt3FDMfZshkSF0wlquRIeUsI2uZUofybDa/j1RgEsqBZIrHqM6xXV1 +/r13+mMZC4otE0qhBV9jTYffaxooOnae8/Ve0FgaPWpNm7AD6p7l4a3csIkcggMS +xx7cntR5AoGBAOvPgDDLJ7h+AgY7jAd13/eZ75NtuEWbSq4c4Kwwy1K17L+3isX/ +RRkQ5qGTNsT6j1dddfwzX6Rhsi+JywczEdJdWgGNpFCIa8Ly2+47YLDpv0ZIISIZ +V0Ngg6dyuuQo7gFlLz9Dhe/If32/93gEW6HZOjn+GmQu53ZSDdHvukpjAoGBANyT +0GZzaP2ulV3Pk+9nJhC6eK2BZWF4ODXHsAgNUEyWZ4qDM71oDxyFbaWS2IDg7jz7 +T2wVffRFDcx8oNXWbhWbejSBGHWI8HRk0Ki83K0r7Cj8Uhy53rQjGOsdLf3K9T9h +GGVcwMHlBGIvswqTnJKysvgcoh1Ir+6RqbfCmG5NAoGAaVa8UQ+vor7HcLlRCFQj +xJvDZfxxgMaqSbUkuEbjzQLvy4TWPTSXTWc7X5o/sSasub5KYmsgonHyA0Juq7yo +jWyeNGttp3wJh4CttnJX8y+3/lFiW7UuQi7vIPIjgqC2EXF99ajYQBE0wpvqlHZ9 +6IL9e8KDT5WUWEq3WbzZXzkCgYB/0Md6FnZISdoTui0nFMZh+yvinpB4ookv4L6I +a+6T8rOc99oLbzkSdd7LiwQZ6j0i6R1krC+IVFtimvU39EFmE+oEcqoRsYBkcebX +YFkfn8wBE/Ug4DPEfnH6C7aS0gC68TCJy+2GbYbUvn8pKdAY0aQTUcQ+49fOjmmi +KgjaIQKBgQDoT0af/7a7LnY9dkbkz624HmNVyMPOa4/STrdxyy3NRhq/dysRW+At +x30nvCWpv0Z5BAyaUCrRWPGFxhv3/Z7qb4fx5uUbC3Jc04I5D6fwYqrQofGS8TMK +Lrg83o5Ag++pllu1IeWiGQPRbn7VZ+O6pISgpRpYBexXGyLJ6wtcAw== +-----END RSA PRIVATE KEY----- diff --git a/veilid-tools/src/tests/ios/mod.rs b/veilid-tools/src/tests/ios/mod.rs new file mode 100644 index 00000000..77534395 --- /dev/null +++ b/veilid-tools/src/tests/ios/mod.rs @@ -0,0 +1,62 @@ +use super::native::*; +use super::*; + +use std::backtrace::Backtrace; +use std::panic; + +#[no_mangle] +pub extern "C" fn run_veilid_tools_tests() { + veilid_tools_setup_ios_tests(); + block_on(async { + run_all_tests().await; + }) +} + +pub fn veilid_tools_setup_ios_tests() { + cfg_if! { + if #[cfg(feature = "tracing")] { + use tracing_oslog::OsLogger; + use tracing_subscriber::prelude::*; + + let mut filters = filter::Targets::new(); + for ig in DEFAULT_LOG_IGNORE_LIST { + filters = filters.with_target(ig, filter::LevelFilter::OFF); + } + tracing_subscriber::registry() + .with(filters) + .with(filter::LevelFilter::TRACE) + .with(OsLogger::new("com.veilid.veilidtools-tests", "")) + .init(); + } else { + use oslog::OsLogger; + + OsLogger::new("com.veilid.veilidtools-tests") + .level_filter(LevelFilter::Trace) + .init() + .unwrap(); + } + } + + panic::set_hook(Box::new(|panic_info| { + let bt = Backtrace::capture(); + if let Some(location) = panic_info.location() { + error!( + "panic occurred in file '{}' at line {}", + location.file(), + location.line(), + ); + } else { + error!("panic occurred but can't get location information..."); + } + if let Some(s) = panic_info.payload().downcast_ref::<&str>() { + error!("panic payload: {:?}", s); + } else if let Some(s) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", s); + } else if let Some(a) = panic_info.payload().downcast_ref::() { + error!("panic payload: {:?}", a); + } else { + error!("no panic payload"); + } + error!("Backtrace:\n{:?}", bt); + })); +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/.gitignore b/veilid-tools/src/tests/ios/veilidtools-tests/.gitignore new file mode 100644 index 00000000..438326e0 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/.gitignore @@ -0,0 +1,91 @@ +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## User settings +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Obj-C/Swift specific +*.hmap + +## App packaging +*.ipa +*.dSYM.zip +*.dSYM + +## Playgrounds +timeline.xctimeline +playground.xcworkspace + +# Swift Package Manager +# +# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. +# Packages/ +# Package.pins +# Package.resolved +# *.xcodeproj +# +# Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata +# hence it is not needed unless you have added a package configuration file to your project +# .swiftpm + +.build/ + +# CocoaPods +# +# We recommend against adding the Pods directory to your .gitignore. However +# you should judge for yourself, the pros and cons are mentioned at: +# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control +# +# Pods/ +# +# Add this line if you want to avoid checking in source code from the Xcode workspace +# *.xcworkspace + +# Carthage +# +# Add this line if you want to avoid checking in source code from Carthage dependencies. +# Carthage/Checkouts + +Carthage/Build/ + +# Accio dependency management +Dependencies/ +.accio/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. +# Instead, use fastlane to re-generate the screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +# Code Injection +# +# After new code Injection tools there's a generated folder /iOSInjectionProject +# https://github.com/johnno1962/injectionforxcode + +iOSInjectionProject/ + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.c b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.c new file mode 100644 index 00000000..5d59813e --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.c @@ -0,0 +1,8 @@ +// +// veilidtools.c +// veilidtools-tests +// +// Created by JSmith on 7/6/21. +// + +#include "veilid-tools.h" diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.h b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.h new file mode 100644 index 00000000..8f11707a --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilid-tools.h @@ -0,0 +1,13 @@ +// +// veilid-tools.h +// veilid-tools-tests +// +// Created by JSmith on 7/6/21. +// + +#ifndef veilid_tools_h +#define veilid_tools_h + +void run_veilid_tools_tests(void); + +#endif /* veilid-tools_h */ diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests-Bridging-Header.h b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests-Bridging-Header.h new file mode 100644 index 00000000..3344ec39 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests-Bridging-Header.h @@ -0,0 +1,5 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#include "veilid-tools.h" diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.pbxproj b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.pbxproj new file mode 100644 index 00000000..c34e5d62 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.pbxproj @@ -0,0 +1,419 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 4317C6BD3694A676009C717F /* (null) in Sources */ = {isa = PBXBuildFile; }; + 43C436B0368904AC002D11C5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C436AF368904AC002D11C5 /* AppDelegate.swift */; }; + 43C436B2368904AC002D11C5 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C436B1368904AC002D11C5 /* SceneDelegate.swift */; }; + 43C436B4368904AC002D11C5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43C436B3368904AC002D11C5 /* ViewController.swift */; }; + 43C436B7368904AC002D11C5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43C436B5368904AC002D11C5 /* Main.storyboard */; }; + 43C436B9368904AD002D11C5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 43C436B8368904AD002D11C5 /* Assets.xcassets */; }; + 43C436BC368904AD002D11C5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 43C436BA368904AD002D11C5 /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 4317C6BA3694A675009C717F /* veilidtools-tests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "veilidtools-tests-Bridging-Header.h"; sourceTree = ""; }; + 4317C6BB3694A676009C717F /* veilid-tools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "veilid-tools.h"; sourceTree = ""; }; + 4317C6BC3694A676009C717F /* veilid-tools.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "veilid-tools.c"; sourceTree = ""; }; + 43C436AC368904AC002D11C5 /* veilidtools-tests.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "veilidtools-tests.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 43C436AF368904AC002D11C5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 43C436B1368904AC002D11C5 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; + 43C436B3368904AC002D11C5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + 43C436B6368904AC002D11C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 43C436B8368904AD002D11C5 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 43C436BB368904AD002D11C5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 43C436BD368904AD002D11C5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 43C436A9368904AC002D11C5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4317C6B7369490DA009C717F /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; + 43C436A3368904AC002D11C5 = { + isa = PBXGroup; + children = ( + 4317C6BB3694A676009C717F /* veilid-tools.h */, + 4317C6BC3694A676009C717F /* veilid-tools.c */, + 43C436AE368904AC002D11C5 /* veilidtools-tests */, + 43C436AD368904AC002D11C5 /* Products */, + 4317C6B7369490DA009C717F /* Frameworks */, + 4317C6BA3694A675009C717F /* veilidtools-tests-Bridging-Header.h */, + ); + sourceTree = ""; + }; + 43C436AD368904AC002D11C5 /* Products */ = { + isa = PBXGroup; + children = ( + 43C436AC368904AC002D11C5 /* veilidtools-tests.app */, + ); + name = Products; + sourceTree = ""; + }; + 43C436AE368904AC002D11C5 /* veilidtools-tests */ = { + isa = PBXGroup; + children = ( + 43C436AF368904AC002D11C5 /* AppDelegate.swift */, + 43C436B1368904AC002D11C5 /* SceneDelegate.swift */, + 43C436B3368904AC002D11C5 /* ViewController.swift */, + 43C436B5368904AC002D11C5 /* Main.storyboard */, + 43C436B8368904AD002D11C5 /* Assets.xcassets */, + 43C436BA368904AD002D11C5 /* LaunchScreen.storyboard */, + 43C436BD368904AD002D11C5 /* Info.plist */, + ); + path = "veilidtools-tests"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 43C436AB368904AC002D11C5 /* veilidtools-tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 43C436C0368904AD002D11C5 /* Build configuration list for PBXNativeTarget "veilidtools-tests" */; + buildPhases = ( + 43C436C336893020002D11C5 /* Cargo Build */, + 43C436A8368904AC002D11C5 /* Sources */, + 43C436A9368904AC002D11C5 /* Frameworks */, + 43C436AA368904AC002D11C5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "veilidtools-tests"; + productName = "veilidtools-tests"; + productReference = 43C436AC368904AC002D11C5 /* veilidtools-tests.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 43C436A4368904AC002D11C5 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 1250; + LastUpgradeCheck = 1250; + TargetAttributes = { + 43C436AB368904AC002D11C5 = { + CreatedOnToolsVersion = 12.5.1; + LastSwiftMigration = 1250; + }; + }; + }; + buildConfigurationList = 43C436A7368904AC002D11C5 /* Build configuration list for PBXProject "veilidtools-tests" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 43C436A3368904AC002D11C5; + productRefGroup = 43C436AD368904AC002D11C5 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 43C436AB368904AC002D11C5 /* veilidtools-tests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 43C436AA368904AC002D11C5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C436BC368904AD002D11C5 /* LaunchScreen.storyboard in Resources */, + 43C436B9368904AD002D11C5 /* Assets.xcassets in Resources */, + 43C436B7368904AC002D11C5 /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 43C436C336893020002D11C5 /* Cargo Build */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Cargo Build"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "../../../../../scripts/ios_build.sh veilid_tools --features veilid_tools_ios_tests,rt-tokio\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 43C436A8368904AC002D11C5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 43C436B4368904AC002D11C5 /* ViewController.swift in Sources */, + 43C436B0368904AC002D11C5 /* AppDelegate.swift in Sources */, + 43C436B2368904AC002D11C5 /* SceneDelegate.swift in Sources */, + 4317C6BD3694A676009C717F /* (null) in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 43C436B5368904AC002D11C5 /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 43C436B6368904AC002D11C5 /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 43C436BA368904AD002D11C5 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 43C436BB368904AD002D11C5 /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 43C436BE368904AD002D11C5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 43C436BF368904AD002D11C5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD)"; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 43C436C1368904AD002D11C5 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ZJPQSFX5MW; + INFOPLIST_FILE = "veilidtools-tests/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ""; + "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "-L../../../../../target/lipo-ios/debug", + "-lveilid_tools", + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( + "-L../../../../../target/lipo-ios-sim/debug", + "-lveilid_tools", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.veilid.veilidtools-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "veilidtools-tests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 43C436C2368904AD002D11C5 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ZJPQSFX5MW; + INFOPLIST_FILE = "veilidtools-tests/Info.plist"; + IPHONEOS_DEPLOYMENT_TARGET = 12.3; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ""; + "OTHER_LDFLAGS[sdk=iphoneos*]" = ( + "-L../../../../../target/lipo-ios/release", + "-lveilid_tools", + ); + "OTHER_LDFLAGS[sdk=iphonesimulator*]" = ( + "-L../../../../../target/lipo-ios-sim/release", + "-lveilid_tools", + ); + PRODUCT_BUNDLE_IDENTIFIER = "com.veilid.veilidtools-tests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OBJC_BRIDGING_HEADER = "veilidtools-tests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 43C436A7368904AC002D11C5 /* Build configuration list for PBXProject "veilidtools-tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43C436BE368904AD002D11C5 /* Debug */, + 43C436BF368904AD002D11C5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 43C436C0368904AD002D11C5 /* Build configuration list for PBXNativeTarget "veilidtools-tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 43C436C1368904AD002D11C5 /* Debug */, + 43C436C2368904AD002D11C5 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 43C436A4368904AC002D11C5 /* Project object */; +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/xcshareddata/xcschemes/veilidtools-tests.xcscheme b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/xcshareddata/xcschemes/veilidtools-tests.xcscheme new file mode 100644 index 00000000..ec28aa85 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests.xcodeproj/xcshareddata/xcschemes/veilidtools-tests.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/AppDelegate.swift b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/AppDelegate.swift new file mode 100644 index 00000000..7b8b8e2b --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/AppDelegate.swift @@ -0,0 +1,37 @@ +// +// AppDelegate.swift +// veilidtools-tests +// +// Created by JSmith on 6/27/21. +// + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + + var window : UIWindow? + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + return true + } + + // MARK: UISceneSession Lifecycle + @available(iOS 13.0, *) + func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { + // Called when a new scene session is being created. + // Use this method to select a configuration to create the new scene with. + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } + + @available(iOS 13.0, *) + func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { + // Called when the user discards a scene session. + // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. + // Use this method to release any resources that were specific to the discarded scenes, as they will not return. + } + + +} + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AccentColor.colorset/Contents.json b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..eb878970 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AppIcon.appiconset/Contents.json b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..9221b9bb --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "20x20" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "29x29" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "40x40" + }, + { + "idiom" : "iphone", + "scale" : "2x", + "size" : "60x60" + }, + { + "idiom" : "iphone", + "scale" : "3x", + "size" : "60x60" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "20x20" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "29x29" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "40x40" + }, + { + "idiom" : "ipad", + "scale" : "1x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "76x76" + }, + { + "idiom" : "ipad", + "scale" : "2x", + "size" : "83.5x83.5" + }, + { + "idiom" : "ios-marketing", + "scale" : "1x", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/Contents.json b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/Contents.json new file mode 100644 index 00000000..73c00596 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/LaunchScreen.storyboard b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..3fd36928 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/Main.storyboard b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/Main.storyboard new file mode 100644 index 00000000..678f8974 --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Base.lproj/Main.storyboard @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Info.plist b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Info.plist new file mode 100644 index 00000000..5b531f7b --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/Info.plist @@ -0,0 +1,66 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + $(PRODUCT_MODULE_NAME).SceneDelegate + UISceneStoryboardFile + Main + + + + + UIApplicationSupportsIndirectInputEvents + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/SceneDelegate.swift b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/SceneDelegate.swift new file mode 100644 index 00000000..5ea6baef --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/SceneDelegate.swift @@ -0,0 +1,53 @@ +// +// SceneDelegate.swift +// veilidtools-tests +// +// Created by JSmith on 6/27/21. +// + +import UIKit + +@available(iOS 13.0, *) +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + + var window: UIWindow? + + + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { + // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. + // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. + // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). + guard let _ = (scene as? UIWindowScene) else { return } + } + + func sceneDidDisconnect(_ scene: UIScene) { + // Called as the scene is being released by the system. + // This occurs shortly after the scene enters the background, or when its session is discarded. + // Release any resources associated with this scene that can be re-created the next time the scene connects. + // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead). + } + + func sceneDidBecomeActive(_ scene: UIScene) { + // Called when the scene has moved from an inactive state to an active state. + // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. + } + + func sceneWillResignActive(_ scene: UIScene) { + // Called when the scene will move from an active state to an inactive state. + // This may occur due to temporary interruptions (ex. an incoming phone call). + } + + func sceneWillEnterForeground(_ scene: UIScene) { + // Called as the scene transitions from the background to the foreground. + // Use this method to undo the changes made on entering the background. + } + + func sceneDidEnterBackground(_ scene: UIScene) { + // Called as the scene transitions from the foreground to the background. + // Use this method to save data, release shared resources, and store enough scene-specific state information + // to restore the scene back to its current state. + } + + +} + diff --git a/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/ViewController.swift b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/ViewController.swift new file mode 100644 index 00000000..0f0cf31f --- /dev/null +++ b/veilid-tools/src/tests/ios/veilidtools-tests/veilidtools-tests/ViewController.swift @@ -0,0 +1,21 @@ +// +// ViewController.swift +// veilidtools-tests +// +// Created by JSmith on 6/27/21. +// + +import UIKit +import Darwin + +class ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + run_veilid_tools_tests() + exit(0) + } + + +} + diff --git a/veilid-tools/src/tests/mod.rs b/veilid-tools/src/tests/mod.rs new file mode 100644 index 00000000..e0b727a3 --- /dev/null +++ b/veilid-tools/src/tests/mod.rs @@ -0,0 +1,12 @@ +#[cfg(all(target_os = "android", feature = "veilid_tools_android_tests"))] +mod android; +pub mod common; +#[cfg(all(target_os = "ios", feature = "veilid_tools_ios_tests"))] +mod ios; +#[cfg(not(target_arch = "wasm32"))] +mod native; + +#[allow(unused_imports)] +use super::*; + +pub use common::*; diff --git a/veilid-tools/src/tests/native/mod.rs b/veilid-tools/src/tests/native/mod.rs new file mode 100644 index 00000000..75a481a8 --- /dev/null +++ b/veilid-tools/src/tests/native/mod.rs @@ -0,0 +1,100 @@ +//! Test suite for Native +#![cfg(not(target_arch = "wasm32"))] + +mod test_async_peek_stream; + +use super::*; + +////////////////////////////////////////////////////////////////////////////////// +// Allow access to tests from non cfg(test), as required for android and ios tests + +#[allow(dead_code)] +pub async fn run_all_tests() { + info!("TEST: exec_test_host_interface"); + test_host_interface::test_all().await; + info!("TEST: exec_test_async_peek_stream"); + test_async_peek_stream::test_all().await; + info!("TEST: exec_test_async_tag_lock"); + test_async_tag_lock::test_all().await; + + info!("Finished unit tests"); +} + +#[cfg(feature = "rt-tokio")] +#[allow(dead_code)] +pub fn block_on, T>(f: F) -> T { + let rt = tokio::runtime::Runtime::new().unwrap(); + rt.block_on(f) +} + +#[cfg(feature = "rt-async-std")] +#[allow(dead_code)] +pub fn block_on, T>(f: F) -> T { + async_std::task::block_on(f) +} + +/////////////////////////////////////////////////////////////////////////// +cfg_if! { + if #[cfg(test)] { + + use serial_test::serial; + use std::sync::Once; + + static SETUP_ONCE: Once = Once::new(); + + pub fn setup() { + SETUP_ONCE.call_once(|| { + + cfg_if! { + if #[cfg(feature = "tracing")] { + use tracing_subscriber::{filter, fmt, prelude::*}; + let mut filters = filter::Targets::new().with_default(filter::LevelFilter::TRACE); + for ig in DEFAULT_LOG_IGNORE_LIST { + filters = filters.with_target(ig, filter::LevelFilter::OFF); + } + let fmt_layer = fmt::layer(); + tracing_subscriber::registry() + .with(fmt_layer) + .with(filters) + .init(); + } else { + use simplelog::*; + let mut cb = ConfigBuilder::new(); + for ig in DEFAULT_LOG_IGNORE_LIST { + cb.add_filter_ignore_str(ig); + } + TestLogger::init(LevelFilter::Trace, cb.build()).unwrap(); + } + } + + }); + } + + #[test] + #[serial] + fn run_test_host_interface() { + setup(); + block_on(async { + test_host_interface::test_all().await; + }); + } + + #[test] + #[serial] + fn run_test_async_peek_stream() { + setup(); + block_on(async { + test_async_peek_stream::test_all().await; + }); + } + + #[test] + #[serial] + fn run_test_async_tag_lock() { + setup(); + block_on(async { + test_async_tag_lock::test_all().await; + }); + } + } +} diff --git a/veilid-core/src/tests/native/test_async_peek_stream.rs b/veilid-tools/src/tests/native/test_async_peek_stream.rs similarity index 99% rename from veilid-core/src/tests/native/test_async_peek_stream.rs rename to veilid-tools/src/tests/native/test_async_peek_stream.rs index f73f95b2..0797a4f8 100644 --- a/veilid-core/src/tests/native/test_async_peek_stream.rs +++ b/veilid-tools/src/tests/native/test_async_peek_stream.rs @@ -1,4 +1,4 @@ -use super::*; +use crate::*; cfg_if! { if #[cfg(feature="rt-async-std")] { diff --git a/veilid-core/src/xx/tick_task.rs b/veilid-tools/src/tick_task.rs similarity index 99% rename from veilid-core/src/xx/tick_task.rs rename to veilid-tools/src/tick_task.rs index 2d5d1e70..68ce4ddc 100644 --- a/veilid-core/src/xx/tick_task.rs +++ b/veilid-tools/src/tick_task.rs @@ -1,5 +1,5 @@ use super::*; -use crate::*; + use core::sync::atomic::{AtomicU64, Ordering}; use once_cell::sync::OnceCell; @@ -80,7 +80,7 @@ impl TickTask { } pub async fn tick(&self) -> Result<(), E> { - let now = intf::get_timestamp(); + let now = get_timestamp(); let last_timestamp_us = self.last_timestamp_us.load(Ordering::Acquire); if last_timestamp_us != 0u64 && now.saturating_sub(last_timestamp_us) < self.tick_period_us diff --git a/veilid-tools/src/timeout.rs b/veilid-tools/src/timeout.rs new file mode 100644 index 00000000..1ca2df26 --- /dev/null +++ b/veilid-tools/src/timeout.rs @@ -0,0 +1,32 @@ +use super::*; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + + pub async fn timeout(dur_ms: u32, f: F) -> Result + where + F: Future, + { + match select(Box::pin(sleep(dur_ms)), Box::pin(f)).await { + Either::Left((_x, _b)) => Err(TimeoutError()), + Either::Right((y, _a)) => Ok(y), + } + } + + } else { + + pub async fn timeout(dur_ms: u32, f: F) -> Result + where + F: Future, + { + cfg_if! { + if #[cfg(feature="rt-async-std")] { + async_std::future::timeout(Duration::from_millis(dur_ms as u64), f).await.map_err(|e| e.into()) + } else if #[cfg(feature="rt-tokio")] { + tokio::time::timeout(Duration::from_millis(dur_ms as u64), f).await.map_err(|e| e.into()) + } + } + } + + } +} diff --git a/veilid-core/src/xx/timeout_or.rs b/veilid-tools/src/timeout_or.rs similarity index 99% rename from veilid-core/src/xx/timeout_or.rs rename to veilid-tools/src/timeout_or.rs index 62786c36..43463e32 100644 --- a/veilid-core/src/xx/timeout_or.rs +++ b/veilid-tools/src/timeout_or.rs @@ -1,5 +1,5 @@ use super::*; -use cfg_if::*; + use core::fmt::{Debug, Display}; use core::result::Result; use std::error::Error; diff --git a/veilid-tools/src/timestamp.rs b/veilid-tools/src/timestamp.rs new file mode 100644 index 00000000..4b5c187e --- /dev/null +++ b/veilid-tools/src/timestamp.rs @@ -0,0 +1,25 @@ +use super::*; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use js_sys::Date; + + pub fn get_timestamp() -> u64 { + if is_browser() { + return (Date::now() * 1000.0f64) as u64; + } else { + panic!("WASM requires browser environment"); + } + } + } else { + use std::time::{SystemTime, UNIX_EPOCH}; + + pub fn get_timestamp() -> u64 { + match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(n) => n.as_micros() as u64, + Err(_) => panic!("SystemTime before UNIX_EPOCH!"), + } + } + + } +} diff --git a/veilid-core/src/xx/tools.rs b/veilid-tools/src/tools.rs similarity index 78% rename from veilid-core/src/xx/tools.rs rename to veilid-tools/src/tools.rs index 347de4f1..632336a1 100644 --- a/veilid-core/src/xx/tools.rs +++ b/veilid-tools/src/tools.rs @@ -1,8 +1,10 @@ -use crate::xx::*; -use alloc::string::ToString; +use super::*; + use std::io; use std::path::Path; +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + #[macro_export] macro_rules! assert_err { ($ex:expr) => { @@ -30,6 +32,40 @@ macro_rules! bail_io_error_other { }; } +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +pub fn system_boxed<'a, Out>( + future: impl Future + Send + 'a, +) -> SendPinBoxFutureLifetime<'a, Out> { + Box::pin(future) +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + + // xxx: for now until wasm threads are more stable, and/or we bother with web workers + pub fn get_concurrency() -> u32 { + 1 + } + + } else { + + pub fn get_concurrency() -> u32 { + std::thread::available_parallelism() + .map(|x| x.get()) + .unwrap_or_else(|e| { + warn!("unable to get concurrency defaulting to single core: {}", e); + 1 + }) as u32 + } + + } +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////// + pub fn split_port(name: &str) -> EyreResult<(String, Option)> { if let Some(split) = name.rfind(':') { let hoststr = &name[0..split]; @@ -239,7 +275,7 @@ cfg_if::cfg_if! { Ok(()) } } else { - pub fn ensure_file_private_owner>(_path: P) -> Result<(),String> + pub fn ensure_file_private_owner>(_path: P) -> Result<(), String> { Ok(()) } @@ -262,3 +298,33 @@ pub unsafe fn aligned_8_u8_vec_uninit(n_bytes: usize) -> Vec { cap_units * mem::size_of::(), ) } + +pub fn debug_backtrace() -> String { + let bt = backtrace::Backtrace::new(); + format!("{:?}", bt) +} + +pub fn debug_print_backtrace() { + if is_debug_backtrace_enabled() { + debug!("{}", debug_backtrace()); + } +} + +pub fn is_debug_backtrace_enabled() -> bool { + cfg_if! { + if #[cfg(debug_assertions)] { + cfg_if! { + if #[cfg(target_arch = "wasm32")] { + let rbenv = get_wasm_global_string_value("RUST_BACKTRACE").unwrap_or_default(); + } + else + { + let rbenv = std::env::var("RUST_BACKTRACE").unwrap_or_default(); + } + } + rbenv == "1" || rbenv == "full" + } else { + false + } + } +} diff --git a/veilid-core/src/intf/wasm/utils/mod.rs b/veilid-tools/src/wasm.rs similarity index 52% rename from veilid-core/src/intf/wasm/utils/mod.rs rename to veilid-tools/src/wasm.rs index fcec19f7..1a54aee5 100644 --- a/veilid-core/src/intf/wasm/utils/mod.rs +++ b/veilid-tools/src/wasm.rs @@ -1,8 +1,7 @@ -#![cfg(target_arch = "wasm32")] - -use crate::xx::*; +use super::*; use core::sync::atomic::{AtomicI8, Ordering}; use js_sys::{global, Reflect}; +use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { @@ -22,28 +21,35 @@ pub fn is_browser() -> bool { return cache != 0; } - let res = Reflect::has(&global().as_ref(), &"window".into()).unwrap_or_default(); + let res = Reflect::has(&global().as_ref(), &"navigator".into()).unwrap_or_default(); CACHE.store(res as i8, Ordering::Relaxed); res } -// pub fn is_browser_https() -> bool { -// static CACHE: AtomicI8 = AtomicI8::new(-1); -// let cache = CACHE.load(Ordering::Relaxed); -// if cache != -1 { -// return cache != 0; -// } +pub fn is_browser_https() -> bool { + static CACHE: AtomicI8 = AtomicI8::new(-1); + let cache = CACHE.load(Ordering::Relaxed); + if cache != -1 { + return cache != 0; + } -// let res = js_sys::eval("window.location.protocol === 'https'") -// .map(|res| res.is_truthy()) -// .unwrap_or_default(); + let res = js_sys::eval("self.location.protocol === 'https'") + .map(|res| res.is_truthy()) + .unwrap_or_default(); -// CACHE.store(res as i8, Ordering::Relaxed); + CACHE.store(res as i8, Ordering::Relaxed); -// res -// } + res +} + +pub fn get_wasm_global_string_value>(key: K) -> Option { + let Ok(v) = Reflect::get(&global().as_ref(), &JsValue::from_str(key.as_ref())) else { + return None; + }; + v.as_string() +} #[derive(ThisError, Debug, Clone, Eq, PartialEq)] #[error("JsValue error")] diff --git a/veilid-tools/tests/web.rs b/veilid-tools/tests/web.rs new file mode 100644 index 00000000..ae522f3e --- /dev/null +++ b/veilid-tools/tests/web.rs @@ -0,0 +1,45 @@ +//! Test suite for the Web and headless browsers. +#![cfg(target_arch = "wasm32")] + +use veilid_tools::tests::*; +use veilid_tools::*; + +use wasm_bindgen_test::*; + +wasm_bindgen_test_configure!(run_in_browser); + +extern crate wee_alloc; +#[global_allocator] +static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; + +static SETUP_ONCE: Once = Once::new(); +pub fn setup() -> () { + SETUP_ONCE.call_once(|| { + console_error_panic_hook::set_once(); + cfg_if! { + if #[cfg(feature = "tracing")] { + let mut builder = tracing_wasm::WASMLayerConfigBuilder::new(); + builder.set_report_logs_in_timings(false); + builder.set_max_level(Level::TRACE); + builder.set_console_config(tracing_wasm::ConsoleConfig::ReportWithConsoleColor); + tracing_wasm::set_as_global_default_with_config(builder.build()); + } else { + wasm_logger::init(wasm_logger::Config::default()); + } + } + }); +} + +#[wasm_bindgen_test] +async fn run_test_host_interface() { + setup(); + + test_host_interface::test_all().await; +} + +#[wasm_bindgen_test] +async fn run_test_async_tag_lock() { + setup(); + + test_async_tag_lock::test_all().await; +} diff --git a/veilid-tools/webdriver.json b/veilid-tools/webdriver.json new file mode 100644 index 00000000..c2d6865e --- /dev/null +++ b/veilid-tools/webdriver.json @@ -0,0 +1,15 @@ +{ + "moz:firefoxOptions": { + "prefs": { + "media.navigator.streams.fake": true, + "media.navigator.permission.disabled": true + }, + "args": [] + }, + "goog:chromeOptions": { + "args": [ + "--use-fake-device-for-media-stream", + "--use-fake-ui-for-media-stream" + ] + } +} diff --git a/veilid-wasm/Cargo.toml b/veilid-wasm/Cargo.toml index 758f4f88..2d21b445 100644 --- a/veilid-wasm/Cargo.toml +++ b/veilid-wasm/Cargo.toml @@ -25,7 +25,7 @@ serde_json = "^1" serde = "^1" lazy_static = "^1" send_wrapper = "^0" -futures-util = { version = "^0", default_features = false, features = ["alloc"] } +futures-util = { version = "^0" } data-encoding = { version = "^2" } gloo-utils = { version = "^0", features = ["serde"] } diff --git a/veilid-wasm/src/lib.rs b/veilid-wasm/src/lib.rs index e49bb8e6..33c965aa 100644 --- a/veilid-wasm/src/lib.rs +++ b/veilid-wasm/src/lib.rs @@ -7,20 +7,20 @@ extern crate alloc; use alloc::string::String; use alloc::sync::Arc; use alloc::*; -use core::any::{Any, TypeId}; use core::cell::RefCell; +use core::fmt::Debug; use futures_util::FutureExt; use gloo_utils::format::JsValueSerdeExt; use js_sys::*; use lazy_static::*; use send_wrapper::*; use serde::*; -use tracing::*; use tracing_subscriber::prelude::*; use tracing_subscriber::*; use tracing_wasm::{WASMLayerConfigBuilder, *}; -use veilid_core::xx::*; +use veilid_core::tools::*; use veilid_core::*; +use wasm_bindgen::prelude::*; use wasm_bindgen_futures::*; // Allocator @@ -28,17 +28,18 @@ extern crate wee_alloc; #[global_allocator] static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; -static SETUP_ONCE: Once = Once::new(); -pub fn setup() -> () { - SETUP_ONCE.call_once(|| {}); -} - // API Singleton lazy_static! { static ref VEILID_API: SendWrapper>> = SendWrapper::new(RefCell::new(None)); static ref FILTERS: SendWrapper>> = SendWrapper::new(RefCell::new(BTreeMap::new())); + static ref ROUTING_CONTEXTS: SendWrapper>> = + SendWrapper::new(RefCell::new(BTreeMap::new())); + static ref TABLE_DBS: SendWrapper>> = + SendWrapper::new(RefCell::new(BTreeMap::new())); + static ref TABLE_DB_TRANSACTIONS: SendWrapper>> = + SendWrapper::new(RefCell::new(BTreeMap::new())); } fn get_veilid_api() -> Result { @@ -54,25 +55,21 @@ fn take_veilid_api() -> Result(val: T) -> String { - serde_json::to_string(&val).expect("failed to serialize json value") -} - -pub fn deserialize_json( - arg: &str, -) -> Result { - serde_json::from_str(arg).map_err(|e| veilid_core::VeilidAPIError::ParseError { - message: e.to_string(), - value: String::new(), - }) -} - -pub fn to_json(val: T) -> JsValue { +// JSON Helpers for WASM +pub fn to_json(val: T) -> JsValue { JsValue::from_str(&serialize_json(val)) } -pub fn from_json(val: JsValue) -> Result { +pub fn to_jsvalue(val: T) -> JsValue +where + JsValue: From, +{ + JsValue::from(val) +} + +pub fn from_json( + val: JsValue, +) -> Result { let s = val .as_string() .ok_or_else(|| veilid_core::VeilidAPIError::ParseError { @@ -86,25 +83,32 @@ pub fn from_json(val: JsValue) -> Result = Result; const APIRESULT_UNDEFINED: APIResult<()> = APIResult::Ok(()); -pub fn wrap_api_future(future: F) -> Promise +pub fn wrap_api_future_json(future: F) -> Promise where F: Future> + 'static, - T: Serialize + 'static, + T: Serialize + Debug + 'static, { - future_to_promise(future.map(|res| { - res.map(|v| { - if TypeId::of::<()>() == v.type_id() { - JsValue::UNDEFINED - } else { - to_json(v) - } - }) - .map_err(|e| to_json(e)) - })) + future_to_promise(future.map(|res| res.map(|v| to_json(v)).map_err(|e| to_json(e)))) +} + +pub fn wrap_api_future_plain(future: F) -> Promise +where + F: Future> + 'static, + JsValue: From, + T: 'static, +{ + future_to_promise(future.map(|res| res.map(|v| to_jsvalue(v)).map_err(|e| to_json(e)))) +} + +pub fn wrap_api_future_void(future: F) -> Promise +where + F: Future> + 'static, +{ + future_to_promise(future.map(|res| res.map(|_| JsValue::UNDEFINED).map_err(|e| to_json(e)))) } ///////////////////////////////////////// -// WASM-specific cofnig +// WASM-specific #[derive(Debug, Deserialize, Serialize)] pub struct VeilidWASMConfigLoggingPerformance { @@ -131,6 +135,13 @@ pub struct VeilidWASMConfig { pub logging: VeilidWASMConfigLogging, } +#[derive(Debug, Deserialize, Serialize)] +pub struct VeilidKeyBlob { + pub key: veilid_core::DHTKey, + #[serde(with = "veilid_core::json_as_base64")] + pub blob: Vec, +} + // WASM Bindings #[wasm_bindgen()] @@ -138,48 +149,54 @@ pub fn initialize_veilid_wasm() { console_error_panic_hook::set_once(); } +static SETUP_ONCE: Once = Once::new(); #[wasm_bindgen()] pub fn initialize_veilid_core(platform_config: String) { - let platform_config: VeilidWASMConfig = veilid_core::deserialize_json(&platform_config) - .expect("failed to deserialize platform config json"); + SETUP_ONCE.call_once(|| { + let platform_config: VeilidWASMConfig = veilid_core::deserialize_json(&platform_config) + .expect("failed to deserialize platform config json"); - // Set up subscriber and layers - let subscriber = Registry::default(); - let mut layers = Vec::new(); - let mut filters = (*FILTERS).borrow_mut(); + // Set up subscriber and layers + let subscriber = Registry::default(); + let mut layers = Vec::new(); + let mut filters = (*FILTERS).borrow_mut(); - // Performance logger - if platform_config.logging.performance.enabled { - let filter = - veilid_core::VeilidLayerFilter::new(platform_config.logging.performance.level, None); - let layer = WASMLayer::new( - WASMLayerConfigBuilder::new() - .set_report_logs_in_timings(platform_config.logging.performance.logs_in_timings) - .set_console_config(if platform_config.logging.performance.logs_in_console { - ConsoleConfig::ReportWithConsoleColor - } else { - ConsoleConfig::NoReporting - }) - .build(), - ) - .with_filter(filter.clone()); - filters.insert("performance", filter); - layers.push(layer.boxed()); - }; + // Performance logger + if platform_config.logging.performance.enabled { + let filter = veilid_core::VeilidLayerFilter::new( + platform_config.logging.performance.level, + None, + ); + let layer = WASMLayer::new( + WASMLayerConfigBuilder::new() + .set_report_logs_in_timings(platform_config.logging.performance.logs_in_timings) + .set_console_config(if platform_config.logging.performance.logs_in_console { + ConsoleConfig::ReportWithConsoleColor + } else { + ConsoleConfig::NoReporting + }) + .build(), + ) + .with_filter(filter.clone()); + filters.insert("performance", filter); + layers.push(layer.boxed()); + }; - // API logger - if platform_config.logging.api.enabled { - let filter = veilid_core::VeilidLayerFilter::new(platform_config.logging.api.level, None); - let layer = veilid_core::ApiTracingLayer::get().with_filter(filter.clone()); - filters.insert("api", filter); - layers.push(layer.boxed()); - } + // API logger + if platform_config.logging.api.enabled { + let filter = + veilid_core::VeilidLayerFilter::new(platform_config.logging.api.level, None); + let layer = veilid_core::ApiTracingLayer::get().with_filter(filter.clone()); + filters.insert("api", filter); + layers.push(layer.boxed()); + } - let subscriber = subscriber.with(layers); - subscriber - .try_init() - .map_err(|e| format!("failed to initialize logging: {}", e)) - .expect("failed to initalize WASM platform"); + let subscriber = subscriber.with(layers); + subscriber + .try_init() + .map_err(|e| format!("failed to initialize logging: {}", e)) + .expect("failed to initalize WASM platform"); + }); } #[wasm_bindgen()] @@ -202,13 +219,13 @@ pub fn change_log_level(layer: String, log_level: String) { #[wasm_bindgen()] pub fn startup_veilid_core(update_callback_js: Function, json_config: String) -> Promise { let update_callback_js = SendWrapper::new(update_callback_js); - wrap_api_future(async move { + wrap_api_future_void(async move { let update_callback = Arc::new(move |update: VeilidUpdate| { let _ret = match Function::call1(&update_callback_js, &JsValue::UNDEFINED, &to_json(update)) { Ok(v) => v, Err(e) => { - error!("calling update callback failed: {:?}", e); + console_log(&format!("calling update callback failed: {:?}", e)); return; } }; @@ -226,16 +243,16 @@ pub fn startup_veilid_core(update_callback_js: Function, json_config: String) -> #[wasm_bindgen()] pub fn get_veilid_state() -> Promise { - wrap_api_future(async move { + wrap_api_future_json(async move { let veilid_api = get_veilid_api()?; let core_state = veilid_api.get_state().await?; - Ok(core_state) + APIResult::Ok(core_state) }) } #[wasm_bindgen()] pub fn attach() -> Promise { - wrap_api_future(async move { + wrap_api_future_void(async move { let veilid_api = get_veilid_api()?; veilid_api.attach().await?; APIRESULT_UNDEFINED @@ -244,7 +261,7 @@ pub fn attach() -> Promise { #[wasm_bindgen()] pub fn detach() -> Promise { - wrap_api_future(async move { + wrap_api_future_void(async move { let veilid_api = get_veilid_api()?; veilid_api.detach().await?; APIRESULT_UNDEFINED @@ -253,37 +270,476 @@ pub fn detach() -> Promise { #[wasm_bindgen()] pub fn shutdown_veilid_core() -> Promise { - wrap_api_future(async move { + wrap_api_future_void(async move { let veilid_api = take_veilid_api()?; veilid_api.shutdown().await; APIRESULT_UNDEFINED }) } +fn add_routing_context(routing_context: veilid_core::RoutingContext) -> u32 { + let mut next_id: u32 = 1; + let mut rc = (*ROUTING_CONTEXTS).borrow_mut(); + while rc.contains_key(&next_id) { + next_id += 1; + } + rc.insert(next_id, routing_context); + next_id +} + #[wasm_bindgen()] -pub fn debug(command: String) -> Promise { - wrap_api_future(async move { +pub fn routing_context() -> Promise { + wrap_api_future_plain(async move { let veilid_api = get_veilid_api()?; - let out = veilid_api.debug(command).await?; - Ok(out) + let routing_context = veilid_api.routing_context(); + let new_id = add_routing_context(routing_context); + APIResult::Ok(new_id) + }) +} + +#[wasm_bindgen()] +pub fn release_routing_context(id: u32) -> i32 { + let mut rc = (*ROUTING_CONTEXTS).borrow_mut(); + if rc.remove(&id).is_none() { + return 0; + } + return 1; +} + +#[wasm_bindgen()] +pub fn routing_context_with_privacy(id: u32) -> u32 { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let Ok(routing_context) = routing_context.clone().with_privacy() else { + return 0; + }; + let new_id = add_routing_context(routing_context); + new_id +} + +#[wasm_bindgen()] +pub fn routing_context_with_custom_privacy(id: u32, stability: String) -> u32 { + let stability: veilid_core::Stability = veilid_core::deserialize_json(&stability).unwrap(); + + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let Ok(routing_context) = routing_context.clone().with_custom_privacy(stability) else { + return 0; + }; + let new_id = add_routing_context(routing_context); + new_id +} + +#[wasm_bindgen()] +pub fn routing_context_with_sequencing(id: u32, sequencing: String) -> u32 { + let sequencing: veilid_core::Sequencing = veilid_core::deserialize_json(&sequencing).unwrap(); + + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return 0; + }; + let routing_context = routing_context.clone().with_sequencing(sequencing); + let new_id = add_routing_context(routing_context); + new_id +} + +#[wasm_bindgen()] +pub fn routing_context_app_call(id: u32, target: String, request: String) -> Promise { + let request: Vec = data_encoding::BASE64URL_NOPAD + .decode(request.as_bytes()) + .unwrap(); + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let routing_table = veilid_api.routing_table()?; + let rss = routing_table.route_spec_store(); + + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); + }; + routing_context.clone() + }; + + let target: DHTKey = + DHTKey::try_decode(&target).map_err(|e| VeilidAPIError::parse_error(e, &target))?; + + let target = if rss.get_remote_private_route(&target).is_some() { + veilid_core::Target::PrivateRoute(target) + } else { + veilid_core::Target::NodeId(veilid_core::NodeId::new(target)) + }; + + let answer = routing_context.app_call(target, request).await?; + let answer = data_encoding::BASE64URL_NOPAD.encode(&answer); + APIResult::Ok(answer) + }) +} + +#[wasm_bindgen()] +pub fn routing_context_app_message(id: u32, target: String, message: String) -> Promise { + let message: Vec = data_encoding::BASE64URL_NOPAD + .decode(message.as_bytes()) + .unwrap(); + wrap_api_future_void(async move { + let veilid_api = get_veilid_api()?; + let routing_table = veilid_api.routing_table()?; + let rss = routing_table.route_spec_store(); + + let routing_context = { + let rc = (*ROUTING_CONTEXTS).borrow(); + let Some(routing_context) = rc.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("routing_context_app_call", "id", id)); + }; + routing_context.clone() + }; + + let target: DHTKey = + DHTKey::try_decode(&target).map_err(|e| VeilidAPIError::parse_error(e, &target))?; + + let target = if rss.get_remote_private_route(&target).is_some() { + veilid_core::Target::PrivateRoute(target) + } else { + veilid_core::Target::NodeId(veilid_core::NodeId::new(target)) + }; + + routing_context.app_message(target, message).await?; + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen()] +pub fn new_private_route() -> Promise { + wrap_api_future_json(async move { + let veilid_api = get_veilid_api()?; + + let (key, blob) = veilid_api.new_private_route().await?; + + let keyblob = VeilidKeyBlob { key, blob }; + + APIResult::Ok(keyblob) + }) +} + +#[wasm_bindgen()] +pub fn new_custom_private_route(stability: String, sequencing: String) -> Promise { + let stability: veilid_core::Stability = veilid_core::deserialize_json(&stability).unwrap(); + let sequencing: veilid_core::Sequencing = veilid_core::deserialize_json(&sequencing).unwrap(); + + wrap_api_future_json(async move { + let veilid_api = get_veilid_api()?; + + let (key, blob) = veilid_api + .new_custom_private_route(stability, sequencing) + .await?; + + let keyblob = VeilidKeyBlob { key, blob }; + + APIResult::Ok(keyblob) + }) +} + +#[wasm_bindgen()] +pub fn import_remote_private_route(blob: String) -> Promise { + let blob: Vec = data_encoding::BASE64URL_NOPAD + .decode(blob.as_bytes()) + .unwrap(); + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + + let key = veilid_api.import_remote_private_route(blob)?; + + APIResult::Ok(key.encode()) + }) +} + +#[wasm_bindgen()] +pub fn release_private_route(key: String) -> Promise { + let key: veilid_core::DHTKey = veilid_core::deserialize_json(&key).unwrap(); + wrap_api_future_void(async move { + let veilid_api = get_veilid_api()?; + veilid_api.release_private_route(&key)?; + APIRESULT_UNDEFINED }) } #[wasm_bindgen()] pub fn app_call_reply(id: String, message: String) -> Promise { - wrap_api_future(async move { + let message: Vec = data_encoding::BASE64URL_NOPAD + .decode(message.as_bytes()) + .unwrap(); + wrap_api_future_void(async move { let id = match id.parse() { Ok(v) => v, Err(e) => { return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument(e, "id", id)) } }; - let message = data_encoding::BASE64URL_NOPAD - .decode(message.as_bytes()) - .map_err(|e| veilid_core::VeilidAPIError::invalid_argument(e, "message", message))?; let veilid_api = get_veilid_api()?; - let out = veilid_api.app_call_reply(id, message).await?; - Ok(out) + veilid_api.app_call_reply(id, message).await?; + APIRESULT_UNDEFINED + }) +} + +fn add_table_db(table_db: veilid_core::TableDB) -> u32 { + let mut next_id: u32 = 1; + let mut tdbs = (*TABLE_DBS).borrow_mut(); + while tdbs.contains_key(&next_id) { + next_id += 1; + } + tdbs.insert(next_id, table_db); + next_id +} + +#[wasm_bindgen()] +pub fn open_table_db(name: String, column_count: u32) -> Promise { + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let tstore = veilid_api.table_store()?; + let table_db = tstore + .open(&name, column_count) + .await + .map_err(veilid_core::VeilidAPIError::generic)?; + let new_id = add_table_db(table_db); + APIResult::Ok(new_id) + }) +} + +#[wasm_bindgen()] +pub fn release_table_db(id: u32) -> i32 { + let mut tdbs = (*TABLE_DBS).borrow_mut(); + if tdbs.remove(&id).is_none() { + return 0; + } + return 1; +} + +#[wasm_bindgen()] +pub fn delete_table_db(name: String) -> Promise { + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let tstore = veilid_api.table_store()?; + let deleted = tstore + .delete(&name) + .await + .map_err(veilid_core::VeilidAPIError::generic)?; + APIResult::Ok(deleted) + }) +} + +#[wasm_bindgen()] +pub fn table_db_get_column_count(id: u32) -> u32 { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&id) else { + return 0; + }; + let Ok(cc) = table_db.clone().get_column_count() else { + return 0; + }; + return cc; +} + +#[wasm_bindgen()] +pub fn table_db_get_keys(id: u32, col: u32) -> Option { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&id) else { + return None; + }; + let Ok(keys) = table_db.clone().get_keys(col) else { + return None; + }; + let keys: Vec = keys + .into_iter() + .map(|k| data_encoding::BASE64URL_NOPAD.encode(&k)) + .collect(); + let out = veilid_core::serialize_json(keys); + Some(out) +} + +fn add_table_db_transaction(tdbt: veilid_core::TableDBTransaction) -> u32 { + let mut next_id: u32 = 1; + let mut tdbts = (*TABLE_DB_TRANSACTIONS).borrow_mut(); + while tdbts.contains_key(&next_id) { + next_id += 1; + } + tdbts.insert(next_id, tdbt); + next_id +} + +#[wasm_bindgen()] +pub fn table_db_transact(id: u32) -> u32 { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&id) else { + return 0; + }; + let tdbt = table_db.clone().transact(); + let tdbtid = add_table_db_transaction(tdbt); + return tdbtid; +} + +#[wasm_bindgen()] +pub fn release_table_db_transaction(id: u32) -> i32 { + let mut tdbts = (*TABLE_DB_TRANSACTIONS).borrow_mut(); + if tdbts.remove(&id).is_none() { + return 0; + } + return 1; +} + +#[wasm_bindgen()] +pub fn table_db_transaction_commit(id: u32) -> Promise { + wrap_api_future_void(async move { + let tdbt = { + let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_commit", "id", id)); + }; + tdbt.clone() + }; + + tdbt.commit() + .await + .map_err(veilid_core::VeilidAPIError::generic)?; + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen()] +pub fn table_db_transaction_rollback(id: u32) -> Promise { + wrap_api_future_void(async move { + let tdbt = { + let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_rollback", "id", id)); + }; + tdbt.clone() + }; + + tdbt.rollback(); + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen()] +pub fn table_db_transaction_store(id: u32, col: u32, key: String, value: String) -> Promise { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode(key.as_bytes()) + .unwrap(); + let value: Vec = data_encoding::BASE64URL_NOPAD + .decode(value.as_bytes()) + .unwrap(); + wrap_api_future_void(async move { + let tdbt = { + let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_store", "id", id)); + }; + tdbt.clone() + }; + + tdbt.store(col, &key, &value); + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen()] +pub fn table_db_transaction_delete(id: u32, col: u32, key: String) -> Promise { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode(key.as_bytes()) + .unwrap(); + wrap_api_future_void(async move { + let tdbt = { + let tdbts = (*TABLE_DB_TRANSACTIONS).borrow(); + let Some(tdbt) = tdbts.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_transaction_delete", "id", id)); + }; + tdbt.clone() + }; + + tdbt.delete(col, &key); + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen()] +pub fn table_db_store(id: u32, col: u32, key: String, value: String) -> Promise { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode(key.as_bytes()) + .unwrap(); + let value: Vec = data_encoding::BASE64URL_NOPAD + .decode(value.as_bytes()) + .unwrap(); + wrap_api_future_void(async move { + let table_db = { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_store", "id", id)); + }; + table_db.clone() + }; + + table_db + .store(col, &key, &value) + .await + .map_err(veilid_core::VeilidAPIError::generic)?; + APIRESULT_UNDEFINED + }) +} + +#[wasm_bindgen()] +pub fn table_db_load(id: u32, col: u32, key: String) -> Promise { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode(key.as_bytes()) + .unwrap(); + wrap_api_future_plain(async move { + let table_db = { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_load", "id", id)); + }; + table_db.clone() + }; + + let out = table_db + .load(col, &key) + .map_err(veilid_core::VeilidAPIError::generic)?; + let out = out.map(|x| data_encoding::BASE64URL_NOPAD.encode(&x)); + APIResult::Ok(out) + }) +} + +#[wasm_bindgen()] +pub fn table_db_delete(id: u32, col: u32, key: String) -> Promise { + let key: Vec = data_encoding::BASE64URL_NOPAD + .decode(key.as_bytes()) + .unwrap(); + wrap_api_future_plain(async move { + let table_db = { + let table_dbs = (*TABLE_DBS).borrow(); + let Some(table_db) = table_dbs.get(&id) else { + return APIResult::Err(veilid_core::VeilidAPIError::invalid_argument("table_db_delete", "id", id)); + }; + table_db.clone() + }; + + let out = table_db + .delete(col, &key) + .await + .map_err(veilid_core::VeilidAPIError::generic)?; + APIResult::Ok(out) + }) +} + +#[wasm_bindgen()] +pub fn debug(command: String) -> Promise { + wrap_api_future_plain(async move { + let veilid_api = get_veilid_api()?; + let out = veilid_api.debug(command).await?; + APIResult::Ok(out) }) } diff --git a/veilid-wasm/tests/web.rs b/veilid-wasm/tests/web.rs index 8856b033..a986d126 100644 --- a/veilid-wasm/tests/web.rs +++ b/veilid-wasm/tests/web.rs @@ -61,7 +61,6 @@ fn init_callbacks() { case "network.dht.min_peer_refresh_time": return 2000000; case "network.dht.validate_dial_info_receipt_time": return 5000000; case "network.upnp": return false; - case "network.natpmp": return false; case "network.detect_address_changes": return true; case "network.address_filter": return true; case "network.restricted_nat_retries": return 3; diff --git a/veilid-wasm/wasm_build.sh b/veilid-wasm/wasm_build.sh index 9aa3cbf8..093bbcc1 100755 --- a/veilid-wasm/wasm_build.sh +++ b/veilid-wasm/wasm_build.sh @@ -1,6 +1,13 @@ #!/bin/bash SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +set -eo pipefail + +get_abs_filename() { + # $1 : relative filename + echo "$(cd "$(dirname "$1")" && pwd)/$(basename "$1")" +} + pushd $SCRIPTDIR &> /dev/null if [ -f /usr/local/opt/llvm/bin/llvm-dwarfdump ]; then @@ -15,7 +22,15 @@ else fi -if [[ "$1" == "debug" ]]; then +if [[ "$1" == "release" ]]; then + OUTPUTDIR=../target/wasm32-unknown-unknown/release/pkg + INPUTDIR=../target/wasm32-unknown-unknown/release + + cargo build --target wasm32-unknown-unknown --release + mkdir -p $OUTPUTDIR + wasm-bindgen --out-dir $OUTPUTDIR --target web --no-typescript $INPUTDIR/veilid_wasm.wasm + wasm-strip $OUTPUTDIR/veilid_wasm_bg.wasm +else OUTPUTDIR=../target/wasm32-unknown-unknown/debug/pkg INPUTDIR=../target/wasm32-unknown-unknown/debug @@ -24,14 +39,9 @@ if [[ "$1" == "debug" ]]; then wasm-bindgen --out-dir $OUTPUTDIR --target web --no-typescript --keep-debug --debug $INPUTDIR/veilid_wasm.wasm ./wasm-sourcemap.py $OUTPUTDIR/veilid_wasm_bg.wasm -o $OUTPUTDIR/veilid_wasm_bg.wasm.map --dwarfdump $DWARFDUMP # wasm-strip $OUTPUTDIR/veilid_wasm_bg.wasm -else - OUTPUTDIR=../target/wasm32-unknown-unknown/release/pkg - INPUTDIR=../target/wasm32-unknown-unknown/release - - cargo build --target wasm32-unknown-unknown --release - mkdir -p $OUTPUTDIR - wasm-bindgen --out-dir $OUTPUTDIR --target web --no-typescript $INPUTDIR/veilid_wasm.wasm - wasm-strip $OUTPUTDIR/veilid_wasm_bg.wasm fi popd &> /dev/null + +# Print for use with scripts +echo SUCCESS:OUTPUTDIR=$(get_abs_filename $OUTPUTDIR) \ No newline at end of file