diff --git a/Cargo.lock b/Cargo.lock index 04f9da4092b..edc741823f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,7 +11,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -24,15 +24,6 @@ dependencies = [ "psl-types", ] -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.1" @@ -46,7 +37,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", - "generic-array 0.14.7", + "generic-array 0.14.9", ] [[package]] @@ -133,9 +124,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "ammonia" -version = "4.1.1" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b346764dd0814805de8abf899fe03065bcee69bb1a4771c785817e39f3978f" +checksum = "17e913097e1a2124b46746c980134e8c954bc17a6a59bb3fde96f088d126dde6" dependencies = [ "cssparser", "html5ever", @@ -144,12 +135,6 @@ dependencies = [ "url", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -167,9 +152,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.19" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -182,9 +167,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" @@ -197,35 +182,35 @@ dependencies = [ [[package]] name = "anstyle-query" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.9" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "anyhow" -version = "1.0.98" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" dependencies = [ "derive_arbitrary", ] @@ -408,7 +393,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -446,27 +431,24 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.27" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddb939d66e4ae03cee6091612804ba446b12878410cfa17f785f4dd67d4014e8" +checksum = "5a89bce6054c720275ac2432fbba080a66a2106a44a1b804553930ca6909f4e0" dependencies = [ - "brotli", - "flate2", + "compression-codecs", + "compression-core", "futures-core", - "memchr", "pin-project-lite", "tokio", - "zstd", - "zstd-safe", ] [[package]] name = "async-lock" -version = "3.4.0" +version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "event-listener-strategy", "pin-project-lite", ] @@ -490,18 +472,18 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "async-trait" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -548,14 +530,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "autodoc" -version = "0.1.0" -dependencies = [ - "env_logger", - "log", -] - [[package]] name = "axum" version = "0.7.9" @@ -570,7 +544,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "itoa", "matchit", @@ -655,7 +629,7 @@ checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -673,7 +647,7 @@ dependencies = [ "cookie", "http 1.3.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "mime", "pretty_assertions", @@ -688,21 +662,6 @@ dependencies = [ "url", ] -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.2.0" @@ -824,11 +783,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -895,7 +854,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.9", ] [[package]] @@ -904,7 +863,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.9", ] [[package]] @@ -913,7 +872,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.9", ] [[package]] @@ -950,9 +909,9 @@ checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" [[package]] name = "brotli" -version = "8.0.1" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9991eea70ea4f293524138648e41ee89b0b2b12ddef3b255effa43c8056e0e0d" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -1024,11 +983,11 @@ checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" [[package]] name = "camino" -version = "1.1.10" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0da45bc31171d8d6960122e222a67740df867c1dd53b4d51caa297084c185cab" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" dependencies = [ - "serde", + "serde_core", ] [[package]] @@ -1048,7 +1007,7 @@ checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "thiserror 1.0.69", @@ -1062,10 +1021,10 @@ checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" dependencies = [ "camino", "cargo-platform", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -1082,10 +1041,11 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" [[package]] name = "cc" -version = "1.2.30" +version = "1.2.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" +checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -1097,15 +1057,15 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc3ba3c5408fae183329e0d1ac8f8eed3cb7b647590fd93e6d6288f6b09db0be" dependencies = [ - "phf", + "phf 0.11.3", "serde", ] [[package]] name = "cfg-if" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -1149,17 +1109,16 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -1202,9 +1161,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.41" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9" +checksum = "0c2cfd7bf8a6017ddaa4e32ffe7403d547790db06bd171c1c53926faab501623" dependencies = [ "clap_builder", "clap_derive", @@ -1212,9 +1171,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.41" +version = "4.5.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d" +checksum = "0a4c05b9e80c5ccd3a7ef080ad7b6ba7d6fc00a985b8b157197075677c82c7a0" dependencies = [ "anstream", "anstyle", @@ -1224,9 +1183,9 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.55" +version = "4.5.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5abde44486daf70c5be8b8f8f1b66c49f86236edf6fa2abadb4d961c4c6229a" +checksum = "2348487adcd4631696ced64ccdb40d38ac4d31cae7f2eec8817fcea1b9d1c43c" dependencies = [ "clap", ] @@ -1243,21 +1202,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.41" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "coarsetime" @@ -1288,15 +1247,35 @@ dependencies = [ [[package]] name = "comfy-table" -version = "7.1.4" +version = "7.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a65ebfec4fb190b6f90e944a817d60499ee0744e582530e2c9900a22e591d9a" +checksum = "b03b7db8e0b4b2fdad6c551e634134e99ec000e5c8c3b6856c65e8bbaded7a3b" dependencies = [ - "crossterm 0.28.1", + "crossterm 0.29.0", "unicode-segmentation", - "unicode-width 0.2.1", + "unicode-width 0.2.2", ] +[[package]] +name = "compression-codecs" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef8a506ec4b81c460798f572caead636d57d3d7e940f998160f52bd254bf2d23" +dependencies = [ + "brotli", + "compression-core", + "flate2", + "memchr", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47641d3deaf41fb1538ac1f54735925e275eaf3bf4d55c81b137fba797e5cbb" + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1308,15 +1287,15 @@ dependencies = [ [[package]] name = "console" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" +checksum = "b430743a6eb14e9764d4260d4c0d8123087d504eeb9c48f2b2a5e810dd369df4" dependencies = [ "encode_unicode", "libc", "once_cell", - "unicode-width 0.2.1", - "windows-sys 0.60.2", + "unicode-width 0.2.2", + "windows-sys 0.61.2", ] [[package]] @@ -1382,9 +1361,9 @@ checksum = "3618cccc083bb987a415d85c02ca6c9994ea5b44731ec28b9ecf09658655fba9" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -1501,7 +1480,7 @@ checksum = "a782b93fae93e57ca8ad3e9e994e784583f5933aeaaa5c80a545c4b437be2047" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -1525,7 +1504,7 @@ checksum = "e01c9214319017f6ebd8e299036e1f717fa9bb6724e758f7d6fb2477599d1a29" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -1690,14 +1669,15 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "crossterm_winapi", + "document-features", "parking_lot", - "rustix 0.38.44", + "rustix", "winapi", ] @@ -1722,7 +1702,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.9", "rand_core 0.6.4", "subtle 2.6.1", "zeroize", @@ -1734,7 +1714,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.9", "rand_core 0.6.4", "typenum", ] @@ -1758,7 +1738,7 @@ dependencies = [ "cssparser-macros", "dtoa-short", "itoa", - "phf", + "phf 0.11.3", "smallvec", ] @@ -1769,26 +1749,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "csv" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" +checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" dependencies = [ "csv-core", "itoa", "ryu", - "serde", + "serde_core", ] [[package]] name = "csv-core" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" +checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" dependencies = [ "memchr", ] @@ -1820,24 +1800,24 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.48" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2d5c8f48d9c0c23250e52b55e82a6ab4fdba6650c931f5a0a57a43abda812b" +checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc" dependencies = [ "curl-sys", "libc", "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.10", + "socket2 0.6.1", "windows-sys 0.59.0", ] [[package]] name = "curl-sys" -version = "0.4.82+curl-8.14.1" +version = "0.4.83+curl-8.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d63638b5ec65f1a4ae945287b3fd035be4554bbaf211901159c9a2a74fb5be" +checksum = "5830daf304027db10c82632a464879d46a3f7c4ba17a31592657ad16c719b483" dependencies = [ "cc", "libc", @@ -1873,7 +1853,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -1921,7 +1901,7 @@ dependencies = [ "schemars 0.8.22", "serde", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -1958,7 +1938,7 @@ dependencies = [ "cosmwasm-std", "cw-storage-plus", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "thiserror 1.0.69", ] @@ -2010,8 +1990,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -2025,7 +2015,21 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.106", + "syn 2.0.107", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.107", ] [[package]] @@ -2034,9 +2038,20 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2078,6 +2093,47 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.107", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.17", +] + [[package]] name = "delegate-display" version = "3.0.0" @@ -2089,7 +2145,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2105,12 +2161,12 @@ dependencies = [ [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071" dependencies = [ "powerfmt", - "serde", + "serde_core", ] [[package]] @@ -2126,13 +2182,13 @@ dependencies = [ [[package]] name = "derive_arbitrary" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2150,10 +2206,10 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ - "darling", + "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2163,7 +2219,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2192,7 +2248,7 @@ checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "unicode-xid", ] @@ -2204,7 +2260,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "unicode-xid", ] @@ -2229,7 +2285,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.9", ] [[package]] @@ -2262,7 +2318,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2273,7 +2329,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2293,6 +2349,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "document-features" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -2316,9 +2381,9 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "easy-addr" @@ -2326,7 +2391,7 @@ version = "0.1.0" dependencies = [ "cosmwasm-std", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2452,7 +2517,7 @@ dependencies = [ "crypto-bigint", "digest 0.10.7", "ff", - "generic-array 0.14.7", + "generic-array 0.14.9", "group", "hkdf", "pem-rfc7468", @@ -2488,30 +2553,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", -] - -[[package]] -name = "env_filter" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", + "syn 2.0.107", ] [[package]] @@ -2522,12 +2564,12 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2558,9 +2600,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "event-listener" -version = "5.4.0" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -2573,7 +2615,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.0", + "event-listener 5.4.1", "pin-project-lite", ] @@ -2585,7 +2627,7 @@ dependencies = [ "console_error_panic_hook", "js-sys", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "wasm-bindgen", "wasm-bindgen-futures", "wasm-storage", @@ -2618,7 +2660,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2654,16 +2696,22 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + [[package]] name = "fixedbitset" version = "0.4.2" @@ -2672,9 +2720,9 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" +checksum = "dc5a4e564e38c699f2880d3fda590bedc2e69f3f84cd48b457bd892ce61d0aa9" dependencies = [ "crc32fast", "miniz_oxide", @@ -2715,9 +2763,9 @@ checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] @@ -2848,7 +2896,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -2887,20 +2935,6 @@ version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" -[[package]] -name = "generator" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827" -dependencies = [ - "cc", - "cfg-if", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "generic-array" version = "0.12.4" @@ -2912,9 +2946,9 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "serde", "typenum", @@ -2931,21 +2965,21 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "js-sys", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", "wasm-bindgen", ] @@ -2959,17 +2993,11 @@ dependencies = [ "polyval", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "gloo-net" @@ -3064,7 +3092,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.10.0", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -3073,9 +3101,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", @@ -3083,7 +3111,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.10.0", + "indexmap 2.12.0", "slab", "tokio", "tokio-util", @@ -3092,12 +3120,13 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -3114,6 +3143,15 @@ dependencies = [ "serde_json", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -3141,22 +3179,28 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.4" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", "foldhash", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + [[package]] name = "hashlink" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.4", + "hashbrown 0.15.5", ] [[package]] @@ -3196,6 +3240,16 @@ dependencies = [ "http 1.3.1", ] +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.4.1" @@ -3246,18 +3300,18 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "idna", "ipnet", "once_cell", "rand 0.9.2", "ring", - "rustls 0.23.29", - "thiserror 2.0.12", + "rustls 0.23.33", + "thiserror 2.0.17", "tinyvec", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tracing", "url", "webpki-roots 0.26.11", @@ -3278,11 +3332,11 @@ dependencies = [ "parking_lot", "rand 0.9.2", "resolv-conf", - "rustls 0.23.29", + "rustls 0.23.33", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tracing", "webpki-roots 0.26.11", ] @@ -3453,9 +3507,9 @@ checksum = "f58b778a5761513caf593693f8951c97a5b610841e754788400f32102eefdff1" [[package]] name = "humantime" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "humantime-serde" @@ -3493,20 +3547,22 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", - "h2 0.4.11", + "futures-core", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", + "pin-utils", "smallvec", "tokio", "want", @@ -3533,14 +3589,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.3.1", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", - "rustls 0.23.29", + "rustls 0.23.33", "rustls-pki-types", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.2", + "webpki-roots 1.0.3", ] [[package]] @@ -3549,7 +3605,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "pin-project-lite", "tokio", @@ -3558,9 +3614,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.15" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" +checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ "base64 0.22.1", "bytes", @@ -3569,12 +3625,12 @@ dependencies = [ "futures-util", "http 1.3.1", "http-body 1.0.1", - "hyper 1.6.0", + "hyper 1.7.0", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.5.10", + "socket2 0.6.1", "tokio", "tower-service", "tracing", @@ -3582,9 +3638,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -3698,9 +3754,9 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", @@ -3725,7 +3781,7 @@ checksum = "0ab604ee7085efba6efc65e4ebca0e9533e3aff6cb501d7d77b211e3a781c6d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -3759,9 +3815,9 @@ dependencies = [ [[package]] name = "indenter" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" [[package]] name = "indexed_db_futures" @@ -3778,7 +3834,7 @@ dependencies = [ "js-sys", "sealed", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "wasm-bindgen", "wasm-bindgen-futures", @@ -3794,7 +3850,7 @@ dependencies = [ "macroific", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -3810,13 +3866,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.16.0", "serde", + "serde_core", ] [[package]] @@ -3827,7 +3884,7 @@ checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" dependencies = [ "console", "portable-atomic", - "unicode-width 0.2.1", + "unicode-width 0.2.2", "unit-prefix", "vt100", "web-time", @@ -3860,7 +3917,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", - "generic-array 0.14.7", + "generic-array 0.14.9", ] [[package]] @@ -3903,17 +3960,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "io-uring" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" -dependencies = [ - "bitflags 2.9.1", - "cfg-if", - "libc", -] - [[package]] name = "ip_network" version = "0.4.1" @@ -4023,45 +4069,21 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "jiff" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" -dependencies = [ - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde", -] - -[[package]] -name = "jiff-static" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "jobserver" -version = "0.1.33" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305" dependencies = [ "once_cell", "wasm-bindgen", @@ -4089,7 +4111,7 @@ dependencies = [ "serde", "serde_json", "superboring", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -4181,9 +4203,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libm" @@ -4193,11 +4215,11 @@ checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libredox" -version = "0.1.6" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4488594b9328dee448adb906d8b126d9b7deb7cf5c22161ee591610bb1be83c0" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "libc", "redox_syscall", ] @@ -4227,15 +4249,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" - -[[package]] -name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "lioness" @@ -4255,34 +4271,26 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" +[[package]] +name = "litrs" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5e54036fe321fd421e10d732f155734c4e4afd610dd556d9a82833ab3ee0bed" + [[package]] name = "lock_api" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "loom" -version = "0.7.2" +version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "tracing", - "tracing-subscriber", -] +checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" [[package]] name = "lru-slab" @@ -4316,7 +4324,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -4328,7 +4336,7 @@ dependencies = [ "proc-macro2", "quote", "sealed", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -4341,9 +4349,15 @@ dependencies = [ "macroific_core", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] +[[package]] +name = "managed" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca88d725a0a943b096803bd34e73a4437208b6077654cc4ecb2947a5f91618d" + [[package]] name = "maplit" version = "1.0.2" @@ -4369,16 +4383,16 @@ checksum = "ac84fd3f360fcc43dc5f5d186f02a94192761a080e8bc58621ad4d12296a58cf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "matchers" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "regex-automata 0.1.10", + "regex-automata", ] [[package]] @@ -4399,9 +4413,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.5" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "memoffset" @@ -4451,6 +4465,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -4461,19 +4476,19 @@ checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.48.0", ] [[package]] name = "mio" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -4491,7 +4506,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "url", @@ -4516,6 +4531,26 @@ dependencies = [ "tracing", ] +[[package]] +name = "mixtcp" +version = "0.0.1" +dependencies = [ + "bytes", + "dirs", + "nym-bin-common", + "nym-ip-packet-requests", + "nym-sdk", + "reqwest 0.12.24", + "rustls 0.23.33", + "serde_json", + "smoltcp", + "thiserror 2.0.17", + "tokio", + "tracing", + "tracing-subscriber", + "webpki-roots 1.0.3", +] + [[package]] name = "mock_instant" version = "0.6.0" @@ -4524,23 +4559,22 @@ checksum = "dce6dd36094cac388f119d2e9dc82dc730ef91c32a6222170d630e5414b956e6" [[package]] name = "moka" -version = "0.12.10" +version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" dependencies = [ "async-lock", "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", - "event-listener 5.4.0", + "equivalent", + "event-listener 5.4.1", "futures-util", - "loom", "parking_lot", "portable-atomic", "rustc_version 0.4.1", "smallvec", "tagptr", - "thiserror 1.0.69", "uuid", ] @@ -4591,7 +4625,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55e5bda7ca0f9ac5e75b5debac3b75e29a8ac8e2171106a2c3bb466389a8dd83" dependencies = [ "anyhow", - "bitflags 2.9.1", + "bitflags 2.10.0", "byteorder", "libc", "log", @@ -4657,7 +4691,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg-if", "libc", ] @@ -4668,7 +4702,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "cfg-if", "cfg_aliases", "libc", @@ -4740,6 +4774,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -4880,9 +4923,9 @@ dependencies = [ "pin-project", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.22", + "reqwest 0.12.24", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_json", "sha2 0.10.9", @@ -4890,7 +4933,7 @@ dependencies = [ "tempfile", "tendermint", "test-with", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -4936,7 +4979,7 @@ dependencies = [ "sha2 0.10.9", "tendermint", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "ts-rs", @@ -4968,8 +5011,8 @@ dependencies = [ "nym-service-provider-requests-common", "nym-validator-client", "nym-wireguard-types", - "semver 1.0.26", - "thiserror 2.0.12", + "semver 1.0.27", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -4989,11 +5032,11 @@ dependencies = [ "nym-sphinx", "nym-wireguard-types", "rand 0.8.5", - "semver 1.0.26", + "semver 1.0.27", "serde", "sha2 0.10.9", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", ] @@ -5014,7 +5057,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "url", "zeroize", ] @@ -5123,7 +5166,7 @@ dependencies = [ "serde_json", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "toml 0.8.23", @@ -5160,7 +5203,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tungstenite", @@ -5174,6 +5217,7 @@ version = "1.1.15" dependencies = [ "async-trait", "base64 0.22.1", + "bincode", "bs58", "cfg-if", "clap", @@ -5182,7 +5226,7 @@ dependencies = [ "gloo-timers", "http-body-util", "humantime", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-util", "nym-bandwidth-controller", "nym-client-core-config-types", @@ -5213,11 +5257,12 @@ dependencies = [ "sha2 0.10.9", "si-scale", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", "tokio-tungstenite", + "tokio-util", "tokio_with_wasm", "tracing", "tungstenite 0.20.1", @@ -5240,7 +5285,7 @@ dependencies = [ "nym-sphinx-params", "nym-statistics-common", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "url", ] @@ -5255,7 +5300,7 @@ dependencies = [ "nym-gateway-requests", "serde", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5275,7 +5320,7 @@ dependencies = [ "nym-task", "sqlx", "sqlx-pool-guard", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5298,7 +5343,8 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", + "tokio", "tokio_with_wasm", "tsify", "wasm-bindgen", @@ -5359,7 +5405,7 @@ dependencies = [ "serde", "sha2 0.10.9", "subtle 2.6.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -5372,7 +5418,7 @@ dependencies = [ "log", "nym-network-defaults", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "toml 0.8.23", "url", ] @@ -5389,7 +5435,7 @@ dependencies = [ "nym-ip-packet-requests", "nym-sdk", "pnet_packet", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -5407,7 +5453,7 @@ dependencies = [ "schemars 0.8.22", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", "vergen 8.3.1", ] @@ -5466,14 +5512,14 @@ dependencies = [ "nym-network-defaults", "nym-validator-client", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "sqlx", "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -5507,14 +5553,14 @@ dependencies = [ "nym-network-defaults", "nym-validator-client", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "sqlx", "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -5534,7 +5580,7 @@ dependencies = [ "nym-http-api-client", "nym-http-api-common", "nym-serde-helpers", - "reqwest 0.12.22", + "reqwest 0.12.24", "schemars 0.8.22", "serde", "serde_json", @@ -5564,7 +5610,7 @@ dependencies = [ "serde", "sqlx", "sqlx-pool-guard", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "zeroize", ] @@ -5582,7 +5628,7 @@ dependencies = [ "nym-credentials-interface", "nym-ecash-time", "nym-validator-client", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", ] @@ -5607,7 +5653,7 @@ dependencies = [ "nym-validator-client", "rand 0.8.5", "si-scale", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5632,7 +5678,7 @@ dependencies = [ "nym-validator-client", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "zeroize", ] @@ -5649,7 +5695,7 @@ dependencies = [ "serde", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "utoipa", ] @@ -5668,7 +5714,7 @@ dependencies = [ "ctr", "digest 0.10.7", "ed25519-dalek", - "generic-array 0.14.7", + "generic-array 0.14.9", "hkdf", "hmac", "jwt-simple", @@ -5680,7 +5726,7 @@ dependencies = [ "serde_bytes", "sha2 0.10.9", "subtle-encoding", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", "zeroize", ] @@ -5703,7 +5749,7 @@ dependencies = [ "serde", "serde_derive", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -5718,7 +5764,7 @@ dependencies = [ "cw-utils", "cw2", "nym-multisig-contract-common", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -5730,8 +5776,8 @@ dependencies = [ "nym-http-api-client", "nym-network-defaults", "nym-validator-client", - "semver 1.0.26", - "thiserror 2.0.12", + "semver 1.0.27", + "thiserror 2.0.17", "tokio", "tracing", "url", @@ -5743,9 +5789,9 @@ version = "0.1.0" dependencies = [ "nym-coconut-dkg-common", "nym-crypto", - "semver 1.0.26", + "semver 1.0.27", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "url", @@ -5764,10 +5810,10 @@ dependencies = [ name = "nym-exit-policy" version = "0.1.0" dependencies = [ - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tracing", "utoipa", ] @@ -5834,7 +5880,7 @@ dependencies = [ "rand 0.8.5", "serde", "sha2 0.10.9", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -5868,7 +5914,7 @@ dependencies = [ "rand 0.8.5", "serde", "si-scale", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -5918,7 +5964,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -5935,7 +5981,7 @@ dependencies = [ "anyhow", "bs58", "futures", - "generic-array 0.14.7", + "generic-array 0.14.9", "nym-compact-ecash", "nym-credentials", "nym-credentials-interface", @@ -5951,7 +5997,7 @@ dependencies = [ "serde_json", "strum", "subtle 2.6.1", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5970,7 +6016,7 @@ dependencies = [ "nym-statistics-common", "sqlx", "strum", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -5989,7 +6035,7 @@ dependencies = [ "nym-gateway-requests", "nym-sphinx", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -6006,7 +6052,7 @@ dependencies = [ "nym-ffi-shared", "nym-sdk", "nym-sphinx-anonymous-replies", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "uniffi", "uniffi_build", @@ -6042,12 +6088,12 @@ dependencies = [ "nym-http-api-common", "nym-network-defaults", "once_cell", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "serde_plain", "serde_yaml", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "url", @@ -6062,8 +6108,8 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "reqwest 0.12.22", - "syn 2.0.106", + "reqwest 0.12.24", + "syn 2.0.107", "uuid", ] @@ -6095,7 +6141,7 @@ version = "0.1.0" dependencies = [ "nym-credential-storage", "nym-credentials", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "zeroize", @@ -6121,7 +6167,7 @@ version = "0.1.0" dependencies = [ "log", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6133,7 +6179,7 @@ dependencies = [ "futures", "nym-ip-packet-requests", "nym-sdk", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -6151,7 +6197,7 @@ dependencies = [ "nym-sphinx", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6189,10 +6235,10 @@ dependencies = [ "nym-wireguard", "nym-wireguard-types", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tun", @@ -6208,7 +6254,7 @@ dependencies = [ "k256", "ledger-transport", "ledger-transport-hid", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6252,10 +6298,10 @@ dependencies = [ "nym-contracts-common", "rand_chacha 0.3.1", "schemars 0.8.22", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_repr", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "ts-rs", "utoipa", @@ -6281,7 +6327,7 @@ dependencies = [ "nym-task", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -6300,7 +6346,7 @@ dependencies = [ "cw4", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6342,7 +6388,7 @@ dependencies = [ "petgraph", "rand 0.8.5", "rand_chacha 0.3.1", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "tokio", @@ -6388,16 +6434,17 @@ dependencies = [ "publicsuffix", "rand 0.8.5", "regex", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "sqlx", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-tungstenite", + "tokio-util", "url", "zeroize", ] @@ -6471,7 +6518,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -6521,7 +6568,7 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "utoipa", @@ -6581,15 +6628,15 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "regex", - "reqwest 0.12.22", - "semver 1.0.26", + "reqwest 0.12.24", + "semver 1.0.27", "serde", "serde_json", "serde_json_path", "sqlx", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -6613,7 +6660,7 @@ dependencies = [ "bs58", "nym-credentials", "nym-crypto", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "time", @@ -6635,7 +6682,7 @@ dependencies = [ "rand_chacha 0.3.1", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "wasm-utils", ] @@ -6650,7 +6697,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "wasm-bindgen", @@ -6677,7 +6724,7 @@ dependencies = [ "snow", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -6724,7 +6771,7 @@ name = "nym-ordered-buffer" version = "0.1.0" dependencies = [ "log", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6741,7 +6788,7 @@ dependencies = [ "rand 0.8.5", "rayon", "sphinx-packet", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", "zeroize", ] @@ -6765,7 +6812,7 @@ dependencies = [ "nym-contracts-common", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6777,7 +6824,7 @@ dependencies = [ "cw-controllers", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", ] @@ -6794,7 +6841,7 @@ dependencies = [ "nym-registration-common", "nym-sdk", "nym-validator-client", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -6834,6 +6881,7 @@ dependencies = [ "nym-bandwidth-controller", "nym-bin-common", "nym-client-core", + "nym-config", "nym-credential-storage", "nym-credential-utils", "nym-credentials", @@ -6841,6 +6889,7 @@ dependencies = [ "nym-crypto", "nym-gateway-requests", "nym-http-api-client", + "nym-ip-packet-requests", "nym-network-defaults", "nym-ordered-buffer", "nym-service-providers-common", @@ -6853,12 +6902,13 @@ dependencies = [ "nym-topology", "nym-validator-client", "parking_lot", + "pnet_packet", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "tap", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -6888,7 +6938,7 @@ version = "0.1.0" dependencies = [ "bincode", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -6904,7 +6954,7 @@ dependencies = [ "nym-sphinx-anonymous-replies", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", ] @@ -6954,7 +7004,7 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "url", @@ -6984,12 +7034,13 @@ dependencies = [ "nym-validator-client", "pin-project", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.24", "schemars 0.8.22", "serde", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", + "tokio-util", "url", ] @@ -7020,13 +7071,15 @@ dependencies = [ "serde", "serde_json", "tap", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "nym-sphinx" version = "0.1.0" dependencies = [ + "bincode", + "log", "nym-crypto", "nym-metrics", "nym-mixnet-contract-common", @@ -7044,8 +7097,10 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_distr", - "thiserror 2.0.12", + "serde", + "thiserror 2.0.17", "tokio", + "tokio-util", "tracing", ] @@ -7053,7 +7108,7 @@ dependencies = [ name = "nym-sphinx-acknowledgements" version = "0.1.0" dependencies = [ - "generic-array 0.14.7", + "generic-array 0.14.9", "nym-crypto", "nym-pemstore", "nym-sphinx-addressing", @@ -7063,7 +7118,7 @@ dependencies = [ "nym-topology", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -7077,7 +7132,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7093,7 +7148,8 @@ dependencies = [ "nym-topology", "rand 0.8.5", "rand_chacha 0.3.1", - "thiserror 2.0.12", + "serde", + "thiserror 2.0.17", "tracing", "wasm-bindgen", ] @@ -7111,7 +7167,7 @@ dependencies = [ "nym-sphinx-types", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", "wasmtimer", ] @@ -7130,7 +7186,7 @@ dependencies = [ "nym-sphinx-types", "nym-topology", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7141,7 +7197,8 @@ dependencies = [ "nym-sphinx-anonymous-replies", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.12", + "serde", + "thiserror 2.0.17", ] [[package]] @@ -7154,7 +7211,7 @@ dependencies = [ "nym-sphinx-forwarding", "nym-sphinx-params", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7167,7 +7224,7 @@ dependencies = [ "nym-crypto", "nym-sphinx-types", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7176,7 +7233,7 @@ version = "0.1.0" dependencies = [ "nym-sphinx-addressing", "nym-sphinx-types", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7185,7 +7242,7 @@ version = "0.2.0" dependencies = [ "nym-outfox", "sphinx-packet", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -7237,7 +7294,7 @@ dependencies = [ "strum", "strum_macros", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "utoipa", @@ -7250,12 +7307,12 @@ version = "0.1.0" dependencies = [ "aes-gcm", "argon2", - "generic-array 0.14.7", + "generic-array 0.14.9", "getrandom 0.2.16", "rand 0.8.5", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "zeroize", ] @@ -7268,7 +7325,8 @@ dependencies = [ "futures", "log", "nym-test-utils", - "thiserror 2.0.12", + "serde", + "thiserror 2.0.17", "tokio", "tokio-util", "tracing", @@ -7315,10 +7373,10 @@ dependencies = [ "nym-sphinx-addressing", "nym-sphinx-types", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "tsify", @@ -7333,7 +7391,7 @@ dependencies = [ "etherparse", "log", "nym-wireguard-types", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tokio-tun", ] @@ -7354,7 +7412,7 @@ dependencies = [ "nym-mixnet-contract-common", "nym-validator-client", "nym-vesting-contract-common", - "reqwest 0.12.22", + "reqwest 0.12.24", "schemars 0.8.22", "serde", "serde_json", @@ -7362,7 +7420,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs", "url", "utoipa", @@ -7377,10 +7435,10 @@ dependencies = [ "jwt-simple", "nym-crypto", "nym-http-api-client", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", ] @@ -7420,12 +7478,12 @@ dependencies = [ "nym-serde-helpers", "nym-vesting-contract-common", "prost", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "sha2 0.10.9", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -7470,7 +7528,7 @@ dependencies = [ "serde_with", "sha2 0.10.9", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -7491,7 +7549,7 @@ dependencies = [ "nym-task", "nym-validator-client", "rand 0.8.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -7509,7 +7567,7 @@ dependencies = [ "nym-contracts-common", "nym-mixnet-contract-common", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs", ] @@ -7530,7 +7588,7 @@ dependencies = [ "serde", "serde-wasm-bindgen 0.6.5", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tsify", "wasm-bindgen", @@ -7582,7 +7640,7 @@ dependencies = [ "nym-node-metrics", "nym-task", "nym-wireguard-types", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7629,7 +7687,7 @@ dependencies = [ "nym-credentials-interface", "schemars 0.8.22", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "utoipa", ] @@ -7663,7 +7721,7 @@ dependencies = [ "nym-network-defaults", "rand 0.8.5", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "x25519-dalek", ] @@ -7685,12 +7743,12 @@ dependencies = [ "nym-bin-common", "nym-config", "nym-task", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", "sha2 0.10.9", "tar", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tracing", @@ -7712,11 +7770,11 @@ dependencies = [ "nym-task", "nym-validator-client", "nyxd-scraper", - "reqwest 0.12.22", + "reqwest 0.12.24", "schemars 0.8.22", "serde", "sqlx", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-util", @@ -7744,7 +7802,7 @@ dependencies = [ "sqlx", "tendermint", "tendermint-rpc", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -7755,32 +7813,23 @@ dependencies = [ [[package]] name = "objc2-core-foundation" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] [[package]] name = "objc2-io-kit" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ "libc", "objc2-core-foundation", ] -[[package]] -name = "object" -version = "0.36.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -7823,9 +7872,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.109" +version = "0.9.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" +checksum = "0a9f0075ba3c21b09f8e8b2026584b1d18d49388648f2fbbf3c97ea8deced8e2" dependencies = [ "cc", "libc", @@ -7985,9 +8034,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -7995,15 +8044,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -8072,26 +8121,25 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" +checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" dependencies = [ "memchr", - "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" +checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" dependencies = [ "pest", "pest_generator", @@ -8099,22 +8147,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" +checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "pest_meta" -version = "2.8.1" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" +checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" dependencies = [ "pest", "sha2 0.10.9", @@ -8127,7 +8175,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.10.0", + "indexmap 2.12.0", ] [[package]] @@ -8137,7 +8185,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_shared 0.13.1", + "serde", ] [[package]] @@ -8147,7 +8205,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", ] [[package]] @@ -8156,7 +8214,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared", + "phf_shared 0.11.3", "rand 0.8.5", ] @@ -8167,10 +8225,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -8182,6 +8240,15 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher 1.0.1", +] + [[package]] name = "pin-project" version = "1.1.10" @@ -8199,7 +8266,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -8293,7 +8360,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -8362,20 +8429,11 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" -[[package]] -name = "portable-atomic-util" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" -dependencies = [ - "portable-atomic", -] - [[package]] name = "postgres-protocol" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ff0abab4a9b844b93ef7b81f1efc0a366062aaef2cd702c76256b5dc075c54" +checksum = "fbef655056b916eb868048276cfd5d6a7dea4f81560dfd047f97c8c6fe3fcfd4" dependencies = [ "base64 0.22.1", "byteorder", @@ -8391,9 +8449,9 @@ dependencies = [ [[package]] name = "postgres-types" -version = "0.2.9" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613283563cd90e1dfc3518d548caee47e0e725455ed619881f5cf21f36de4b48" +checksum = "ef4605b7c057056dd35baeb6ac0c0338e4975b1f2bef0f65da953285eb007095" dependencies = [ "bytes", "fallible-iterator", @@ -8402,9 +8460,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" dependencies = [ "zerovec", ] @@ -8451,11 +8509,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ - "toml_edit", + "toml_edit 0.23.7", ] [[package]] @@ -8477,14 +8535,14 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" dependencies = [ "unicode-ident", ] @@ -8510,7 +8568,7 @@ dependencies = [ "memchr", "parking_lot", "protobuf", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -8533,7 +8591,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -8567,9 +8625,9 @@ dependencies = [ [[package]] name = "psl" -version = "2.1.126" +version = "2.1.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45f621acfbd2ca5670eee9a95270747dfa9a29e63e1937d7e6a1ac6994331966" +checksum = "6fd2e88c2c28854abe53489ce679f9dad712e2ac01a342dbeccae851cf0654d9" dependencies = [ "psl-types", ] @@ -8598,9 +8656,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quinn" -version = "0.11.8" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", @@ -8608,9 +8666,9 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls 0.23.29", - "socket2 0.5.10", - "thiserror 2.0.12", + "rustls 0.23.33", + "socket2 0.6.1", + "thiserror 2.0.17", "tokio", "tracing", "web-time", @@ -8618,20 +8676,20 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.12" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", - "getrandom 0.3.3", + "getrandom 0.3.4", "lru-slab", "rand 0.9.2", "ring", "rustc-hash", - "rustls 0.23.29", + "rustls 0.23.33", "rustls-pki-types", "slab", - "thiserror 2.0.12", + "thiserror 2.0.17", "tinyvec", "tracing", "web-time", @@ -8639,23 +8697,23 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.13" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.5.10", + "socket2 0.6.1", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.40" +version = "1.0.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" dependencies = [ "proc-macro2", ] @@ -8728,7 +8786,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", ] [[package]] @@ -8743,9 +8801,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -8753,9 +8811,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -8763,11 +8821,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.15" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8af0dde094006011e6a740d4879319439489813bd0bcdc7d821beaeeff48ec" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", ] [[package]] @@ -8778,72 +8836,57 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", -] - -[[package]] -name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" @@ -8888,9 +8931,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.22" +version = "0.12.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" +checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "async-compression", "base64 0.22.1", @@ -8900,7 +8943,7 @@ dependencies = [ "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-rustls 0.27.7", "hyper-util", "js-sys", @@ -8908,14 +8951,14 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.29", + "rustls 0.23.33", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.2", + "tokio-rustls 0.26.4", "tokio-util", "tower 0.5.2", "tower-http 0.6.6", @@ -8925,7 +8968,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.2", + "webpki-roots 1.0.3", ] [[package]] @@ -8934,14 +8977,14 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] name = "resolv-conf" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" +checksum = "6b3789b30bd25ba102de4beabd95d21ac45b69b1be7d14522bab988c526d6799" [[package]] name = "rfc6979" @@ -9030,9 +9073,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.7.2" +version = "8.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "025908b8682a26ba8d12f6f2d66b987584a4a87bc024abc5bbc12553a8cd178a" +checksum = "fb44e1917075637ee8c7bcb865cf8830e3a92b5b1189e44e3a0ab5a0d5be314b" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -9041,22 +9084,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.7.2" +version = "8.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6065f1a4392b71819ec1ea1df1120673418bf386f50de1d6f54204d836d4349c" +checksum = "382499b49db77a7c19abd2a574f85ada7e9dbe125d5d1160fa5cad7c4cf71fc9" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.106", + "syn 2.0.107", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.7.2" +version = "8.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6cc0c81648b20b70c491ff8cce00c1c3b223bb8ed2b5d41f0e54c6c4c0a3594" +checksum = "21fcbee55c2458836bcdbfffb6ec9ba74bbc23ca7aa6816015a3dd2c4d8fc185" dependencies = [ "sha2 0.10.9", "walkdir", @@ -9078,12 +9121,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "rustc-demangle" -version = "0.1.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" - [[package]] name = "rustc-hash" version = "2.1.1" @@ -9105,33 +9142,20 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "semver 1.0.26", -] - -[[package]] -name = "rustix" -version = "0.38.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" -dependencies = [ - "bitflags 2.9.1", - "errno", - "libc", - "linux-raw-sys 0.4.15", - "windows-sys 0.59.0", + "semver 1.0.27", ] [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] @@ -9162,15 +9186,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.29" +version = "0.23.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" +checksum = "751e04a496ca00bb97a5e043158d23d66b5aabf2e1d5aa2a0aaebb1aafe6f82c" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.4", + "rustls-webpki 0.103.7", "subtle 2.6.1", "zeroize", ] @@ -9251,9 +9275,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "e10b3f4191e8a80e6b43eebabfac91e5dcecebb27a71f04e820c47ec41d314bf" dependencies = [ "ring", "rustls-pki-types", @@ -9262,9 +9286,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.21" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -9283,11 +9307,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.27" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -9337,15 +9361,9 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.29.1", - "syn 2.0.106", + "syn 2.0.107", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -9369,7 +9387,7 @@ checksum = "1783eabc414609e28a5ba76aee5ddd52199f7107a0b24c2e9746a1ecc34a683d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -9390,7 +9408,7 @@ checksum = "22f968c5ea23d555e670b449c1c5e7b2fc399fdaec1d304a17cd48e288abc107" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -9401,7 +9419,7 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", - "generic-array 0.14.7", + "generic-array 0.14.9", "pkcs8", "serdect 0.2.0", "subtle 2.6.1", @@ -9432,7 +9450,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "core-foundation", "core-foundation-sys", "libc", @@ -9441,9 +9459,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.14.0" +version = "2.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" dependencies = [ "core-foundation-sys", "libc", @@ -9460,11 +9478,12 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" dependencies = [ "serde", + "serde_core", ] [[package]] @@ -9475,10 +9494,11 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ + "serde_core", "serde_derive", ] @@ -9515,22 +9535,32 @@ dependencies = [ [[package]] name = "serde_bytes" -version = "0.11.17" +version = "0.11.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" dependencies = [ "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -9541,7 +9571,7 @@ checksum = "e578a843d40b4189a4d66bba51d7684f57da5bd7c304c64e14bd63efbef49509" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -9552,19 +9582,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "serde_json" -version = "1.0.141" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -9580,7 +9611,7 @@ dependencies = [ "serde_json", "serde_json_path_core", "serde_json_path_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -9592,7 +9623,7 @@ dependencies = [ "inventory", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -9614,17 +9645,18 @@ checksum = "aafbefbe175fa9bf03ca83ef89beecff7d2a95aaacd5732325b90ac8c3bd7b90" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "serde_path_to_error" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", "serde", + "serde_core", ] [[package]] @@ -9644,7 +9676,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -9670,19 +9702,18 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" +checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.12.0", "schemars 0.9.0", "schemars 1.0.4", - "serde", - "serde_derive", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -9690,14 +9721,14 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" +checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" dependencies = [ - "darling", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -9706,7 +9737,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.12.0", "itoa", "ryu", "serde", @@ -9812,9 +9843,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -9879,6 +9910,22 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +[[package]] +name = "smoltcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad095989c1533c1c266d9b1e8d70a1329dd3723c3edac6d03bbd67e7bf6f4bb" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "cfg-if", + "defmt 0.3.100", + "heapless", + "libc", + "log", + "managed", +] + [[package]] name = "snafu" version = "0.7.5" @@ -9929,12 +9976,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] @@ -10007,24 +10054,24 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener 5.4.0", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.4", + "hashbrown 0.15.5", "hashlink", - "indexmap 2.10.0", + "indexmap 2.12.0", "log", "memchr", "once_cell", "percent-encoding", - "rustls 0.23.29", + "rustls 0.23.33", "serde", "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "tokio-stream", @@ -10043,7 +10090,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -10066,7 +10113,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.106", + "syn 2.0.107", "tokio", "url", ] @@ -10079,7 +10126,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.1", + "bitflags 2.10.0", "byteorder", "bytes", "chrono", @@ -10091,7 +10138,7 @@ dependencies = [ "futures-core", "futures-io", "futures-util", - "generic-array 0.14.7", + "generic-array 0.14.9", "hex", "hkdf", "hmac", @@ -10109,7 +10156,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "whoami", @@ -10136,7 +10183,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.9.1", + "bitflags 2.10.0", "byteorder", "chrono", "crc", @@ -10161,7 +10208,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "whoami", @@ -10187,7 +10234,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tracing", "url", @@ -10205,9 +10252,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -10223,7 +10270,7 @@ checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", - "phf_shared", + "phf_shared 0.11.3", "precomputed-hash", "serde", ] @@ -10235,7 +10282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ "phf_generator", - "phf_shared", + "phf_shared 0.11.3", "proc-macro2", "quote", ] @@ -10275,7 +10322,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -10331,9 +10378,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.106" +version = "2.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b" dependencies = [ "proc-macro2", "quote", @@ -10363,14 +10410,14 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "sysinfo" -version = "0.37.0" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07cec4dc2d2e357ca1e610cfb07de2fa7a10fc3e9fe89f72545f3d244ea87753" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ "libc", "memchr", @@ -10426,15 +10473,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.20.0" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand 2.3.0", - "getrandom 0.3.3", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.8", - "windows-sys 0.59.0", + "rustix", + "windows-sys 0.61.2", ] [[package]] @@ -10512,7 +10559,7 @@ dependencies = [ "pin-project", "rand 0.8.5", "reqwest 0.11.27", - "semver 1.0.26", + "semver 1.0.27", "serde", "serde_bytes", "serde_json", @@ -10560,7 +10607,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -10596,7 +10643,7 @@ dependencies = [ "serde_json", "sqlx", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tokio", "toml 0.8.23", @@ -10625,11 +10672,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -10640,18 +10687,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -10687,9 +10734,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -10705,15 +10752,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -10741,9 +10788,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" dependencies = [ "tinyvec_macros", ] @@ -10756,41 +10803,38 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.47.1" +version = "1.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", - "mio 1.0.4", + "mio 1.1.0", "parking_lot", "pin-project-lite", "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2 0.6.1", "tokio-macros", "tracing", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "tokio-postgres" -version = "0.7.13" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c95d533c83082bb6490e0189acaa0bbeef9084e60471b696ca6988cd0541fb0" +checksum = "2b40d66d9b2cfe04b628173409368e58247e8eddbbd3b0e6c6ba1d09f20f6c9e" dependencies = [ "async-trait", "byteorder", @@ -10801,12 +10845,12 @@ dependencies = [ "log", "parking_lot", "percent-encoding", - "phf", + "phf 0.13.1", "pin-project-lite", "postgres-protocol", "postgres-types", "rand 0.9.2", - "socket2 0.5.10", + "socket2 0.6.1", "tokio", "tokio-util", "whoami", @@ -10835,11 +10879,11 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.29", + "rustls 0.23.33", "tokio", ] @@ -10897,15 +10941,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.15" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", "futures-sink", "futures-util", - "hashbrown 0.15.4", "pin-project-lite", "slab", "tokio", @@ -10932,7 +10975,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37e04c1865c281139e5ccf633cb9f76ffdaabeebfe53b703984cf82878e2aabb" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -10952,8 +10995,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -10965,20 +11008,50 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.12.0", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.11", "toml_write", "winnow", ] +[[package]] +name = "toml_edit" +version = "0.23.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +dependencies = [ + "indexmap 2.12.0", + "toml_datetime 0.7.3", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +dependencies = [ + "winnow", +] + [[package]] name = "toml_write" version = "0.1.2" @@ -10996,11 +11069,11 @@ dependencies = [ "axum", "base64 0.22.1", "bytes", - "h2 0.4.11", + "h2 0.4.12", "http 1.3.1", "http-body 1.0.1", "http-body-util", - "hyper 1.6.0", + "hyper 1.7.0", "hyper-timeout", "hyper-util", "percent-encoding", @@ -11058,7 +11131,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "async-compression", - "bitflags 2.9.1", + "bitflags 2.10.0", "bytes", "futures-core", "futures-util", @@ -11084,7 +11157,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.10.0", "bytes", "futures-util", "http 1.3.1", @@ -11128,7 +11201,7 @@ checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -11153,9 +11226,9 @@ dependencies = [ [[package]] name = "tracing-indicatif" -version = "0.3.11" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c714cc8fc46db04fcfddbd274c6ef59bebb1b435155984e7c6e89c3ce66f200" +checksum = "04d4e11e0e27acef25a47f27e9435355fecdc488867fa2bc90e75b0700d2823d" dependencies = [ "indicatif", "tracing", @@ -11201,14 +11274,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.19" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" dependencies = [ "matchers", - "nu-ansi-term", + "nu-ansi-term 0.50.3", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -11235,7 +11308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" dependencies = [ "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -11244,7 +11317,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ec6adcab41b1391b08a308cc6302b79f8095d1673f6947c2dc65ffb028b0b2d" dependencies = [ - "nu-ansi-term", + "nu-ansi-term 0.46.0", "tracing-core", "tracing-log 0.1.4", "tracing-subscriber", @@ -11292,7 +11365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6" dependencies = [ "lazy_static", - "thiserror 2.0.12", + "thiserror 2.0.17", "ts-rs-macros", ] @@ -11319,7 +11392,7 @@ checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "termcolor", ] @@ -11346,7 +11419,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals 0.28.0", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -11393,9 +11466,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "ucd-trie" @@ -11417,9 +11490,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" [[package]] name = "unicode-normalization" @@ -11450,9 +11523,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -11462,9 +11535,9 @@ checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "uniffi" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b334fd69b3cf198b63616c096aabf9820ab21ed9b2aa1367ddd4b411068bf520" +checksum = "c6d968cb62160c11f2573e6be724ef8b1b18a277aededd17033f8a912d73e2b4" dependencies = [ "anyhow", "camino", @@ -11479,9 +11552,9 @@ dependencies = [ [[package]] name = "uniffi_bindgen" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ff0132b533483cf19abb30bba5c72c24d9f3e4d9a2ff71cb3e22e73899fd46e" +checksum = "f6b39ef1acbe1467d5d210f274fae344cb6f8766339330cb4c9688752899bf6b" dependencies = [ "anyhow", "askama", @@ -11491,7 +11564,7 @@ dependencies = [ "glob", "goblin", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.12.0", "once_cell", "serde", "tempfile", @@ -11505,9 +11578,9 @@ dependencies = [ [[package]] name = "uniffi_build" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d84d607076008df3c32dd2100ee4e727269f11d3faa35691af70d144598f666" +checksum = "6683e6b665423cddeacd89a3f97312cf400b2fb245a26f197adaf65c45d505b2" dependencies = [ "anyhow", "camino", @@ -11516,9 +11589,9 @@ dependencies = [ [[package]] name = "uniffi_core" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53e3b997192dc15ef1778c842001811ec7f241a093a693ac864e1fc938e64fa9" +checksum = "c2d990b553d6b9a7ee9c3ae71134674739913d52350b56152b0e613595bb5a6f" dependencies = [ "anyhow", "bytes", @@ -11528,22 +11601,22 @@ dependencies = [ [[package]] name = "uniffi_internal_macros" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64bec2f3a33f2f08df8150e67fa45ba59a2ca740bf20c1beb010d4d791f9a1b" +checksum = "04f4f224becf14885c10e6e400b95cc4d1985738140cb194ccc2044563f8a56b" dependencies = [ "anyhow", - "indexmap 2.10.0", + "indexmap 2.12.0", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "uniffi_macros" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8708716d2582e4f3d7e9f320290b5966eb951ca421d7630571183615453efc" +checksum = "b481d385af334871d70904e6a5f129be7cd38c18fcf8dd8fd1f646b426a56d58" dependencies = [ "camino", "fs-err", @@ -11551,16 +11624,16 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.106", + "syn 2.0.107", "toml 0.5.11", "uniffi_meta", ] [[package]] name = "uniffi_meta" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d226fc167754ce548c5ece9828c8a06f03bf1eea525d2659ba6bd648bd8e2f3" +checksum = "10f817868a3b171bb7bf259e882138d104deafde65684689b4694c846d322491" dependencies = [ "anyhow", "siphasher 0.3.11", @@ -11570,22 +11643,22 @@ dependencies = [ [[package]] name = "uniffi_pipeline" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b925b6421df15cf4bedee27714022cd9626fb4d7eee0923522a608b274ba4371" +checksum = "4b147e133ad7824e32426b90bc41fda584363563f2ba747f590eca1fd6fd14e6" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.10.0", + "indexmap 2.12.0", "tempfile", "uniffi_internal_macros", ] [[package]] name = "uniffi_udl" -version = "0.29.3" +version = "0.29.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c42649b721df759d9d4692a376b82b62ce3028ec9fc466f4780fb8cdf728996" +checksum = "caed654fb73da5abbc7a7e9c741532284532ba4762d6fe5071372df22a41730a" dependencies = [ "anyhow", "textwrap", @@ -11623,9 +11696,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.4" +version = "2.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" dependencies = [ "form_urlencoded", "idna", @@ -11663,7 +11736,7 @@ version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.12.0", "serde", "serde_json", "utoipa-gen", @@ -11678,7 +11751,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.106", + "syn 2.0.107", "uuid", ] @@ -11717,7 +11790,7 @@ checksum = "268d76aaebb80eba79240b805972e52d7d410d4bcc52321b951318b0f440cd60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -11728,17 +11801,17 @@ checksum = "382673bda1d05c85b4550d32fd4192ccd4cffe9a908543a0795d1e7682b36246" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "utoipauto-core", ] [[package]] name = "uuid" -version = "1.17.0" +version = "1.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" +checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" dependencies = [ - "getrandom 0.3.3", + "getrandom 0.3.4", "js-sys", "serde", "wasm-bindgen", @@ -11902,12 +11975,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] @@ -11922,40 +11995,41 @@ version = "0.12.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" dependencies = [ - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "7e038d41e478cc73bae0ff9b36c60cff1c98b8f38f8d7e8061e79ee63608ac5c" dependencies = [ "cfg-if", "js-sys", @@ -11966,9 +12040,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11976,31 +12050,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-bindgen-test" -version = "0.3.50" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66c8d5e33ca3b6d9fa3b4676d774c5778031d27a578c2b007f905acf816152c3" +checksum = "4e381134e148c1062f965a42ed1f5ee933eef2927c3f70d1812158f711d39865" dependencies = [ "js-sys", "minicov", @@ -12011,13 +12085,13 @@ dependencies = [ [[package]] name = "wasm-bindgen-test-macro" -version = "0.3.50" +version = "0.3.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17d5042cc5fa009658f9a7333ef24291b1291a25b6382dd68862a7f3b969f69b" +checksum = "b673bca3298fe582aeef8352330ecbad91849f85090805582400850f8270a2e8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -12043,7 +12117,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "time", "tsify", "url", @@ -12065,7 +12139,7 @@ dependencies = [ "nym-store-cipher", "serde", "serde-wasm-bindgen 0.6.5", - "thiserror 2.0.12", + "thiserror 2.0.17", "wasm-bindgen", "wasm-utils", ] @@ -12101,9 +12175,9 @@ dependencies = [ [[package]] name = "wasmtimer" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8d49b5d6c64e8558d9b1b065014426f35c18de636895d24893dbbd329743446" +checksum = "1c598d6b99ea013e35844697fc4670d08339d5cda15588f193c6beedd12f644b" dependencies = [ "futures", "js-sys", @@ -12115,9 +12189,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9367c417a924a74cae129e6a2ae3b47fabb1f8995595ab474029da749a8be120" dependencies = [ "js-sys", "wasm-bindgen", @@ -12139,7 +12213,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57ffde1dc01240bdf9992e3205668b235e59421fd085e8a317ed98da0178d414" dependencies = [ - "phf", + "phf 0.11.3", "phf_codegen", "string_cache", "string_cache_codegen", @@ -12166,14 +12240,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.2", + "webpki-roots 1.0.3", ] [[package]] name = "webpki-roots" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" dependencies = [ "rustls-pki-types", ] @@ -12189,20 +12263,20 @@ dependencies = [ [[package]] name = "whoami" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" dependencies = [ - "redox_syscall", + "libredox", "wasite", "web-sys", ] [[package]] name = "widestring" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" [[package]] name = "winapi" @@ -12222,11 +12296,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -12244,7 +12318,7 @@ dependencies = [ "windows-collections", "windows-core", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -12265,7 +12339,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", - "windows-link", + "windows-link 0.1.3", "windows-result", "windows-strings", ] @@ -12277,30 +12351,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] [[package]] name = "windows-implement" -version = "0.60.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] name = "windows-interface" -version = "0.59.1" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -12309,6 +12383,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -12316,7 +12396,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -12325,7 +12405,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -12334,7 +12414,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -12379,7 +12459,16 @@ version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.53.2", + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", ] [[package]] @@ -12430,18 +12519,19 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.53.2" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", ] [[package]] @@ -12450,7 +12540,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -12473,9 +12563,9 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -12497,9 +12587,9 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -12521,9 +12611,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" [[package]] name = "windows_i686_gnullvm" @@ -12533,9 +12623,9 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -12557,9 +12647,9 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -12581,9 +12671,9 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -12605,9 +12695,9 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -12629,15 +12719,15 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.53.0" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.12" +version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" dependencies = [ "memchr", ] @@ -12653,13 +12743,10 @@ dependencies = [ ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags 2.9.1", -] +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "writeable" @@ -12690,12 +12777,12 @@ dependencies = [ [[package]] name = "xattr" -version = "1.5.1" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" dependencies = [ "libc", - "rustix 1.0.8", + "rustix", ] [[package]] @@ -12724,28 +12811,28 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.26" +version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -12765,15 +12852,15 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] @@ -12786,7 +12873,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -12802,9 +12889,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.2" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" dependencies = [ "yoke", "zerofrom", @@ -12819,7 +12906,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.106", + "syn 2.0.107", ] [[package]] @@ -12833,9 +12920,9 @@ dependencies = [ "crossbeam-utils", "displaydoc", "flate2", - "indexmap 2.10.0", + "indexmap 2.12.0", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.17", "zopfli", ] @@ -12854,9 +12941,9 @@ dependencies = [ "nym-crypto", "nym-http-api-client", "rand 0.8.5", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tsify", "uuid", @@ -12898,9 +12985,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.15+zstd.1.5.7" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", @@ -12913,10 +13000,10 @@ dependencies = [ "itertools 0.14.0", "nym-bin-common", "nym-http-api-client", - "reqwest 0.12.22", + "reqwest 0.12.24", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.17", "tokio", "tracing", "url", diff --git a/Cargo.toml b/Cargo.toml index 267c5607551..e3ddd96d2d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,7 +115,6 @@ members = [ "common/wireguard-private-metadata/tests", "common/wireguard-types", "common/zulip-client", - "documentation/autodoc", "gateway", "nym-api", "nym-api/nym-api-requests", @@ -124,7 +123,6 @@ members = [ "nym-credential-proxy/nym-credential-proxy", "nym-credential-proxy/nym-credential-proxy-requests", "nym-credential-proxy/vpn-api-lib-wasm", - "nym-ip-packet-client", "nym-network-monitor", "nym-node", "nym-node-status-api/nym-node-status-agent", @@ -142,6 +140,7 @@ members = [ "sdk/ffi/go", "sdk/ffi/shared", "sdk/rust/nym-sdk", + "mixtcp", "service-providers/common", "service-providers/ip-packet-router", "service-providers/network-requester", @@ -320,6 +319,7 @@ rand_seeder = "0.2.3" rayon = "1.5.1" regex = "1.10.6" reqwest = { version = "0.12.15", default-features = false } +rustls = { version = "0.23", default-features = false, features = ["std", "ring"] } rs_merkle = "1.5.0" safer-ffi = "0.1.13" schemars = "0.8.22" @@ -335,6 +335,7 @@ serde_yaml = "0.9.25" serde_plain = "1.0.2" sha2 = "0.10.9" si-scale = "0.2.3" +smoltcp = "0.12" snow = "0.9.6" sphinx-packet = "=0.6.0" sqlx = "0.8.6" @@ -378,6 +379,7 @@ uuid = "*" vergen = { version = "=8.3.1", default-features = false } vergen-gitcl = { version = "1.0.8", default-features = false } walkdir = "2" +webpki-roots = "1.0.2" x25519-dalek = "2.0.0" zeroize = "1.7.0" diff --git a/common/client-core/Cargo.toml b/common/client-core/Cargo.toml index fd1e82d7acc..308449e77eb 100644 --- a/common/client-core/Cargo.toml +++ b/common/client-core/Cargo.toml @@ -10,6 +10,7 @@ license.workspace = true [dependencies] async-trait = { workspace = true } +bincode = { workspace = true } base64 = { workspace = true } bs58 = { workspace = true } clap = { workspace = true, optional = true } @@ -25,6 +26,7 @@ sha2 = { workspace = true } si-scale = { workspace = true } thiserror = { workspace = true } url = { workspace = true, features = ["serde"] } +tokio-util = { workspace = true, features = ["codec"] } time = { workspace = true } tokio = { workspace = true, features = ["sync", "macros"] } tracing = { workspace = true } diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index af065b3dec5..455ef77942d 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -36,6 +36,7 @@ use crate::init::{ types::{GatewaySetup, InitialisationResult}, }; use futures::channel::mpsc; +use futures::SinkExt; use nym_bandwidth_controller::BandwidthController; use nym_client_core_config_types::{ForgetMe, RememberMe}; use nym_client_core_gateways_storage::{GatewayDetails, GatewaysDetailsStore}; @@ -66,7 +67,7 @@ use std::os::raw::c_int as RawFd; use std::path::Path; use std::sync::Arc; use time::OffsetDateTime; -use tokio::sync::mpsc::Sender; +use tokio_util::sync::{PollSendError, PollSender}; use url::Url; #[cfg(target_arch = "wasm32")] @@ -108,10 +109,7 @@ pub struct ClientInput { } impl ClientInput { - pub async fn send( - &self, - message: InputMessage, - ) -> Result<(), tokio::sync::mpsc::error::SendError> { + pub async fn send(&mut self, message: InputMessage) -> Result<(), PollSendError> { self.input_sender.send(message).await } } @@ -756,7 +754,7 @@ where config: &Config, user_agent: Option, client_stats_id: String, - input_sender: Sender, + input_sender: PollSender, shutdown_tracker: &ShutdownTracker, ) -> ClientStatsSender { tracing::info!("Starting statistics control..."); @@ -975,7 +973,7 @@ where &self.config, self.user_agent.clone(), generate_client_stats_id(*self_address.identity()), - input_sender.clone(), + tokio_util::sync::PollSender::new(input_sender.clone()), &shutdown_tracker.child_tracker(), ); @@ -1102,7 +1100,7 @@ where client_input: ClientInputStatus::AwaitingProducer { client_input: ClientInput { connection_command_sender: client_connection_tx, - input_sender, + input_sender: PollSender::new(input_sender), client_request_sender, }, }, diff --git a/common/client-core/src/client/inbound_messages.rs b/common/client-core/src/client/inbound_messages.rs index b74c9195bea..4dcd5ae0860 100644 --- a/common/client-core/src/client/inbound_messages.rs +++ b/common/client-core/src/client/inbound_messages.rs @@ -1,16 +1,26 @@ // Copyright 2020-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 - use nym_sphinx::addressing::clients::Recipient; use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::forwarding::packet::MixPacket; use nym_sphinx::params::PacketType; use nym_task::connections::TransmissionLane; +use serde::{Deserialize, Serialize}; +use std::convert::TryInto; +use tokio_util::{ + bytes::Buf, + bytes::BytesMut, + codec::{Decoder, Encoder}, +}; + +use crate::error::ClientCoreError; -pub type InputMessageSender = tokio::sync::mpsc::Sender; +pub type InputMessageSender = tokio_util::sync::PollSender; pub type InputMessageReceiver = tokio::sync::mpsc::Receiver; -#[derive(Debug)] +const LENGHT_ENCODING_PREFIX_SIZE: usize = 4; + +#[derive(Serialize, Deserialize, Debug)] pub enum InputMessage { /// Fire an already prepared mix packets into the network. /// No guarantees are made about it. For example no retransmssion @@ -65,6 +75,10 @@ pub enum InputMessage { } impl InputMessage { + pub fn simple(data: &[u8], recipient: Recipient) -> Self { + InputMessage::new_regular(recipient, data.to_vec(), TransmissionLane::General, None) + } + pub fn new_premade( msgs: Vec, lane: TransmissionLane, @@ -185,4 +199,71 @@ impl InputMessage { self.set_max_retransmissions(max_retransmissions); self } + #[allow(clippy::expect_used)] + pub fn serialized_size(&self) -> u64 { + bincode::serialized_size(self).expect("failed to get serialized InputMessage size") + + LENGHT_ENCODING_PREFIX_SIZE as u64 + } +} + +// TODO: Tests +pub struct AdressedInputMessageCodec(pub Recipient); + +impl Encoder<&[u8]> for AdressedInputMessageCodec { + type Error = ClientCoreError; + + fn encode(&mut self, item: &[u8], buf: &mut BytesMut) -> Result<(), Self::Error> { + let mut codec = InputMessageCodec; + let input_message = InputMessage::simple(item, self.0); + codec.encode(input_message, buf)?; + Ok(()) + } +} + +pub struct InputMessageCodec; + +impl Encoder for InputMessageCodec { + type Error = ClientCoreError; + + fn encode(&mut self, item: InputMessage, buf: &mut BytesMut) -> Result<(), Self::Error> { + #[allow(clippy::expect_used)] + let encoded = bincode::serialize(&item).expect("failed to serialize InputMessage"); + let encoded_len = encoded.len() as u32; + let mut encoded_with_len = encoded_len.to_le_bytes().to_vec(); + encoded_with_len.extend(encoded); + buf.reserve(encoded_with_len.len()); + buf.extend_from_slice(&encoded_with_len); + Ok(()) + } +} + +impl Decoder for InputMessageCodec { + type Item = InputMessage; + type Error = ClientCoreError; + + fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + if buf.len() < LENGHT_ENCODING_PREFIX_SIZE { + return Ok(None); + } + #[allow(clippy::expect_used)] + let len = u32::from_le_bytes( + buf[0..LENGHT_ENCODING_PREFIX_SIZE] + .try_into() + .expect("Could not coarce to array"), + ) as usize; + if buf.len() < len + LENGHT_ENCODING_PREFIX_SIZE { + return Ok(None); + } + + let decoded = match bincode::deserialize( + &buf[LENGHT_ENCODING_PREFIX_SIZE..len + LENGHT_ENCODING_PREFIX_SIZE], + ) { + Ok(decoded) => decoded, + Err(_) => return Ok(None), + }; + + buf.advance(len + LENGHT_ENCODING_PREFIX_SIZE); + + Ok(Some(decoded)) + } } diff --git a/common/client-core/src/client/mix_traffic/transceiver.rs b/common/client-core/src/client/mix_traffic/transceiver.rs index f749700ab2a..48955886973 100644 --- a/common/client-core/src/client/mix_traffic/transceiver.rs +++ b/common/client-core/src/client/mix_traffic/transceiver.rs @@ -13,7 +13,7 @@ use nym_validator_client::nyxd::contract_traits::DkgQueryClient; use std::fmt::Debug; use std::os::raw::c_int as RawFd; use thiserror::Error; -use tracing::{debug, error}; +use tracing::debug; #[cfg(not(target_arch = "wasm32"))] use futures::channel::oneshot; diff --git a/common/client-core/src/client/replies/reply_controller/requests.rs b/common/client-core/src/client/replies/reply_controller/requests.rs index d4a7014065b..4d2782ceba7 100644 --- a/common/client-core/src/client/replies/reply_controller/requests.rs +++ b/common/client-core/src/client/replies/reply_controller/requests.rs @@ -8,7 +8,6 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; use nym_sphinx::anonymous_replies::ReplySurbWithKeyRotation; use nym_task::connections::{ConnectionId, TransmissionLane}; use std::sync::Weak; -use tracing::error; pub(crate) fn new_control_channels() -> (ReplyControllerSender, ReplyControllerReceiver) { let (tx, rx) = mpsc::unbounded(); diff --git a/common/client-core/src/client/statistics_control.rs b/common/client-core/src/client/statistics_control.rs index dcfbd2e19c5..fd3d8a48ecd 100644 --- a/common/client-core/src/client/statistics_control.rs +++ b/common/client-core/src/client/statistics_control.rs @@ -17,7 +17,7 @@ #![warn(clippy::dbg_macro)] use crate::client::inbound_messages::{InputMessage, InputMessageSender}; -use futures::StreamExt; +use futures::{SinkExt, StreamExt}; use nym_client_core_config_types::StatsReporting; use nym_sphinx::addressing::Recipient; use nym_statistics_common::clients::{ diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index e5744667014..32688dfd4d2 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -441,7 +441,7 @@ mod tests { #[test] fn test_multiple_urls_prepared_for_retries() { - let urls = vec![ + let urls = [ Url::parse("https://api1.nym.com").unwrap(), Url::parse("https://api2.nym.com").unwrap(), Url::parse("https://api3.nym.com").unwrap(), diff --git a/common/client-core/src/lib.rs b/common/client-core/src/lib.rs index 1f2faf36597..e9f04940393 100644 --- a/common/client-core/src/lib.rs +++ b/common/client-core/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::::clone_from_slice`: please upgrade to generic-array 1.x - TODO use std::future::Future; #[cfg(all( diff --git a/common/client-core/surb-storage/src/lib.rs b/common/client-core/surb-storage/src/lib.rs index 079c213bbc7..ffd7ad85f6f 100644 --- a/common/client-core/surb-storage/src/lib.rs +++ b/common/client-core/surb-storage/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] // silences clippy warning: use of deprecated associated function `nym_crypto::generic_array::GenericArray::::from_exact_iter`: please upgrade to generic-array 1.x - TODO pub use backend::*; pub use combined::CombinedReplyStorage; pub use key_storage::SentReplyKeys; diff --git a/common/client-libs/validator-client/src/nym_api/mod.rs b/common/client-libs/validator-client/src/nym_api/mod.rs index 8c549491e96..c3bc21a78f9 100644 --- a/common/client-libs/validator-client/src/nym_api/mod.rs +++ b/common/client-libs/validator-client/src/nym_api/mod.rs @@ -273,6 +273,49 @@ pub trait NymApiClientExt: ApiClient { Ok(SkimmedNodesWithMetadata::new(nodes, metadata)) } + async fn get_all_basic_exit_assigned_nodes_with_metadata( + &self, + ) -> Result { + // Get all nodes that can act as exit gateways + let mut page = 0; + let res = self + .get_basic_exit_assigned_nodes_v2(false, Some(page), None, false) + .await?; + + let metadata = res.metadata; + let mut nodes = res.nodes.data; + + if res.nodes.pagination.total == nodes.len() { + return Ok(SkimmedNodesWithMetadata::new(nodes, metadata)); + } + + page += 1; + + loop { + let res = self + .get_basic_exit_assigned_nodes_v2(false, Some(page), None, false) + .await?; + + if !metadata.consistency_check(&res.metadata) { + return Err(NymAPIError::InternalResponseInconsistency { + url: self.api_url().clone(), + details: "Inconsistent paged metadata".to_string(), + }); + } + + nodes.append(&mut res.nodes.data.clone()); + + // Check if we've got all nodes + if nodes.len() >= res.nodes.pagination.total { + break; + } else { + page += 1; + } + } + + Ok(SkimmedNodesWithMetadata::new(nodes, metadata)) + } + async fn get_all_described_nodes(&self) -> Result, NymAPIError> { // TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere let mut page = 0; @@ -467,6 +510,47 @@ pub trait NymApiClientExt: ApiClient { .await } + /// retrieve basic information for nodes are capable of operating as an exit gateway + /// this includes legacy gateways and nym-nodes + #[instrument(level = "debug", skip(self))] + async fn get_basic_exit_assigned_nodes_v2( + &self, + no_legacy: bool, + page: Option, + per_page: Option, + use_bincode: bool, + ) -> Result, NymAPIError> { + let mut params = Vec::new(); + + if no_legacy { + params.push(("no_legacy", "true".to_string())) + } + + if let Some(page) = page { + params.push(("page", page.to_string())) + } + + if let Some(per_page) = per_page { + params.push(("per_page", per_page.to_string())) + } + + if use_bincode { + params.push(("output", "bincode".to_string())) + } + + self.get_response( + &[ + routes::V2_API_VERSION, + "unstable", + routes::NYM_NODES_ROUTES, + "skimmed", + "exit-gateways", + ], + ¶ms, + ) + .await + } + /// retrieve basic information for nodes that got assigned 'mixing' node in this epoch /// this includes legacy mixnodes and nym-nodes #[deprecated(note = "use get_basic_active_mixing_assigned_nodes_v2")] diff --git a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs index d06df5ca3bc..9622780091d 100644 --- a/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs +++ b/common/cosmwasm-smart-contracts/coconut-dkg/src/types.rs @@ -1,6 +1,8 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(clippy::derivable_impls)] +// MAX: surpressing warning for the moment, will be dealt with in a different PR (TODO) use cosmwasm_schema::cw_serde; use std::fmt::{Display, Formatter}; use std::str::FromStr; diff --git a/common/credential-storage/src/ephemeral_storage.rs b/common/credential-storage/src/ephemeral_storage.rs index 62bcb6cffc0..4c11a456e92 100644 --- a/common/credential-storage/src/ephemeral_storage.rs +++ b/common/credential-storage/src/ephemeral_storage.rs @@ -246,7 +246,7 @@ mod tests { let _exp_date_sigs = generate_expiration_date_signatures( sig_req.expiration_date.ecash_unix_timestamp(), &[signing_keys.secret_key()], - &vec![signing_keys.verification_key()], + &[signing_keys.verification_key()], &signing_keys.verification_key(), &[1], )?; @@ -263,7 +263,7 @@ mod tests { let wallet = issuance.aggregate_signature_shares( &signing_keys.verification_key(), - &vec![partial_wallet], + &[partial_wallet], sig_req, )?; diff --git a/common/crypto/src/lib.rs b/common/crypto/src/lib.rs index 1dff7b82be0..e8426bb0854 100644 --- a/common/crypto/src/lib.rs +++ b/common/crypto/src/lib.rs @@ -1,6 +1,8 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] // silences clippy warning: deprecated associated function `generic_array::GenericArray::::from_exact_iter`: please upgrade to generic-array 1.x - TODO + #[cfg(feature = "asymmetric")] pub mod asymmetric; pub mod bech32_address_validation; diff --git a/common/gateway-requests/src/lib.rs b/common/gateway-requests/src/lib.rs index bdb9a30a0f1..e6a27d58467 100644 --- a/common/gateway-requests/src/lib.rs +++ b/common/gateway-requests/src/lib.rs @@ -1,5 +1,6 @@ // Copyright 2020-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] // silences clippy warning: deprecated associated function `nym_crypto::generic_array::GenericArray::::clone_from_slice`: please upgrade to generic-array 1.x - TODO pub use nym_crypto::generic_array; use nym_crypto::OutputSizeUser; diff --git a/common/gateway-requests/src/models.rs b/common/gateway-requests/src/models.rs index f7e79d76856..89a44f86da3 100644 --- a/common/gateway-requests/src/models.rs +++ b/common/gateway-requests/src/models.rs @@ -82,7 +82,7 @@ mod tests { let exp_date_sigs = generate_expiration_date_signatures( sig_req.expiration_date.ecash_unix_timestamp(), &[keypair.secret_key()], - &vec![keypair.verification_key()], + &[keypair.verification_key()], &keypair.verification_key(), &[keypair.index.unwrap()], ) @@ -106,14 +106,14 @@ mod tests { .unwrap(); let wallet = issuance - .aggregate_signature_shares(&keypair.verification_key(), &vec![partial_wallet], sig_req) + .aggregate_signature_shares(&keypair.verification_key(), &[partial_wallet], sig_req) .unwrap(); let mut issued = issuance.into_issued_ticketbook(wallet, 1); let coin_indices_signatures = generate_coin_indices_signatures( nym_credentials_interface::ecash_parameters(), &[keypair.secret_key()], - &vec![keypair.verification_key()], + &[keypair.verification_key()], &keypair.verification_key(), &[keypair.index.unwrap()], ) diff --git a/common/gateway-requests/src/types/binary_request.rs b/common/gateway-requests/src/types/binary_request.rs index b52ee819cd7..5424a29a1fc 100644 --- a/common/gateway-requests/src/types/binary_request.rs +++ b/common/gateway-requests/src/types/binary_request.rs @@ -70,7 +70,7 @@ impl BinaryRequest { let plaintext = match self { BinaryRequest::ForwardSphinx { packet } => packet.into_v1_bytes()?, - BinaryRequest::ForwardSphinxV2 { packet } => packet.into_v2_bytes()?, + BinaryRequest::ForwardSphinxV2 { packet } => packet.to_v2_bytes()?, }; BinaryData::make_encrypted_blob(kind as u8, &plaintext, shared_key) diff --git a/common/http-api-client/src/lib.rs b/common/http-api-client/src/lib.rs index 3ac1ef6ea9c..37bf40f79c3 100644 --- a/common/http-api-client/src/lib.rs +++ b/common/http-api-client/src/lib.rs @@ -1,6 +1,9 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] +// silences clippy warning: use of deprecated tuple variant `HttpClientError::GenericRequestFailure`: use another more strongly typed variant - this variant is only left for compatibility reasons - TODO + //! Nym HTTP API Client //! //! Centralizes and implements the core API client functionality. This crate provides custom, @@ -92,7 +95,7 @@ //! pub status: ApiStatus, //! pub uptime: u64, //! } -//! +//! //! #[derive(Clone, Copy, Debug, Serialize, Deserialize)] //! pub enum ApiStatus { //! Up, diff --git a/common/nym-connection-monitor/src/mixnet_beacon.rs b/common/nym-connection-monitor/src/mixnet_beacon.rs index 021519ef172..77e007092d6 100644 --- a/common/nym-connection-monitor/src/mixnet_beacon.rs +++ b/common/nym-connection-monitor/src/mixnet_beacon.rs @@ -28,7 +28,7 @@ impl MixnetConnectionBeacon { } } - async fn send_mixnet_self_ping(&self) -> Result { + async fn send_mixnet_self_ping(&mut self) -> Result { trace!("Sending mixnet self ping"); let (input_message, request_id) = create_self_ping(self.our_address); self.mixnet_client_sender @@ -38,7 +38,7 @@ impl MixnetConnectionBeacon { Ok(request_id) } - pub async fn run(self, shutdown: CancellationToken) -> Result<()> { + pub async fn run(mut self, shutdown: CancellationToken) -> Result<()> { debug!("Mixnet connection beacon is running"); let mut ping_interval = tokio::time::interval(MIXNET_SELF_PING_INTERVAL); loop { diff --git a/common/nym-connection-monitor/src/sync_self_ping.rs b/common/nym-connection-monitor/src/sync_self_ping.rs index 79ec9a10984..80402571022 100644 --- a/common/nym-connection-monitor/src/sync_self_ping.rs +++ b/common/nym-connection-monitor/src/sync_self_ping.rs @@ -3,7 +3,6 @@ use std::time::Duration; -use futures::StreamExt; use nym_sdk::mixnet::{MixnetClient, MixnetMessageSender, Recipient}; use tracing::{debug, error}; @@ -22,23 +21,22 @@ pub async fn self_ping_and_wait( wait_for_self_ping_return(mixnet_client, &request_ids).await } -async fn send_self_pings(our_address: Recipient, mixnet_client: &MixnetClient) -> Result> { - // Send pings - let request_ids = futures::stream::iter(1..=3) - .then(|_| async { - let (input_message, request_id) = create_self_ping(our_address); - mixnet_client - .send(input_message) - .await - .map_err(|err| Error::NymSdkError(Box::new(err)))?; - Ok::(request_id) - }) - .collect::>() - .await; +async fn send_self_pings( + our_address: Recipient, + mixnet_client: &mut MixnetClient, +) -> Result> { + let mut request_ids = Vec::with_capacity(3); + + for _ in 1..=3 { + let (input_message, request_id) = create_self_ping(our_address); + mixnet_client + .send(input_message) + .await + .map_err(|err| Error::NymSdkError(Box::new(err)))?; + request_ids.push(request_id); + } - // Check the vec of results and return the first error, if any. If there are not errors, unwrap - // all the results into a vec of u64s. - request_ids.into_iter().collect::>>() + Ok(request_ids) } async fn wait_for_self_ping_return( diff --git a/common/nymsphinx/Cargo.toml b/common/nymsphinx/Cargo.toml index d6b77f6969b..5c9dae785ad 100644 --- a/common/nymsphinx/Cargo.toml +++ b/common/nymsphinx/Cargo.toml @@ -13,6 +13,10 @@ rand = { workspace = true } rand_distr = { workspace = true } rand_chacha = { workspace = true } thiserror = { workspace = true } +serde = { workspace = true, features = ["derive"] } +bincode = { workspace = true } +log = { workspace = true } +tokio-util = { workspace = true, features = ["codec"] } nym-sphinx-acknowledgements = { path = "acknowledgements" } nym-sphinx-addressing = { path = "addressing" } @@ -47,11 +51,5 @@ features = ["sync"] [features] default = ["sphinx"] -sphinx = [ - "nym-sphinx-params/sphinx", - "nym-sphinx-types/sphinx", -] -outfox = [ - "nym-sphinx-params/outfox", - "nym-sphinx-types/outfox", -] +sphinx = ["nym-sphinx-params/sphinx", "nym-sphinx-types/sphinx"] +outfox = ["nym-sphinx-params/outfox", "nym-sphinx-types/outfox"] diff --git a/common/nymsphinx/acknowledgements/src/lib.rs b/common/nymsphinx/acknowledgements/src/lib.rs index 490e94eaea8..d37990d49f1 100644 --- a/common/nymsphinx/acknowledgements/src/lib.rs +++ b/common/nymsphinx/acknowledgements/src/lib.rs @@ -1,5 +1,6 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] // silences clippy warning: deprecated associated function `nym_crypto::generic_array::GenericArray::::clone_from_slice`: please upgrade to generic-array 1.x - TODO pub mod identifier; pub mod key; diff --git a/common/nymsphinx/anonymous-replies/Cargo.toml b/common/nymsphinx/anonymous-replies/Cargo.toml index ef9c74b73da..278dbc60104 100644 --- a/common/nymsphinx/anonymous-replies/Cargo.toml +++ b/common/nymsphinx/anonymous-replies/Cargo.toml @@ -12,6 +12,7 @@ rand = { workspace = true } bs58 = { workspace = true } thiserror = { workspace = true } tracing = { workspace = true } +serde = { workspace = true } nym-crypto = { path = "../../crypto", features = ["stream_cipher", "rand"] } nym-sphinx-addressing = { path = "../addressing" } diff --git a/common/nymsphinx/anonymous-replies/src/lib.rs b/common/nymsphinx/anonymous-replies/src/lib.rs index 2c47a23a760..84bfa7a3231 100644 --- a/common/nymsphinx/anonymous-replies/src/lib.rs +++ b/common/nymsphinx/anonymous-replies/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] // silences clippy warning: deprecated struct `nym_crypto::generic_array::GenericArray`: please upgrade to generic-array 1.x - TODO pub mod encryption_key; pub mod reply_surb; pub mod requests; diff --git a/common/nymsphinx/anonymous-replies/src/requests/mod.rs b/common/nymsphinx/anonymous-replies/src/requests/mod.rs index ecb2fc7ac50..b6916a20e8b 100644 --- a/common/nymsphinx/anonymous-replies/src/requests/mod.rs +++ b/common/nymsphinx/anonymous-replies/src/requests/mod.rs @@ -7,6 +7,7 @@ use crate::{ReplySurbError, ReplySurbWithKeyRotation}; use nym_sphinx_addressing::clients::{Recipient, RecipientFormattingError}; use nym_sphinx_params::key_rotation::InvalidSphinxKeyRotation; use rand::{CryptoRng, RngCore}; +use serde::{Deserialize, Serialize}; use std::fmt::{Display, Formatter}; use std::mem; use thiserror::Error; @@ -30,7 +31,7 @@ pub enum InvalidAnonymousSenderTagRepresentation { InvalidLength { received: usize, expected: usize }, } -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[cfg_attr(target_arch = "wasm32", wasm_bindgen)] pub struct AnonymousSenderTag([u8; SENDER_TAG_SIZE]); diff --git a/common/nymsphinx/forwarding/Cargo.toml b/common/nymsphinx/forwarding/Cargo.toml index c8beb33b12f..b2b25bab0c2 100644 --- a/common/nymsphinx/forwarding/Cargo.toml +++ b/common/nymsphinx/forwarding/Cargo.toml @@ -13,3 +13,4 @@ nym-sphinx-params = { path = "../params" } nym-sphinx-types = { path = "../types", features = ["sphinx", "outfox"] } nym-sphinx-anonymous-replies = { path = "../anonymous-replies" } thiserror = { workspace = true } +serde = { workspace = true } \ No newline at end of file diff --git a/common/nymsphinx/forwarding/src/packet.rs b/common/nymsphinx/forwarding/src/packet.rs index 9fb9f3a9155..32b8c893ee2 100644 --- a/common/nymsphinx/forwarding/src/packet.rs +++ b/common/nymsphinx/forwarding/src/packet.rs @@ -4,6 +4,11 @@ use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, NymNodeRoutingAddressError}; use nym_sphinx_params::{PacketSize, PacketType, SphinxKeyRotation}; use nym_sphinx_types::{NymPacket, NymPacketError}; +use serde::{ + Deserialize, Deserializer, Serialize, Serializer, + de::{self, Visitor}, +}; +use std::fmt; use nym_sphinx_anonymous_replies::reply_surb::AppliedReplySurb; use nym_sphinx_params::key_rotation::InvalidSphinxKeyRotation; @@ -174,7 +179,7 @@ impl MixPacket { }) } - pub fn into_v2_bytes(self) -> Result, MixPacketFormattingError> { + pub fn to_v2_bytes(&self) -> Result, MixPacketFormattingError> { Ok(std::iter::once(self.packet_type as u8) .chain(std::iter::once(self.key_rotation as u8)) .chain(self.next_hop.as_bytes()) @@ -183,4 +188,31 @@ impl MixPacket { } } +// MAX TODO implement for v1 as well for back compat? - this was added in the original asyncread/write work when we only had one v +impl Serialize for MixPacket { + fn serialize(&self, serializer: S) -> Result { + serializer.serialize_bytes(&self.to_v2_bytes().map_err(serde::ser::Error::custom)?) + } +} + +struct MixPacketVisitor; + +impl<'de> Visitor<'de> for MixPacketVisitor { + type Value = MixPacket; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a byte array representing a mix packet") + } + + fn visit_bytes(self, v: &[u8]) -> Result { + MixPacket::try_from_v2_bytes(v).map_err(serde::de::Error::custom) + } +} + +impl<'de> Deserialize<'de> for MixPacket { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_bytes(MixPacketVisitor) + } +} + // TODO: test for serialization and errors! diff --git a/common/nymsphinx/framing/src/processing.rs b/common/nymsphinx/framing/src/processing.rs index d647543d6a8..a1dc96cfc48 100644 --- a/common/nymsphinx/framing/src/processing.rs +++ b/common/nymsphinx/framing/src/processing.rs @@ -14,7 +14,7 @@ use nym_sphinx_types::{ }; use std::fmt::Display; use thiserror::Error; -use tracing::{debug, error, info, trace}; +use tracing::{debug, info, trace}; #[derive(Debug)] pub enum MixProcessingResultData { diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index eb83ec43ea0..e10f8803a4d 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -1,7 +1,10 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use std::io; + use crate::message::{NymMessage, NymMessageError, PaddedMessage, PlainMessage}; +use log::debug; use nym_crypto::aes::cipher::{KeyIvInit, StreamCipher}; use nym_crypto::asymmetric::x25519; use nym_crypto::shared_key::recompute_shared_key; @@ -15,10 +18,13 @@ use nym_sphinx_chunking::reconstruction::MessageReconstructor; use nym_sphinx_params::{ PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm, }; +use serde::{Deserialize, Serialize}; use thiserror::Error; +use tokio_util::bytes::{Buf, BytesMut}; +use tokio_util::codec::{Decoder, Encoder}; // TODO: should this live in this file? -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct ReconstructedMessage { /// The actual plaintext message that was received. pub message: Vec, @@ -56,6 +62,62 @@ impl From for ReconstructedMessage { } } +pub struct ReconstructedMessageCodec; +const LENGHT_ENCODING_PREFIX_SIZE: usize = 4; + +impl Encoder for ReconstructedMessageCodec { + type Error = MessageRecoveryError; + + fn encode( + &mut self, + item: ReconstructedMessage, + buf: &mut BytesMut, + ) -> Result<(), Self::Error> { + let encoded = bincode::serialize(&item).expect("failed to serialize ReconstructedMessage"); + let encoded_len = encoded.len() as u32; + let mut encoded_with_len = encoded_len.to_le_bytes().to_vec(); + encoded_with_len.extend(encoded); + buf.reserve(encoded_with_len.len()); + buf.extend_from_slice(&encoded_with_len); + Ok(()) + } +} + +impl Decoder for ReconstructedMessageCodec { + type Item = ReconstructedMessage; + type Error = MessageRecoveryError; + + fn decode(&mut self, buf: &mut BytesMut) -> Result, Self::Error> { + if buf.len() < LENGHT_ENCODING_PREFIX_SIZE { + return Ok(None); + } + + let len = u32::from_le_bytes( + buf[0..LENGHT_ENCODING_PREFIX_SIZE] + .try_into() + .expect("We know that we have at least LENGHT_ENCODING_PREFIX_SIZE bytes in there"), + ) as usize; + + if buf.len() < len + LENGHT_ENCODING_PREFIX_SIZE { + return Ok(None); + } + + let decoded = match bincode::deserialize( + &buf[LENGHT_ENCODING_PREFIX_SIZE..len + LENGHT_ENCODING_PREFIX_SIZE], + ) { + Ok(decoded) => decoded, + Err(e) => { + debug!("Failed to decode the message - {:?}", e); + return Ok(None); + } + }; + + buf.advance(len + LENGHT_ENCODING_PREFIX_SIZE); + + Ok(Some(decoded)) + } +} + #[derive(Debug, Error)] pub enum MessageRecoveryError { #[error( @@ -75,6 +137,9 @@ pub enum MessageRecoveryError { #[error("Failed to recover message fragment - {0}")] FragmentRecoveryError(#[from] ChunkingError), + + #[error("Failed to recover message fragment - {0}")] + MessageRecoveryError(#[from] io::Error), } pub trait MessageReceiver { diff --git a/common/socks5-client-core/Cargo.toml b/common/socks5-client-core/Cargo.toml index ce8421f3d73..d5019e3c403 100644 --- a/common/socks5-client-core/Cargo.toml +++ b/common/socks5-client-core/Cargo.toml @@ -19,6 +19,7 @@ serde = { workspace = true, features = ["derive"] } # for config serialization/d tap = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal"] } +tokio-util = { workspace = true } url = { workspace = true } nym-bandwidth-controller = { path = "../../common/bandwidth-controller" } diff --git a/common/socks5-client-core/src/socks/client.rs b/common/socks5-client-core/src/socks/client.rs index ffdca82a70f..868ced71b59 100644 --- a/common/socks5-client-core/src/socks/client.rs +++ b/common/socks5-client-core/src/socks/client.rs @@ -7,6 +7,7 @@ use super::{SocksVersion, RESERVED, SOCKS4_VERSION, SOCKS5_VERSION}; use crate::config; use futures::channel::mpsc; use futures::task::{Context, Poll}; +use futures::SinkExt; use log::*; use nym_client_core::client::inbound_messages::{InputMessage, InputMessageSender}; use nym_service_providers_common::interface::{ProviderInterfaceVersion, RequestVersion}; diff --git a/common/socks5/proxy-helpers/src/ordered_sender.rs b/common/socks5/proxy-helpers/src/ordered_sender.rs index d71f8435587..de30293eb04 100644 --- a/common/socks5/proxy-helpers/src/ordered_sender.rs +++ b/common/socks5/proxy-helpers/src/ordered_sender.rs @@ -3,11 +3,12 @@ use crate::proxy_runner::MixProxySender; use bytes::Bytes; +use futures::SinkExt; use log::{debug, error}; use nym_socks5_requests::{ConnectionId, SocketData}; use std::io; -pub(crate) struct OrderedMessageSender { +pub(crate) struct OrderedMessageSender { connection_id: ConnectionId, // addresses are provided for better logging local_destination_address: String, @@ -18,7 +19,7 @@ pub(crate) struct OrderedMessageSender { mix_message_adapter: F, } -impl OrderedMessageSender +impl OrderedMessageSender where F: Fn(SocketData) -> S, { @@ -55,7 +56,7 @@ where (self.mix_message_adapter)(data) } - async fn send_message(&self, message: S) { + async fn send_message(&mut self, message: S) { if self.mixnet_sender.send(message).await.is_err() { panic!("BatchRealMessageReceiver has stopped receiving!") } diff --git a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs index 2e3087bbc02..d1b5653d6ea 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/inbound.rs @@ -74,7 +74,7 @@ async fn wait_for_lane( } } -pub(super) async fn run_inbound( +pub(super) async fn run_inbound( mut reader: OwnedReadHalf, mut message_sender: OrderedMessageSender, connection_id: ConnectionId, diff --git a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs index 75a51e366a5..6da9fcd4664 100644 --- a/common/socks5/proxy-helpers/src/proxy_runner/mod.rs +++ b/common/socks5/proxy-helpers/src/proxy_runner/mod.rs @@ -9,6 +9,7 @@ use nym_task::ShutdownTracker; use std::fmt::Debug; use std::{sync::Arc, time::Duration}; use tokio::{net::TcpStream, sync::Notify}; +use tokio_util::sync::PollSender; mod inbound; mod outbound; @@ -35,7 +36,7 @@ impl From<(Vec, bool)> for ProxyMessage { } } -pub type MixProxySender = tokio::sync::mpsc::Sender; +pub type MixProxySender = PollSender; pub type MixProxyReader = tokio::sync::mpsc::Receiver; // TODO: when we finally get to implementing graceful shutdown, diff --git a/common/store-cipher/src/lib.rs b/common/store-cipher/src/lib.rs index 98d586151fc..5119021d166 100644 --- a/common/store-cipher/src/lib.rs +++ b/common/store-cipher/src/lib.rs @@ -1,6 +1,7 @@ // Copyright 2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +#![allow(deprecated)] use aes_gcm::aead::{Aead, Nonce}; use aes_gcm::{AeadCore, AeadInPlace, KeyInit}; use rand::{thread_rng, CryptoRng, Fill, RngCore}; diff --git a/common/task/Cargo.toml b/common/task/Cargo.toml index d96fcbe0686..dd163d65a20 100644 --- a/common/task/Cargo.toml +++ b/common/task/Cargo.toml @@ -15,6 +15,7 @@ thiserror = { workspace = true } tokio = { workspace = true, features = ["macros", "sync"] } tokio-util = { workspace = true, features = ["rt"] } tracing = { workspace = true } +serde = { workspace = true } [target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio] workspace = true @@ -39,4 +40,4 @@ tokio = { workspace = true, features = ["rt-multi-thread", "net", "signal", "tes nym-test-utils = { path = "../test-utils" } [lints] -workspace = true \ No newline at end of file +workspace = true diff --git a/common/task/src/connections.rs b/common/task/src/connections.rs index b0e8fc6ecca..d0e0c4c269a 100644 --- a/common/task/src/connections.rs +++ b/common/task/src/connections.rs @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 use futures::channel::mpsc; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; // const LANE_CONSIDERED_CLEAR: usize = 10; pub type ConnectionId = u64; -#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum TransmissionLane { General, // we need to treat surb-related requests and responses at higher priority diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs index 113240dc115..c3afa46ea16 100644 --- a/gateway/src/node/client_handling/bandwidth.rs +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -3,7 +3,6 @@ use std::num::ParseIntError; use thiserror::Error; -use tracing::error; #[derive(Debug, Error)] pub enum BandwidthError { diff --git a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs index af476bfba78..8354e60cb13 100644 --- a/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs +++ b/gateway/src/node/internal_service_providers/authenticator/mixnet_listener.rs @@ -792,7 +792,7 @@ impl MixnetListener { // When an incoming mixnet message triggers a response that we send back. async fn handle_response( - &self, + &mut self, response: Vec, recipient: Option, sender_tag: Option, diff --git a/mixtcp/Cargo.toml b/mixtcp/Cargo.toml new file mode 100644 index 00000000000..222a447a69b --- /dev/null +++ b/mixtcp/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "mixtcp" +version = "0.0.1" +edition = "2021" +license.workspace = true + +[dependencies] +smoltcp = { workspace = true, default-features = false, features = [ + "std", + "medium-ip", + "proto-ipv4", + "proto-ipv6", + "socket-tcp", + "socket-icmp", +] } +tokio = { workspace = true } +bytes = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { workspace = true } +nym-bin-common = { path = "../common/bin-common", features = ["basic_tracing"] } +nym-sdk = { path = "../sdk/rust/nym-sdk" } +nym-ip-packet-requests = { path = "../common/ip-packet-requests" } +thiserror.workspace = true +rustls = { workspace = true } + +[dev-dependencies] +reqwest.workspace = true +dirs.workspace = true +webpki-roots.workspace = true +serde_json.workspace = true diff --git a/mixtcp/README.md b/mixtcp/README.md new file mode 100644 index 00000000000..9a245b2fde6 --- /dev/null +++ b/mixtcp/README.md @@ -0,0 +1,30 @@ +# MixTCP + +This is an initial proof of concept of a SmolTCP `device` that uses the Mixnet for transport. It relies on the `IpMixStream` module from the Rust SDK to set up a connection with an Exit Gateway's Ip-Packet-Router, meaning that this is the IP that is seen by the receiver of the request. + +This can be used as the basis for building HTTP(S) crates on top of the Mixnet whilst abstracting away the complexities of using the Mixnet for transport. + +More to come in the future. + +`examples/` contains examples for: +- a TLS ping with Cloudflare +- creating a `reqwest`-like HTTPS `GET` request and receiving a response + +## Component Interaction +```sh + create_device() + | + +--------------+---------------+ + | | | + v v v + NymIprDevice NymIprBridge IpPair + | | (10.0.x.x) + | | + +-- channels --+ + | + v + IpMixStream + | + v + Mixnet +``` diff --git a/mixtcp/examples/cloudflare_ping.rs b/mixtcp/examples/cloudflare_ping.rs new file mode 100644 index 00000000000..e8f4c9a196a --- /dev/null +++ b/mixtcp/examples/cloudflare_ping.rs @@ -0,0 +1,284 @@ +#![allow(clippy::result_large_err)] +use mixtcp::{create_device, MixtcpError}; +use rustls::{pki_types::ServerName, ClientConfig, ClientConnection}; +use std::{ + io::{self, Read, Write}, + sync::Arc, +}; +use tracing::info; + +use nym_sdk::stream_wrapper::IpMixStream; +use smoltcp::{ + iface::{Config, Interface, SocketSet}, + socket::tcp, + time::Instant, + wire::{HardwareAddress, IpAddress, IpCidr, Ipv4Address}, +}; +use std::sync::Once; +use std::time::Duration; + +static INIT: Once = Once::new(); + +pub struct TlsOverTcp { + pub conn: ClientConnection, +} + +impl TlsOverTcp { + pub fn new(domain: &str) -> Result { + let mut root_store = rustls::RootCertStore::empty(); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + let config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + let server_name = ServerName::try_from(domain) + .map_err(|_| MixtcpError::InvalidDnsName)? + .to_owned(); + + let conn = ClientConnection::new(Arc::new(config), server_name) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + + Ok(Self { conn }) + } + + /// Move data from TLS connection to TCP socket + pub fn write_tls(&mut self, socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + let mut buf = [0u8; 4096]; + while self.conn.wants_write() { + match self.conn.write_tls(&mut buf.as_mut_slice()) { + Ok(n) if n > 0 => { + socket + .send_slice(&buf[..n]) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + } + _ => break, + } + } + Ok(()) + } + + /// Move data from TCP socket to TLS connection + pub fn read_tls(&mut self, socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + if socket.can_recv() { + let _ = socket.recv(|chunk| { + if !chunk.is_empty() { + inspect_tls_packet(chunk); + let _ = self.conn.read_tls(&mut io::Cursor::new(&mut *chunk)); + let _ = self.conn.process_new_packets(); + } + (chunk.len(), ()) + }); + } + Ok(()) + } + + pub fn send(&mut self, data: &[u8], socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + self.conn + .writer() + .write_all(data) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + self.write_tls(socket) + } + + pub fn recv(&mut self, socket: &mut tcp::Socket) -> Result, MixtcpError> { + self.read_tls(socket)?; + let mut result = Vec::new(); + let mut buf = vec![0u8; 4096]; + match self.conn.reader().read(&mut buf) { + Ok(n) if n > 0 => result.extend_from_slice(&buf[..n]), + _ => {} + } + Ok(result) + } +} + +fn inspect_tls_packet(data: &[u8]) { + if data.len() < 5 { + return; + } + let content_type = data[0]; + if !(0x14..=0x17).contains(&content_type) { + return; + } + let version = u16::from_be_bytes([data[1], data[2]]); + let length = u16::from_be_bytes([data[3], data[4]]); + info!( + "TLS packet: ContentType={:#04x}, Version={:#06x}, Length={}", + content_type, version, length + ); + if content_type == 0x16 && data.len() > 5 { + let handshake_type = data[5]; + let handshake_types = match handshake_type { + 0x01 => "ClientHello", + 0x02 => "ServerHello", + 0x0b => "Certificate", + 0x0c => "ServerKeyExchange", + 0x0d => "CertificateRequest", + 0x0e => "ServerHelloDone", + 0x0f => "CertificateVerify", + 0x10 => "ClientKeyExchange", + 0x14 => "Finished", + _ => "Unknown", + }; + info!( + "Handshake type: {:#04x} ({}), Length: {}", + handshake_type, handshake_types, length + ); + } +} + +fn init_logging() { + if tracing::dispatcher::has_been_set() { + return; + } + INIT.call_once(|| { + nym_bin_common::logging::setup_tracing_logger(); + }); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + init_logging(); + + let ipr_stream = IpMixStream::new().await?; + let (mut device, bridge, allocated_ips) = create_device(ipr_stream).await?; + info!("Allocated IP: {}", allocated_ips.ipv4); + + tokio::spawn(async move { + bridge.run().await.unwrap(); + }); + + let config = Config::new(HardwareAddress::Ip); + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::from(allocated_ips.ipv4), 32)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::UNSPECIFIED) + .unwrap(); + + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 16384]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + let target_ip = Ipv4Address::new(1, 1, 1, 1); + let target_port = 443; + + let mut timestamp = Instant::from_millis(0); + let start = tokio::time::Instant::now(); + let mut connected = false; + let mut tls = None; + let mut handshake_completed = false; + let mut request_sent = false; + + loop { + if start.elapsed() > Duration::from_secs(60) { + info!("Test timeout after 60 seconds"); + break; + } + + iface.poll(timestamp, &mut device, &mut sockets); + timestamp += smoltcp::time::Duration::from_millis(1); + let socket = sockets.get_mut::(tcp_handle); + + // TCP connection setup + if !connected && !socket.is_open() { + match socket.connect(iface.context(), (target_ip, target_port), 49152) { + Ok(_) => { + info!("TCP connect started"); + connected = true; + } + Err(e) => { + info!("TCP connect failed: {}", e); + break; + } + } + } + + // TLS setup after TCP established + if socket.state() == tcp::State::Established && tls.is_none() { + info!("TCP established - creating TLS connection"); + match TlsOverTcp::new("cloudflare.com") { + Ok(t) => tls = Some(t), + Err(e) => { + info!("TLS create failed: {}", e); + break; + } + } + } + + // TLS handshake and request + if let Some(ref mut tls_conn) = tls { + let _ = tls_conn.read_tls(socket); + let _ = tls_conn.write_tls(socket); + + // Complete handshake + if !tls_conn.conn.is_handshaking() && !handshake_completed { + handshake_completed = true; + info!("TLS handshake completed - ready for HTTPS"); + + // Send simple HTTP request + let request = b"GET /cdn-cgi/trace HTTP/1.1\r\nHost: cloudflare.com\r\nUser-Agent: mixtcp-test/1.0\r\nAccept: */*\r\nConnection: close\r\n\r\n"; + match tls_conn.send(request, socket) { + Ok(_) => { + info!("HTTPS request sent"); + request_sent = true; + } + Err(e) => { + info!("HTTPS send failed: {}", e); + break; + } + } + } + + // Read response after request sent + if request_sent { + let mut response_data = Vec::new(); + let mut buf = vec![0u8; 4096]; + + match tls_conn.conn.reader().read(&mut buf) { + Ok(0) => { + info!("Response complete - connection closed"); + break; + } + Ok(n) if n > 0 => { + response_data.extend_from_slice(&buf[..n]); + info!("Received {} bytes", n); + + if let Ok(response_str) = std::str::from_utf8(&response_data) { + if response_str.contains("\r\n\r\n") { + info!("HTTPS response received!"); + + if let Some(status_end) = response_str.find("\r\n") { + info!("HTTP Status: {}", &response_str[..status_end]); + } + + info!("Full response: {}", response_str); + return Ok(()); + } + } + } + Ok(1_usize..) => { + todo!() + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // Keep polling + } + Err(e) => { + info!("Read error: {}", e); + break; + } + } + } + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + + Err("No HTTP response received".into()) +} diff --git a/mixtcp/examples/https_client.rs b/mixtcp/examples/https_client.rs new file mode 100644 index 00000000000..99b01024c21 --- /dev/null +++ b/mixtcp/examples/https_client.rs @@ -0,0 +1,339 @@ +#![allow(clippy::result_large_err)] +use mixtcp::{create_device, MixtcpError, NymIprDevice}; +use nym_sdk::stream_wrapper::IpMixStream; +use reqwest::StatusCode; +use rustls::{pki_types::ServerName, ClientConfig, ClientConnection}; +use smoltcp::{ + iface::{Config, Interface, SocketSet}, + socket::tcp, + time::Instant, + wire::{HardwareAddress, IpAddress, IpCidr, Ipv4Address}, +}; +use std::sync::Once; +use std::{ + io::{self, Read, Write}, + sync::Arc, + time::Duration, +}; +use tracing::info; + +static INIT: Once = Once::new(); + +pub struct TlsOverTcp { + pub conn: ClientConnection, +} + +impl TlsOverTcp { + pub fn new(domain: &str) -> Result { + let mut root_store = rustls::RootCertStore::empty(); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + let config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + let server_name = ServerName::try_from(domain) + .map_err(|_| MixtcpError::InvalidDnsName)? + .to_owned(); + + let conn = ClientConnection::new(Arc::new(config), server_name) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + + Ok(Self { conn }) + } + + pub fn write_tls(&mut self, socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + let mut buf = [0u8; 4096]; + while self.conn.wants_write() { + match self.conn.write_tls(&mut buf.as_mut_slice()) { + Ok(n) if n > 0 => { + socket + .send_slice(&buf[..n]) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + } + _ => break, + } + } + Ok(()) + } + + pub fn read_tls(&mut self, socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + if socket.can_recv() { + let _ = socket.recv(|chunk| { + if !chunk.is_empty() { + let _ = self.conn.read_tls(&mut io::Cursor::new(&mut *chunk)); + let _ = self.conn.process_new_packets(); + } + (chunk.len(), ()) + }); + } + Ok(()) + } + + pub fn send(&mut self, data: &[u8], socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + self.conn + .writer() + .write_all(data) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + self.write_tls(socket) + } +} + +/// Reqwest-ish client right now, just a handrolled GET request for the example +pub struct MixtcpReqwestClient { + device: Arc>, + _bridge: tokio::task::JoinHandle<()>, + _allocated_ip: Ipv4Address, +} + +impl MixtcpReqwestClient { + pub async fn new() -> Result { + let ipr_stream = IpMixStream::new() + .await + .map_err(|_| MixtcpError::MixnetConnectionFailed)?; + + let (mut device, bridge, allocated_ips) = create_device(ipr_stream).await?; + info!("Allocated IP: {}", allocated_ips.ipv4); + + let bridge_handle = tokio::spawn(async move { + if let Err(e) = bridge.run().await { + tracing::error!("Bridge error: {}", e); + } + }); + + let config = Config::new(HardwareAddress::Ip); + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::from(allocated_ips.ipv4), 32)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::UNSPECIFIED) + .unwrap(); + + let device = Arc::new(tokio::sync::Mutex::new((iface, device))); + + Ok(Self { + device, + _bridge: bridge_handle, + _allocated_ip: allocated_ips.ipv4, + }) + } + + pub async fn get(&self, url: &str) -> Result { + let parsed_url = reqwest::Url::parse(url).map_err(|_| MixtcpError::InvalidUrl)?; + let host = parsed_url.host_str().ok_or(MixtcpError::InvalidUrl)?; + let path = parsed_url.path(); + + let response_bytes = self.simple_get_request(host, path).await?; + let (status, body) = self.parse_simple_response(&response_bytes)?; + + Ok(MixtcpResponse { status, body }) + } + + async fn simple_get_request(&self, domain: &str, path: &str) -> Result, MixtcpError> { + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 16384]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + let target_ip = Ipv4Address::new(1, 1, 1, 1); + let target_port = 443; + + let mut timestamp = Instant::from_millis(0); + let start = tokio::time::Instant::now(); + let mut connected = false; + let mut tls = None; + let mut handshake_completed = false; + let mut request_sent = false; + let mut response_data = Vec::new(); + + let mut device_guard = self.device.lock().await; + let (ref mut iface, ref mut device) = &mut *device_guard; + + loop { + if start.elapsed() > Duration::from_secs(60) { + return Err(MixtcpError::Timeout); + } + + iface.poll(timestamp, device, &mut sockets); + timestamp += smoltcp::time::Duration::from_millis(1); + let socket = sockets.get_mut::(tcp_handle); + + if !connected && !socket.is_open() { + match socket.connect(iface.context(), (target_ip, target_port), 49152) { + Ok(_) => { + info!("TCP connect started"); + connected = true; + } + Err(e) => { + info!("TCP connect failed: {}", e); + return Err(MixtcpError::TcpConnectionFailed); + } + } + } + + if socket.state() == tcp::State::Established && tls.is_none() { + info!("TCP established - creating TLS connection"); + match TlsOverTcp::new(domain) { + Ok(t) => tls = Some(t), + Err(e) => { + info!("TLS create failed: {}", e); + return Err(MixtcpError::TlsHandshakeFailed); + } + } + } + + if let Some(ref mut tls_conn) = tls { + let _ = tls_conn.read_tls(socket); + let _ = tls_conn.write_tls(socket); + + if !tls_conn.conn.is_handshaking() && !handshake_completed { + handshake_completed = true; + info!("TLS handshake completed - ready for HTTPS"); + + let request = format!( + "GET {} HTTP/1.1\r\nHost: {}\r\nUser-Agent: mixtcp/1.0\r\nAccept: */*\r\nConnection: close\r\n\r\n", + path, domain + ); + tls_conn.send(request.as_bytes(), socket)?; + info!("HTTPS request sent"); + request_sent = true; + } + + if request_sent { + let mut buf = vec![0u8; 4096]; + match tls_conn.conn.reader().read(&mut buf) { + Ok(0) => { + info!("Response complete"); + break; + } + Ok(n) if n > 0 => { + response_data.extend_from_slice(&buf[..n]); + if let Ok(response_str) = std::str::from_utf8(&response_data) { + if response_str.contains("\r\n\r\n") { + return Ok(response_data); + } + } + } + Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {} + Err(e) => { + info!("Read error: {}", e); + return Err(MixtcpError::ResponseReadFailed); + } + Ok(_) => continue, + } + } + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + + Err(MixtcpError::NoResponseReceived) + } + + /// Simple response - just extract status and body + fn parse_simple_response(&self, response_bytes: &[u8]) -> Result<(u16, String), MixtcpError> { + let response_str = String::from_utf8_lossy(response_bytes); + + let status_line = response_str + .lines() + .next() + .ok_or(MixtcpError::InvalidHttpResponse)?; + + let status: u16 = status_line + .split_whitespace() + .nth(1) + .and_then(|s| s.parse().ok()) + .unwrap_or(200); + + if let Some(body_start) = response_str.find("\r\n\r\n") { + let body = response_str[body_start + 4..].to_string(); + Ok((status, body)) + } else { + Err(MixtcpError::InvalidHttpResponse) + } + } +} + +pub struct MixtcpResponse { + status: u16, + body: String, +} + +impl MixtcpResponse { + pub fn status(&self) -> StatusCode { + StatusCode::from_u16(self.status).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR) + } + + pub async fn text(self) -> Result { + Ok(self.body) + } +} + +fn init_logging() { + if tracing::dispatcher::has_been_set() { + return; + } + INIT.call_once(|| { + nym_bin_common::logging::setup_tracing_logger(); + }); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + init_logging(); + + let test_url = "https://cloudflare.com/cdn-cgi/trace"; + + info!("Fetching with plain reqwest..."); + let start = tokio::time::Instant::now(); + let plain_response = reqwest::get(test_url).await?; + let plain_status = plain_response.status(); + let plain_text = plain_response.text().await?; + let plain_duration = start.elapsed(); + + info!( + "Plain reqwest - Status: {}, Time: {:?}", + plain_status, plain_duration + ); + + info!("Setting up mixnet client..."); + let client = MixtcpReqwestClient::new().await?; + let start = tokio::time::Instant::now(); + let mixnet_response = client.get(test_url).await?; + let mixnet_status = mixnet_response.status(); + let mixnet_text = mixnet_response.text().await?; + let mixnet_duration = start.elapsed(); + + info!( + "Mixnet reqwest - Status: {}, Time: {:?}", + mixnet_status, mixnet_duration + ); + + info!("Status codes match: {}", plain_status == mixnet_status); + info!( + "Response lengths match: {}", + plain_text.len() == mixnet_text.len() + ); + + let key_fields = ["fl=", "ip=", "ts=", "visit_scheme="]; + for field in key_fields { + let plain_has = plain_text.contains(field); + let mixnet_has = mixnet_text.contains(field); + info!( + "Field '{}' - Plain: {}, Mixnet: {}", + field, plain_has, mixnet_has + ); + assert_eq!(plain_has, mixnet_has, "Field '{}' mismatch", field); + } + + info!("Plain reqwest time: {:?}", plain_duration); + info!("Mixnet reqwest time: {:?}", mixnet_duration); + let slowdown = mixnet_duration.as_millis() as f64 / plain_duration.as_millis() as f64; + info!("Mixnet slowdown: {:.1}x", slowdown); + info!("Both responses match"); + Ok(()) +} diff --git a/mixtcp/examples/tls.rs b/mixtcp/examples/tls.rs new file mode 100644 index 00000000000..05ae0930749 --- /dev/null +++ b/mixtcp/examples/tls.rs @@ -0,0 +1,267 @@ +#![allow(clippy::result_large_err)] +use mixtcp::{create_device, MixtcpError}; +use rustls::{pki_types::ServerName, ClientConfig, ClientConnection}; +use std::{ + io::{self, Read, Write}, + sync::Arc, +}; +use tracing::info; + +use nym_sdk::stream_wrapper::IpMixStream; +use smoltcp::{ + iface::{Config, Interface, SocketSet}, + socket::tcp, + time::Instant, + wire::{HardwareAddress, IpAddress, IpCidr, Ipv4Address}, +}; +use std::sync::Once; +use std::time::Duration; + +static INIT: Once = Once::new(); + +pub struct TlsOverTcp { + pub conn: ClientConnection, +} + +impl TlsOverTcp { + pub fn new(domain: &str) -> Result { + let mut root_store = rustls::RootCertStore::empty(); + root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + + let config = ClientConfig::builder() + .with_root_certificates(root_store) + .with_no_client_auth(); + + let server_name = ServerName::try_from(domain) + .map_err(|_| MixtcpError::InvalidDnsName)? + .to_owned(); + + let conn = ClientConnection::new(Arc::new(config), server_name) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + + Ok(Self { conn }) + } + + /// Move data from TLS connection to TCP socket + pub fn write_tls(&mut self, socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + let mut buf = [0u8; 4096]; + while self.conn.wants_write() { + match self.conn.write_tls(&mut buf.as_mut_slice()) { + Ok(n) if n > 0 => { + socket + .send_slice(&buf[..n]) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + } + _ => break, + } + } + Ok(()) + } + + /// Move data from TCP socket to TLS connection + pub fn read_tls(&mut self, socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + if socket.can_recv() { + let _ = socket.recv(|chunk| { + if !chunk.is_empty() { + inspect_tls_packet(chunk); + let _ = self.conn.read_tls(&mut io::Cursor::new(&mut *chunk)); + let _ = self.conn.process_new_packets(); + } + (chunk.len(), ()) + }); + } + Ok(()) + } + + pub fn send(&mut self, data: &[u8], socket: &mut tcp::Socket) -> Result<(), MixtcpError> { + self.conn + .writer() + .write_all(data) + .map_err(|_| MixtcpError::TlsHandshakeFailed)?; + self.write_tls(socket) + } + + pub fn recv(&mut self, socket: &mut tcp::Socket) -> Result, MixtcpError> { + self.read_tls(socket)?; + let mut result = Vec::new(); + let mut buf = vec![0u8; 4096]; + match self.conn.reader().read(&mut buf) { + Ok(n) if n > 0 => result.extend_from_slice(&buf[..n]), + _ => {} + } + Ok(result) + } +} + +fn inspect_tls_packet(data: &[u8]) { + if data.len() < 5 { + return; + } + let content_type = data[0]; + if !(0x14..=0x17).contains(&content_type) { + return; + } + let version = u16::from_be_bytes([data[1], data[2]]); + let length = u16::from_be_bytes([data[3], data[4]]); + info!( + "TLS packet: ContentType={:#04x}, Version={:#06x}, Length={}", + content_type, version, length + ); + if content_type == 0x16 && data.len() > 5 { + let handshake_type = data[5]; + let handshake_types = match handshake_type { + 0x01 => "ClientHello", + 0x02 => "ServerHello", + 0x0b => "Certificate", + 0x0c => "ServerKeyExchange", + 0x0d => "CertificateRequest", + 0x0e => "ServerHelloDone", + 0x0f => "CertificateVerify", + 0x10 => "ClientKeyExchange", + 0x14 => "Finished", + _ => "Unknown", + }; + info!( + "Handshake type: {:#04x} ({}), Length: {}", + handshake_type, handshake_types, length + ); + } +} + +fn init_logging() { + if tracing::dispatcher::has_been_set() { + return; + } + INIT.call_once(|| { + nym_bin_common::logging::setup_tracing_logger(); + }); +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + init_logging(); + + let ipr_stream = IpMixStream::new().await?; + let (mut device, bridge, allocated_ips) = create_device(ipr_stream).await?; + info!("Allocated IP: {}", allocated_ips.ipv4); + + tokio::spawn(async move { + bridge.run().await.unwrap(); + }); + + let config = Config::new(HardwareAddress::Ip); + let mut iface = Interface::new(config, &mut device, Instant::now()); + iface.update_ip_addrs(|ip_addrs| { + ip_addrs + .push(IpCidr::new(IpAddress::from(allocated_ips.ipv4), 32)) + .unwrap(); + }); + iface + .routes_mut() + .add_default_ipv4_route(Ipv4Address::UNSPECIFIED) + .unwrap(); + + let tcp_rx_buffer = tcp::SocketBuffer::new(vec![0; 16384]); + let tcp_tx_buffer = tcp::SocketBuffer::new(vec![0; 4096]); + let tcp_socket = tcp::Socket::new(tcp_rx_buffer, tcp_tx_buffer); + let mut sockets = SocketSet::new(vec![]); + let tcp_handle = sockets.add(tcp_socket); + + let target_ip = Ipv4Address::new(1, 1, 1, 1); // Pinging Cloudflare + let target_port = 443; + info!("Connecting to {}:{} through mixnet", target_ip, target_port); + + let mut timestamp = Instant::from_millis(0); + let start = tokio::time::Instant::now(); + let mut connected = false; + let mut tls = None; + let handshake_completed = false; + + loop { + if start.elapsed() > Duration::from_secs(120) { + info!("Test timeout after 120 seconds"); + break; + } + + iface.poll(timestamp, &mut device, &mut sockets); + timestamp += smoltcp::time::Duration::from_millis(1); + let socket = sockets.get_mut::(tcp_handle); + + if !connected && !socket.is_open() { + match socket.connect(iface.context(), (target_ip, target_port), 49152) { + Ok(_) => { + info!("TCP connect started"); + connected = true; + } + Err(e) => { + info!("TCP connect failed: {}", e); + break; + } + } + } + + if start.elapsed().as_secs().is_multiple_of(5) && start.elapsed().as_millis() % 1000 < 100 { + info!( + "State: TCP={:?}, established={}, can_send={}, can_recv={}", + socket.state(), + socket.state() == tcp::State::Established, + socket.may_send(), + socket.can_recv() + ); + } + + if socket.state() == tcp::State::Established && tls.is_none() { + info!("TCP established - creating TLS connection"); + match TlsOverTcp::new("cloudflare.com") { + Ok(t) => tls = Some(t), + Err(e) => { + info!("TLS create failed: {}", e); + break; + } + } + } + + if let Some(ref mut tls_conn) = tls { + let _ = tls_conn.read_tls(socket); + let _ = tls_conn.write_tls(socket); + + if start.elapsed().as_secs().is_multiple_of(10) + && start.elapsed().as_millis() % 1000 < 100 + { + info!( + "TLS state: handshaking={}, wants_read={}, wants_write={}", + tls_conn.conn.is_handshaking(), + tls_conn.conn.wants_read(), + tls_conn.conn.wants_write() + ); + } + + if !tls_conn.conn.is_handshaking() && !handshake_completed { + info!("TLS handshake complete"); + info!( + "TLS verification: handshake_complete=true, wants_read={}, wants_write={}", + tls_conn.conn.wants_read(), + tls_conn.conn.wants_write() + ); + + match tls_conn.recv(socket) { + Ok(data) if data.is_empty() => { + info!("No unexpected application data waiting to be read"); + } + Ok(data) => { + info!("Unexpected application data received: {} bytes", data.len()); + } + Err(e) => { + info!("TLS recv check failed: {}", e); + } + } + info!("TLS handshake successful with cloudflare"); + break; + } + } + tokio::time::sleep(Duration::from_millis(1)).await; + } + + info!("Test completed"); + Ok(()) +} diff --git a/mixtcp/src/bridge.rs b/mixtcp/src/bridge.rs new file mode 100644 index 00000000000..aea60167b42 --- /dev/null +++ b/mixtcp/src/bridge.rs @@ -0,0 +1,120 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-2.0-only + +use crate::error::MixtcpError; +use nym_ip_packet_requests::codec::MultiIpPacketCodec; +use nym_sdk::stream_wrapper::IpMixStream; +use tokio::sync::mpsc; +use tracing::{error, info}; + +/// Asynchronous bridge between smoltcp device and Mixnet. +/// +/// This component runs in a separate task and handles all asynchronous +/// operations required for outbound communication. It receives packets +/// from the device via channels, bundles them according to IPR protocol +/// (MultiIpPacketCodec) and transmits them through the Mixnet. +/// +/// # Packet Processing Flow +/// +/// Outgoing packets: +/// - Receive from device via channel +/// - Bundle using MultiIpPacketCodec +/// - Send through mixnet via send_ip_packet() +/// +/// Incoming packets: +/// - Poll mixnet with handle_incoming() +/// - Forward to device via channel +/// - Device queues for smoltcp consumption +pub struct NymIprBridge { + /// Connected IPR stream for mixnet communication + stream: IpMixStream, + /// Channel for receiving outgoing packets from device + tx_receiver: mpsc::UnboundedReceiver>, + /// Channel for sending incoming packets to device + rx_sender: mpsc::UnboundedSender>, +} + +impl NymIprBridge { + pub fn new( + stream: IpMixStream, + tx_receiver: mpsc::UnboundedReceiver>, + rx_sender: mpsc::UnboundedSender>, + ) -> Self { + Self { + stream, + tx_receiver, + rx_sender, + } + } + + /// Runs the bridge event loop. + /// + /// This method should be spawned in a separate task. It continuously: + /// - Processes outgoing packets from the device + /// - Polls for incoming packets from the mixnet + /// - Maintains packet statistics + /// + /// The loop exits when channels are closed or an error occurs. + pub async fn run(mut self) -> Result<(), MixtcpError> { + info!("Starting Nym IPR bridge"); + let mut packets_sent = 0; + let mut packets_received = 0; + + loop { + tokio::select! { + // Outgoing packets from smoltcp layer above. + Some(packet) = self.tx_receiver.recv() => { + info!("Bridge sending {} byte packet to mixnet", packet.len()); + + // Log packet details for debugging + if packet.len() >= 20 { + let version = (packet[0] >> 4) & 0xF; + let proto = packet[9]; + let src_ip = &packet[12..16]; + let dst_ip = &packet[16..20]; + info!( + "Outgoing IPv{} packet: proto={}, src={}.{}.{}.{}, dst={}.{}.{}.{}", + version, proto, + src_ip[0], src_ip[1], src_ip[2], src_ip[3], + dst_ip[0], dst_ip[1], dst_ip[2], dst_ip[3] + ); + } + + // Necessary to bundle for IPR! See stream_wrapper_ipr.rs tests. + let bundled = MultiIpPacketCodec::bundle_one_packet(packet.into()); + if let Err(e) = self.stream.send_ip_packet(&bundled).await { + error!("Failed to send packet through mixnet: {}", e); + } else { + packets_sent += 1; + info!("Total packets sent: {}", packets_sent); + } + } + + // Poll for incoming packets from mixnet + Ok(packets) = self.stream.handle_incoming() => { + if !packets.is_empty() { + info!("Bridge received {} packets from mixnet", packets.len()); + for packet in packets { + info!("Incoming packet: {} bytes", packet.len()); + + // Forward to device via channel + if self.rx_sender.send(packet.to_vec()).is_err() { + error!("Failed to send packet to device - receiver dropped"); + return Err(MixtcpError::ChannelClosed); + } + packets_received += 1; + info!("Total packets received: {}", packets_received); + } + } + } + + else => { + info!("Bridge shutting down. Sent: {}, Received: {}", packets_sent, packets_received); + break; + } + } + } + + Ok(()) + } +} diff --git a/mixtcp/src/device.rs b/mixtcp/src/device.rs new file mode 100644 index 00000000000..f020459a02d --- /dev/null +++ b/mixtcp/src/device.rs @@ -0,0 +1,165 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-2.0-only + +use smoltcp::{ + phy::{Device, DeviceCapabilities, Medium, RxToken, TxToken}, + time::Instant, +}; +use std::collections::VecDeque; +use tokio::sync::mpsc; +use tracing::{info, warn}; + +/// # Overview +/// We need something to bridge the async / sync weirdness (Device trait fns are sync, IpMixStream fns are +/// async) in a way that allows for the `NymIprDevice` to look and act like any other device. +/// +/// We need to be polling the queue to/from the NymIprBridge, hence the addition of the +/// mpsc channels in the Device struct and the extra fns. +/// +/// # Architecture +/// smoltcp (sync) <-> NymIprDevice <-> channels <-> NymIprBridge <-> Mixnet (async) +/// +/// The device maintains a receive queue for packets coming from the mixnet and +/// uses unbounded channels to communicate with the bridge task that handles the +/// actual mixnet I/O. We poll the channel in receive() to move packets via mpsc +/// from async to sync world. +/// +/// This way no blocking from smoltcp + allows for concurrency. +/// +/// Adapter pattern between sync polling-based I/O and async event-based I/O. +pub struct NymIprDevice { + // Receive queue for packets coming from the mixnet + rx_queue: VecDeque>, + + // Channel to send packets to the bridge task + tx_sender: mpsc::UnboundedSender>, + + // Device capabilities + capabilities: DeviceCapabilities, + + // Channel to receive packets from the bridge task + rx_receiver: mpsc::UnboundedReceiver>, +} + +impl NymIprDevice { + pub fn new( + tx_sender: mpsc::UnboundedSender>, + rx_receiver: mpsc::UnboundedReceiver>, + ) -> Self { + let mut capabilities = DeviceCapabilities::default(); + capabilities.medium = Medium::Ip; + // Standard MTU for IP packets - TODO make configurable + capabilities.max_transmission_unit = 1500; + // Process one packet at a time. TODO experiment with this + capabilities.max_burst_size = Some(1); + + Self { + rx_queue: VecDeque::new(), + tx_sender, + capabilities, + rx_receiver, + } + } + + /// Poll for new packets from the bridge + fn poll_rx_queue(&mut self) { + // Try to receive all available packets without blocking, queue them for smoltcp consumption. + while let Ok(packet) = self.rx_receiver.try_recv() { + info!("Received packet of {} bytes from bridge", packet.len()); + self.rx_queue.push_back(packet); + } + } + + pub fn tx_sender(&self) -> mpsc::UnboundedSender> { + self.tx_sender.clone() + } + + /// Get the receiver for external use + pub fn rx_receiver(&self) -> mpsc::UnboundedReceiver> { + // Create a new channel and return the receiver + // This is a bit of a hack but necessary for the current architecture + let (_tx, rx) = mpsc::unbounded_channel(); + // We just need the receiver for testing + rx + } +} + +impl Device for NymIprDevice { + type RxToken<'a> + = NymRxToken + where + Self: 'a; + type TxToken<'a> + = NymTxToken + where + Self: 'a; + + fn receive(&mut self, _timestamp: Instant) -> Option<(Self::RxToken<'_>, Self::TxToken<'_>)> { + // Poll for new packets from the async bridge + self.poll_rx_queue(); + + // Check if we have a packet to deliver + let packet = self.rx_queue.pop_front()?; + + // Create tokens - RxToken owns the packet data + let rx_token = NymRxToken { buffer: packet }; + let tx_token = NymTxToken { + tx_sender: self.tx_sender.clone(), + }; + + Some((rx_token, tx_token)) + } + + fn transmit(&mut self, _timestamp: Instant) -> Option> { + // We can always transmit (channel will buffer) + Some(NymTxToken { + tx_sender: self.tx_sender.clone(), + }) + } + + fn capabilities(&self) -> DeviceCapabilities { + self.capabilities.clone() + } +} + +/// Receive token - owns the packet buffer +pub struct NymRxToken { + buffer: Vec, +} + +impl RxToken for NymRxToken { + fn consume(self, f: F) -> R + where + F: FnOnce(&[u8]) -> R, + { + info!("Consuming RX packet of {} bytes", self.buffer.len()); + f(&self.buffer) + } +} + +/// Transmit token - holds channel sender +pub struct NymTxToken { + tx_sender: mpsc::UnboundedSender>, +} + +impl TxToken for NymTxToken { + fn consume(self, len: usize, f: F) -> R + where + F: FnOnce(&mut [u8]) -> R, + { + // Create buffer for the packet + let mut buffer = vec![0u8; len]; + + // Let smoltcp fill the packet + let result = f(&mut buffer); + + // Send raw packet to the bridge task for transmission + if let Err(e) = self.tx_sender.send(buffer) { + warn!("Failed to send packet to bridge: {}", e); + } else { + info!("Sent {} byte packet to bridge", len); + } + + result + } +} diff --git a/mixtcp/src/error.rs b/mixtcp/src/error.rs new file mode 100644 index 00000000000..56d80807d11 --- /dev/null +++ b/mixtcp/src/error.rs @@ -0,0 +1,52 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-2.0-only + +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum MixtcpError { + #[error("Channel closed")] + ChannelClosed, + + #[error("Not connected to IPR")] + NotConnected, + + #[error("Nym SDK error: {0}")] + NymSdk(#[from] nym_sdk::Error), + + #[error("TLS handshake failed")] + TlsHandshakeFailed, + + #[error("TLS encrypt/decrypt error")] + TlsCrypto, + + #[error("DNS err placeholder")] + InvalidDnsName, + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("HTTP parse failed")] + HttpParseFailed, + + #[error("Invalid URL")] + InvalidUrl, + + #[error("Mixnet connection failed")] + MixnetConnectionFailed, + + #[error("Request timeout")] + Timeout, + + #[error("TCP connection failed")] + TcpConnectionFailed, + + #[error("Response read failed")] + ResponseReadFailed, + + #[error("No response received")] + NoResponseReceived, + + #[error("Invalid HTTP response")] + InvalidHttpResponse, +} diff --git a/mixtcp/src/lib.rs b/mixtcp/src/lib.rs new file mode 100644 index 00000000000..33fa66cba0a --- /dev/null +++ b/mixtcp/src/lib.rs @@ -0,0 +1,49 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-2.0-only + +mod bridge; +mod device; +mod error; + +pub use bridge::NymIprBridge; +pub use device::NymIprDevice; +pub use error::MixtcpError; + +use nym_ip_packet_requests::IpPair; +use nym_sdk::stream_wrapper::IpMixStream; +use tokio::sync::mpsc; + +/// Create a connected smoltcp device and async bridge for the tunneling packets through the +/// Mixnet to remote hosts via an IPR. +/// +/// This function handles the complete setup process: +/// - Ensures the IPR stream is connected +/// - Retrieves allocated IP addresses +/// - Creates communication channels +/// - Constructs the device and bridge components +pub async fn create_device( + mut ipr_stream: IpMixStream, +) -> Result<(NymIprDevice, NymIprBridge, IpPair), MixtcpError> { + // Ensure the stream is connected + if !ipr_stream.is_connected() { + ipr_stream.connect_tunnel().await?; + } + + // Get the allocated IPs before moving the stream - need these for proper packet creation + // further 'up' the flow in the code calling this fn (see examples/tcp_connect.rs). + let allocated_ips = *ipr_stream + .allocated_ips() + .ok_or(MixtcpError::NotConnected)?; + + // Create channels for device <-> bridge communication + let (tx_to_bridge, tx_from_device) = mpsc::unbounded_channel(); + let (rx_to_device, rx_from_bridge) = mpsc::unbounded_channel(); + + // Create device + let device = NymIprDevice::new(tx_to_bridge, rx_from_bridge); + + // Create bridge (moves ipr_stream) + let bridge = NymIprBridge::new(ipr_stream, tx_from_device, rx_to_device); + + Ok((device, bridge, allocated_ips)) +} diff --git a/nym-api/src/network_monitor/monitor/processor.rs b/nym-api/src/network_monitor/monitor/processor.rs index 5eb6f25a8a2..e84cd0f77f3 100644 --- a/nym-api/src/network_monitor/monitor/processor.rs +++ b/nym-api/src/network_monitor/monitor/processor.rs @@ -17,7 +17,7 @@ use std::ops::Deref; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; use thiserror::Error; -use tracing::{error, trace, warn}; +use tracing::{trace, warn}; pub(crate) type ReceivedProcessorSender = mpsc::UnboundedSender; pub(crate) type ReceivedProcessorReceiver = mpsc::UnboundedReceiver; diff --git a/nym-api/src/node_performance/provider/mod.rs b/nym-api/src/node_performance/provider/mod.rs index aadc61a1d59..f8b1bf61a4a 100644 --- a/nym-api/src/node_performance/provider/mod.rs +++ b/nym-api/src/node_performance/provider/mod.rs @@ -8,7 +8,7 @@ use nym_api_requests::models::RoutingScore; use nym_mixnet_contract_common::{EpochId, NodeId}; use std::collections::HashMap; use thiserror::Error; -use tracing::{debug, error}; +use tracing::debug; pub(crate) mod contract_provider; pub(crate) mod legacy_storage_provider; diff --git a/nym-api/src/unstable_routes/v1/account/models.rs b/nym-api/src/unstable_routes/v1/account/models.rs index 1d802f8aa0d..40c441a8240 100644 --- a/nym-api/src/unstable_routes/v1/account/models.rs +++ b/nym-api/src/unstable_routes/v1/account/models.rs @@ -4,7 +4,6 @@ use cosmwasm_std::{Addr, Coin}; use nym_topology::NodeId; use serde::{Deserialize, Serialize}; -use utoipa::schema; #[derive(Clone, Debug, Serialize, Deserialize, utoipa::ToSchema, utoipa::ToResponse)] #[schema(title = "Coin")] diff --git a/nym-gateway-probe/src/icmp.rs b/nym-gateway-probe/src/icmp.rs index ec757623f18..7c7cd4bde7b 100644 --- a/nym-gateway-probe/src/icmp.rs +++ b/nym-gateway-probe/src/icmp.rs @@ -20,7 +20,7 @@ pub fn icmp_identifier() -> u16 { } pub async fn send_ping_v4( - mixnet_client: &MixnetClient, + mixnet_client: &mut MixnetClient, our_ips: IpPair, sequence_number: u16, destination: Ipv4Addr, @@ -42,7 +42,7 @@ pub async fn send_ping_v4( } pub async fn send_ping_v6( - mixnet_client: &MixnetClient, + mixnet_client: &mut MixnetClient, our_ips: IpPair, sequence_number: u16, destination: Ipv6Addr, diff --git a/nym-gateway-probe/src/lib.rs b/nym-gateway-probe/src/lib.rs index 2224c911e01..c85aea2b482 100644 --- a/nym-gateway-probe/src/lib.rs +++ b/nym-gateway-probe/src/lib.rs @@ -790,7 +790,7 @@ async fn do_ping_exit( } async fn send_icmp_pings( - mixnet_client: &MixnetClient, + mixnet_client: &mut MixnetClient, our_ips: IpPair, exit_router_address: Recipient, ) -> anyhow::Result<()> { diff --git a/nym-ip-packet-client/Cargo.toml b/nym-ip-packet-client/Cargo.toml index 86b0e218302..8d5ce3e7493 100644 --- a/nym-ip-packet-client/Cargo.toml +++ b/nym-ip-packet-client/Cargo.toml @@ -15,10 +15,9 @@ workspace = true bincode.workspace = true bytes.workspace = true futures.workspace = true +nym-ip-packet-requests = { path = "../common/ip-packet-requests" } +nym-sdk = {path = "../sdk/rust/nym-sdk" } thiserror.workspace = true tokio-util.workspace = true tokio.workspace = true tracing.workspace = true - -nym-sdk = { path = "../sdk/rust/nym-sdk" } -nym-ip-packet-requests = { path = "../common/ip-packet-requests" } diff --git a/nym-ip-packet-client/src/connect.rs b/nym-ip-packet-client/src/connect.rs index 5a37cf6d827..1f243523025 100644 --- a/nym-ip-packet-client/src/connect.rs +++ b/nym-ip-packet-client/src/connect.rs @@ -83,7 +83,7 @@ impl IprClientConnect { self.listen_for_connect_response(request_id).await } - async fn send_connect_request(&self, ip_packet_router_address: Recipient) -> Result { + async fn send_connect_request(&mut self, ip_packet_router_address: Recipient) -> Result { let (request, request_id) = IpPacketRequest::new_connect_request(None); // We use 20 surbs for the connect request because typically the IPR is configured to have diff --git a/nym-ip-packet-client/src/error.rs b/nym-ip-packet-client/src/error.rs index b88c332f674..f74ab688a49 100644 --- a/nym-ip-packet-client/src/error.rs +++ b/nym-ip-packet-client/src/error.rs @@ -18,6 +18,9 @@ pub enum Error { )] ReceivedResponseWithNewVersion { expected: u8, received: u8 }, + #[error("got reply for connect request, but it appears intended for the wrong address?")] + GotReplyIntendedForWrongAddress, + #[error("unexpected connect response")] UnexpectedConnectResponse, @@ -41,6 +44,10 @@ pub enum Error { #[error(transparent)] Bincode(#[from] bincode::Error), + #[error("failed to create connect request")] + FailedToCreateConnectRequest { + source: nym_ip_packet_requests::sign::SignatureError, + }, } // Result type based on our error type diff --git a/nym-network-monitor/src/handlers.rs b/nym-network-monitor/src/handlers.rs index 3b848fdcdbb..05f753cd89d 100644 --- a/nym-network-monitor/src/handlers.rs +++ b/nym-network-monitor/src/handlers.rs @@ -220,7 +220,7 @@ async fn send_receive_mixnet(state: AppState) -> Result { }); let send_handle = tokio::spawn(async move { - let mixnet_sender = sender.read().await.split_sender(); + let mut mixnet_sender = sender.read().await.split_sender(); let our_address = *sender.read().await.nym_address(); match timeout( Duration::from_secs(5), diff --git a/nym-outfox/src/lib.rs b/nym-outfox/src/lib.rs index e10d30e8465..0d278c06d72 100644 --- a/nym-outfox/src/lib.rs +++ b/nym-outfox/src/lib.rs @@ -1,3 +1,6 @@ +// MAX: temp ignore deprecated, can be dealt with in its own PR +#![allow(deprecated)] // silences clippy warning: deprecated associated function `chacha20::cipher::generic_array::GenericArray::::from_slice`: please upgrade to generic-array 1.x - TODO + pub mod constants; pub mod error; pub mod format; diff --git a/sdk/ffi/shared/src/lib.rs b/sdk/ffi/shared/src/lib.rs index eddd9b1f3e3..6f653156ddd 100644 --- a/sdk/ffi/shared/src/lib.rs +++ b/sdk/ffi/shared/src/lib.rs @@ -84,12 +84,12 @@ pub fn send_message_internal( message: &str, // TODO add Option, if Some(surb_amount) call send_message() instead with specified #, else send_plain_message as this uses the default ) -> Result<(), Error> { - let client = NYM_CLIENT.lock().expect("could not lock NYM_CLIENT"); + let mut client = NYM_CLIENT.lock().expect("could not lock NYM_CLIENT"); if client.is_none() { bail!("Client is not yet initialised"); } let nym_client = client - .as_ref() + .as_mut() .ok_or_else(|| anyhow!("could not get client as_ref()"))?; RUNTIME.block_on(async move { @@ -102,12 +102,12 @@ pub fn send_message_internal( // TODO send_raw_message_internal pub fn reply_internal(recipient: AnonymousSenderTag, message: &str) -> Result<(), Error> { - let client = NYM_CLIENT.lock().expect("could not lock NYM_CLIENT"); + let mut client = NYM_CLIENT.lock().expect("could not lock NYM_CLIENT"); if client.is_none() { bail!("Client is not yet initialised"); } let nym_client = client - .as_ref() + .as_mut() .ok_or_else(|| anyhow!("could not get client as_ref()"))?; RUNTIME.block_on(async move { diff --git a/sdk/rust/nym-sdk/Cargo.toml b/sdk/rust/nym-sdk/Cargo.toml index 523e5801c52..f218a4f74c3 100644 --- a/sdk/rust/nym-sdk/Cargo.toml +++ b/sdk/rust/nym-sdk/Cargo.toml @@ -15,13 +15,14 @@ name = "nym-proxy-client" path = "src/tcp_proxy/bin/proxy_client.rs" [dependencies] -async-trait = { workspace = true } -bip39 = { workspace = true } nym-client-core = { path = "../../../common/client-core", features = [ "fs-credentials-storage", "fs-surb-storage", "fs-gateways-storage", ] } +async-trait = { workspace = true } +bip39 = { workspace = true } + nym-crypto = { path = "../../../common/crypto" } nym-gateway-requests = { path = "../../../common/gateway-requests" } nym-bandwidth-controller = { path = "../../../common/bandwidth-controller" } @@ -46,6 +47,7 @@ nym-sphinx-addressing = { path = "../../../common/nymsphinx/addressing" } nym-bin-common = { path = "../../../common/bin-common", features = [ "basic_tracing", ] } + bytecodec = { workspace = true } httpcodec = { workspace = true } bytes = { workspace = true } @@ -61,6 +63,11 @@ url = { workspace = true } toml = { workspace = true } tempfile = { workspace = true } +nym-ip-packet-requests = { path = "../../../common/ip-packet-requests" } +pnet_packet = { workspace = true } +nym-config = { path = "../../../common/config" } + + # tcpproxy dependencies clap = { workspace = true, features = ["derive"] } anyhow.workspace = true diff --git a/sdk/rust/nym-sdk/README.md b/sdk/rust/nym-sdk/README.md index 16f19894145..a3cf022683f 100644 --- a/sdk/rust/nym-sdk/README.md +++ b/sdk/rust/nym-sdk/README.md @@ -2,7 +2,8 @@ This repo contains several components: - `mixnet`: exposes Nym Client builders and methods. This is useful if you want to interact directly with the Client, or build transport abstractions. -- `tcp_proxy`: exposes functionality to set up client/server instances that expose a localhost TcpSocket to read/write to like a 'normal' socket connection. `tcp_proxy/bin/` contains standalone `nym-proxy-client` and `nym-proxy-server` binaries. +- `tcp_proxy`: exposes functionality to set up client/server instances that expose a localhost TcpSocket to read/write to like a 'normal' socket connection. `tcp_proxy/bin/` contains standalone `nym-proxy-client` and `nym-proxy-server` binaries. *Note: this module is being superceded by the `stream_wrapper` module, and this module will not have features added to it in the future.* - `clientpool`: a configurable pool of ephemeral Nym Clients which can be created as a background process and quickly grabbed. +- `stream_wrapper`: made up of two parts: a TCP-Socket-like abstraction (`mixnet_stream_wrapper.rs`) for a Nym Client, and an abstraction built on top of this (`mixnet_stream_wrapper_ipr`) which allows for client-side integrations to send IP packets through Exit Gateways' IpPacketRouter, and use the Mixnet as a proxy. For an example of where this is used, see the `mixtcp` crate. Documentation can be found [here](https://nym.com/docs/developers/rust). diff --git a/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs b/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs index b42ad5ef048..38f7c526e6d 100644 --- a/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs +++ b/sdk/rust/nym-sdk/examples/parallel_sending_and_receiving.rs @@ -16,7 +16,7 @@ async fn main() { let our_address = *client.nym_address(); println!("Our client nym address is: {our_address}"); - let sender = client.split_sender(); + let mut sender = client.split_sender(); // receiving task let receiving_task_handle = tokio::spawn(async move { diff --git a/sdk/rust/nym-sdk/src/error.rs b/sdk/rust/nym-sdk/src/error.rs index af7706ec925..525a479d971 100644 --- a/sdk/rust/nym-sdk/src/error.rs +++ b/sdk/rust/nym-sdk/src/error.rs @@ -1,6 +1,9 @@ use nym_validator_client::nyxd::error::NyxdError; use std::path::PathBuf; +use nym_ip_packet_requests::v8::response::{ConnectFailureReason, IpPacketResponseData}; +use nym_validator_client::nym_api::error::NymAPIError; + /// Top-level Error enum for the mixnet client and its relevant types. #[derive(Debug, thiserror::Error)] pub enum Error { @@ -99,6 +102,56 @@ pub enum Error { #[error("Failed to get shutdown tracker from the task runtime registry: {0}")] RegistryAccess(#[from] nym_task::RegistryAccessError), + #[error("nymsphinx receiver error: {0}")] + MessageRecovery(#[from] nym_sphinx::receiver::MessageRecoveryError), + + #[error("client not connected")] + IprStreamClientNotConnected, + + #[error("client already connected or connecting")] + IprStreamClientAlreadyConnectedOrConnecting, + + #[error("trying to send an anonymous reply but peer surb tag is not set")] + MixStreamSurbTagNotSet, + + #[error("trying to send an outgoing message but receipient address is not set")] + MixStreamRecipientNotSet, + + #[error("listening for connection response timed out")] + IPRConnectResponseTimeout, + + #[error("no next frame: assuming stream is closed")] + IPRClientStreamClosed, + + #[error("expected control response, got {0:?}")] + UnexpectedResponseType(IpPacketResponseData), + + #[error("connect denied: {0:?}")] + ConnectDenied(ConnectFailureReason), + + #[error("api directory error: {0}")] + GatewayDirectoryError(#[from] NymAPIError), + + #[error("did not receive Validator endpoint details")] + NoValidatorDetailsAvailable, + + #[error("did not receive URL")] + NoValidatorAPIUrl, + + #[error("did not receive NymVPN API URL")] + NoNymAPIUrl, + + #[error("no available gateway")] + NoGatewayAvailable, + + #[error("no IPR address on selected gateway")] + NoIPRAvailable, + + #[error("message version check failed: {0}")] + IPRMessageVersionCheckFailed(String), + + #[error("no response id found in connect response")] + IPRNoId, } impl Error { diff --git a/sdk/rust/nym-sdk/src/ip_packet_client.rs b/sdk/rust/nym-sdk/src/ip_packet_client.rs new file mode 100644 index 00000000000..c753206a03a --- /dev/null +++ b/sdk/rust/nym-sdk/src/ip_packet_client.rs @@ -0,0 +1,14 @@ +// Copyright 2023-2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +mod connect; +mod error; +pub mod helpers; +pub mod listener; + +pub use connect::IprClientConnect; +pub use error::Error; +pub use listener::{IprListener, MixnetMessageOutcome}; + +// Re-export the currently used version +pub use nym_ip_packet_requests::v8 as current; diff --git a/sdk/rust/nym-sdk/src/ip_packet_client/README.md b/sdk/rust/nym-sdk/src/ip_packet_client/README.md new file mode 100644 index 00000000000..06b57c3b6f6 --- /dev/null +++ b/sdk/rust/nym-sdk/src/ip_packet_client/README.md @@ -0,0 +1,7 @@ +# Modified `nym-ip-packet-client` +This set of code is made up of functions from several crates from the `nym-vpn-client` monorepo which had to be imported and modified to avoid a circular dependency on the `nym-sdk` package for use in the `mixnet_stream_wrapper_ipr` module, and is made up of: +- a modified version of (basically) the entire `nym-ip-packet-client` +- a set of IP Packet helper functions from the `nym-gateway-probe` +- a set of helpers & types from the `nym-connection-monitor` + +All of these can be found in [`nym-vpn-client/nym-vpn-core/crates/`](https://github.com/nymtech/nym-vpn-client/tree/develop/nym-vpn-core/crates). diff --git a/sdk/rust/nym-sdk/src/ip_packet_client/connect.rs b/sdk/rust/nym-sdk/src/ip_packet_client/connect.rs new file mode 100644 index 00000000000..a8fdca86b3c --- /dev/null +++ b/sdk/rust/nym-sdk/src/ip_packet_client/connect.rs @@ -0,0 +1,196 @@ +// Copyright 2023-2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use std::{sync::Arc, time::Duration}; + +pub use crate::mixnet::{ + InputMessage, MixnetClient, MixnetClientSender, MixnetMessageSender, Recipient, + TransmissionLane, +}; +use nym_ip_packet_requests::IpPair; +use tokio::time::sleep; +use tokio_util::sync::CancellationToken; +use tracing::{debug, error}; + +use super::error::{Error, Result}; +use crate::{ + ip_packet_client::current::{ + request::IpPacketRequest, + response::{ + ConnectResponse, ConnectResponseReply, ControlResponse, IpPacketResponse, + IpPacketResponseData, + }, + }, + ip_packet_client::helpers::check_ipr_message_version, +}; + +pub type SharedMixnetClient = Arc>>; + +const IPR_CONNECT_TIMEOUT: Duration = Duration::from_secs(10); + +#[derive(Clone, Debug, PartialEq, Eq)] +enum ConnectionState { + Disconnected, + Connecting, + Connected, + #[allow(unused)] + Disconnecting, +} + +pub struct IprClientConnect { + // During connection we need the mixnet client, but once connected we expect to setup a channel + // from the main mixnet listener at the top-level. + // As such, we drop the shared mixnet client once we're connected. + mixnet_client: SharedMixnetClient, + mixnet_sender: MixnetClientSender, + connected: ConnectionState, + cancel_token: CancellationToken, +} + +impl IprClientConnect { + pub async fn new(mixnet_client: SharedMixnetClient, cancel_token: CancellationToken) -> Self { + let mixnet_sender = mixnet_client.lock().await.as_ref().unwrap().split_sender(); + Self { + mixnet_client, + mixnet_sender, + connected: ConnectionState::Disconnected, + cancel_token, + } + } + + pub async fn connect(&mut self, ip_packet_router_address: Recipient) -> Result { + if self.connected != ConnectionState::Disconnected { + return Err(Error::AlreadyConnected); + } + + tracing::info!("Connecting to exit gateway"); + self.connected = ConnectionState::Connecting; + match self.connect_inner(ip_packet_router_address).await { + Ok(ips) => { + debug!("Successfully connected to the ip-packet-router"); + self.connected = ConnectionState::Connected; + Ok(ips) + } + Err(err) => { + error!("Failed to connect to the ip-packet-router: {:?}", err); + self.connected = ConnectionState::Disconnected; + Err(err) + } + } + } + + async fn connect_inner(&mut self, ip_packet_router_address: Recipient) -> Result { + let request_id = self.send_connect_request(ip_packet_router_address).await?; + + debug!("Waiting for reply..."); + self.listen_for_connect_response(request_id).await + } + + async fn send_connect_request(&mut self, ip_packet_router_address: Recipient) -> Result { + let (request, request_id) = IpPacketRequest::new_connect_request(None); + + // We use 20 surbs for the connect request because typically the IPR is configured to have + // a min threshold of 10 surbs that it reserves for itself to request additional surbs. + let surbs = 20; + self.mixnet_sender + .send(create_input_message( + Recipient::from(ip_packet_router_address), + request, + surbs, + )) + .await + .map_err(|err| Error::SdkError(Box::new(err)))?; + + Ok(request_id) + } + + async fn handle_connect_response(&self, response: ConnectResponse) -> Result { + debug!("Handling dynamic connect response"); + match response.reply { + ConnectResponseReply::Success(r) => Ok(r.ips), + ConnectResponseReply::Failure(reason) => Err(Error::ConnectRequestDenied { reason }), + } + } + + async fn handle_ip_packet_router_response(&self, response: IpPacketResponse) -> Result { + let control_response = match response.data { + IpPacketResponseData::Control(control_response) => control_response, + _ => { + error!("Received non-control response while waiting for connect response"); + return Err(Error::UnexpectedConnectResponse); + } + }; + + match *control_response { + ControlResponse::Connect(resp) => self.handle_connect_response(resp).await, + response => { + error!("Unexpected response: {response:?}"); + Err(Error::UnexpectedConnectResponse) + } + } + } + + async fn listen_for_connect_response(&self, request_id: u64) -> Result { + // Connecting is basically synchronous from the perspective of the mixnet client, so it's safe + // to just grab ahold of the mutex and keep it until we get the response. + let mut mixnet_client_handle = self.mixnet_client.lock().await; + let mixnet_client = mixnet_client_handle.as_mut().unwrap(); + + let timeout = sleep(IPR_CONNECT_TIMEOUT); + tokio::pin!(timeout); + + loop { + tokio::select! { + _ = self.cancel_token.cancelled() => { + error!("Cancelled while waiting for reply to connect request"); + return Err(Error::Cancelled); + }, + _ = &mut timeout => { + error!("Timed out waiting for reply to connect request"); + return Err(Error::TimeoutWaitingForConnectResponse); + }, + msgs = mixnet_client.wait_for_messages() => match msgs { + None => { + return Err(Error::NoMixnetMessagesReceived); + } + Some(msgs) => { + for msg in msgs { + // Confirm that the version is correct + if let Err(err) = check_ipr_message_version(&msg) { + tracing::info!("Mixnet message version mismatch: {err}"); + continue; + } + + // Then we deserialize the message + tracing::debug!("IprClient: got message while waiting for connect response"); + let Ok(response) = IpPacketResponse::from_reconstructed_message(&msg) else { + // This is ok, it's likely just one of our self-pings + tracing::debug!("Failed to deserialize mixnet message"); + continue; + }; + + if response.id() == Some(request_id) { + tracing::debug!("Got response with matching id"); + return self.handle_ip_packet_router_response(response).await; + } + } + } + } + } + } + } +} + +fn create_input_message( + recipient: Recipient, + request: IpPacketRequest, + surbs: u32, +) -> InputMessage { + InputMessage::new_anonymous( + recipient, + request.to_bytes().unwrap(), + surbs, + TransmissionLane::General, + None, + ) +} diff --git a/sdk/rust/nym-sdk/src/ip_packet_client/error.rs b/sdk/rust/nym-sdk/src/ip_packet_client/error.rs new file mode 100644 index 00000000000..a1fbcd2230a --- /dev/null +++ b/sdk/rust/nym-sdk/src/ip_packet_client/error.rs @@ -0,0 +1,79 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::ip_packet_client::current::response::ConnectFailureReason; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("nym sdk")] + // SdkError(#[source] Box), + SdkError(#[source] Box), + + #[error( + "received response with version v{received}, the client is too new and can only understand v{expected}" + )] + ReceivedResponseWithOldVersion { expected: u8, received: u8 }, + + #[error( + "received response with version v{received}, the client is too old and can only understand v{expected}" + )] + ReceivedResponseWithNewVersion { expected: u8, received: u8 }, + + #[error("got reply for connect request, but it appears intended for the wrong address?")] + GotReplyIntendedForWrongAddress, + + #[error("unexpected connect response")] + UnexpectedConnectResponse, + + #[error("mixnet client stopped returning responses")] + NoMixnetMessagesReceived, + + #[error("timeout waiting for connect response from exit gateway (ipr)")] + TimeoutWaitingForConnectResponse, + + #[error("connection cancelled")] + Cancelled, + + #[error("connect request denied: {reason}")] + ConnectRequestDenied { reason: ConnectFailureReason }, + + #[error("failed to get version from message")] + NoVersionInMessage, + + #[error("already connected to the mixnet")] + AlreadyConnected, + + #[error("failed to create connect request")] + FailedToCreateConnectRequest { + source: nym_ip_packet_requests::sign::SignatureError, + }, + + /// Below error types are from the nym-connection-monitor crate + #[error( + "timeout waiting for mixnet self ping, the entry gateway is not routing our mixnet traffic" + )] + TimeoutWaitingForMixnetSelfPing, + + #[error("failed to serialize message")] + FailedToSerializeMessage { + #[from] + source: bincode::Error, + }, + + #[error("failed to create icmp echo request packet")] + IcmpEchoRequestPacketCreationFailure, + + #[error("failed to create icmp packet")] + IcmpPacketCreationFailure, + + #[error("failed to create ipv4 packet")] + Ipv4PacketCreationFailure, +} + +impl From for Error { + fn from(err: crate::error::Error) -> Self { + Error::SdkError(Box::new(err)) + } +} + +pub type Result = std::result::Result; diff --git a/sdk/rust/nym-sdk/src/ip_packet_client/helpers.rs b/sdk/rust/nym-sdk/src/ip_packet_client/helpers.rs new file mode 100644 index 00000000000..d6eebf9abf0 --- /dev/null +++ b/sdk/rust/nym-sdk/src/ip_packet_client/helpers.rs @@ -0,0 +1,368 @@ +// Copyright 2023-2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::error::{Error, Result}; +use crate::ip_packet_client::current::VERSION as CURRENT_VERSION; +pub use crate::mixnet::ReconstructedMessage; +use nym_config::defaults::mixnet_vpn::{NYM_TUN_DEVICE_ADDRESS_V4, NYM_TUN_DEVICE_ADDRESS_V6}; + +use crate::stream_wrapper::IpMixStream; + +use nym_ip_packet_requests::{codec::MultiIpPacketCodec, IpPair}; + +use bytes::Bytes; +use pnet_packet::{ + icmp::{ + echo_reply::EchoReplyPacket, + echo_request::{EchoRequestPacket, MutableEchoRequestPacket}, + IcmpPacket, + }, + icmpv6, + ipv4::{Ipv4Packet, MutableIpv4Packet}, + ipv6::{Ipv6Packet, MutableIpv6Packet}, + Packet, +}; +use std::cmp::Ordering; +use std::net::{Ipv4Addr, Ipv6Addr}; + +/** + * This function is from the original nym-ip-packet-client crate. + */ +pub(crate) fn check_ipr_message_version(message: &ReconstructedMessage) -> Result<()> { + // Assuming it's a IPR message, it will have a version as its first byte + if let Some(version) = message.message.first() { + match version.cmp(&CURRENT_VERSION) { + Ordering::Greater => Err(Error::ReceivedResponseWithNewVersion { + expected: CURRENT_VERSION, + received: *version, + }), + Ordering::Less => Err(Error::ReceivedResponseWithOldVersion { + expected: CURRENT_VERSION, + received: *version, + }), + Ordering::Equal => { + // We're good + Ok(()) + } + } + } else { + Err(Error::NoVersionInMessage) + } +} + +/** + * Functions below are from the nym-connection-monitor crate. + */ +pub fn create_icmpv4_echo_request( + sequence_number: u16, + identifier: u16, +) -> Result> { + let buffer = vec![0; 64]; + let mut icmp_echo_request = MutableEchoRequestPacket::owned(buffer) + .ok_or(Error::IcmpEchoRequestPacketCreationFailure)?; + + // Configure the ICMP echo request packet + icmp_echo_request.set_identifier(identifier); + icmp_echo_request.set_sequence_number(sequence_number); + icmp_echo_request.set_icmp_type(pnet_packet::icmp::IcmpTypes::EchoRequest); + icmp_echo_request.set_icmp_code(pnet_packet::icmp::IcmpCode::new(0)); + + // Calculate checksum once we've set all the fields + let icmp_packet = + IcmpPacket::new(icmp_echo_request.packet()).ok_or(Error::IcmpPacketCreationFailure)?; + let checksum = pnet_packet::icmp::checksum(&icmp_packet); + icmp_echo_request.set_checksum(checksum); + + Ok(icmp_echo_request.consume_to_immutable()) +} + +pub fn create_icmpv6_echo_request( + sequence_number: u16, + identifier: u16, + source: &Ipv6Addr, + destination: &Ipv6Addr, +) -> Result> { + let buffer = vec![0; 64]; + // let mut icmp_echo_request = MutableEchoRequestPacket::owned(buffer) + let mut icmp_echo_request = icmpv6::echo_request::MutableEchoRequestPacket::owned(buffer) + .ok_or(Error::IcmpEchoRequestPacketCreationFailure)?; + + // Configure the ICMP echo request packet + icmp_echo_request.set_identifier(identifier); + icmp_echo_request.set_sequence_number(sequence_number); + icmp_echo_request.set_icmpv6_type(pnet_packet::icmpv6::Icmpv6Types::EchoRequest); + icmp_echo_request.set_icmpv6_code(pnet_packet::icmpv6::Icmpv6Code::new(0)); + + // Calculate checksum once we've set all the fields + let icmp_packet = icmpv6::Icmpv6Packet::new(icmp_echo_request.packet()) + .ok_or(Error::IcmpPacketCreationFailure)?; + let checksum = pnet_packet::icmpv6::checksum(&icmp_packet, source, destination); + icmp_echo_request.set_checksum(checksum); + + Ok(icmp_echo_request.consume_to_immutable()) +} + +pub fn wrap_icmp_in_ipv4( + icmp_echo_request: EchoRequestPacket, + source: Ipv4Addr, + destination: Ipv4Addr, +) -> Result { + // 20 bytes for IPv4 header + ICMP payload + let total_length = 20 + icmp_echo_request.packet().len(); + // IPv4 header + ICMP payload + let ipv4_buffer = vec![0u8; 20 + icmp_echo_request.packet().len()]; + let mut ipv4_packet = + MutableIpv4Packet::owned(ipv4_buffer).ok_or(Error::Ipv4PacketCreationFailure)?; + + ipv4_packet.set_version(4); + ipv4_packet.set_header_length(5); + ipv4_packet.set_total_length(total_length as u16); + ipv4_packet.set_ttl(64); + ipv4_packet.set_next_level_protocol(pnet_packet::ip::IpNextHeaderProtocols::Icmp); + ipv4_packet.set_source(source); + ipv4_packet.set_destination(destination); + ipv4_packet.set_flags(pnet_packet::ipv4::Ipv4Flags::DontFragment); + ipv4_packet.set_checksum(0); + ipv4_packet.set_payload(icmp_echo_request.packet()); + + let ipv4_checksum = compute_ipv4_checksum(&ipv4_packet.to_immutable()); + ipv4_packet.set_checksum(ipv4_checksum); + + Ok(ipv4_packet.consume_to_immutable()) +} + +pub fn wrap_icmp_in_ipv6( + icmp_echo_request: icmpv6::echo_request::EchoRequestPacket, + source: Ipv6Addr, + destination: Ipv6Addr, +) -> Result { + let ipv6_buffer = vec![0u8; 40 + icmp_echo_request.packet().len()]; + let mut ipv6_packet = + MutableIpv6Packet::owned(ipv6_buffer).ok_or(Error::Ipv4PacketCreationFailure)?; + + ipv6_packet.set_version(6); + ipv6_packet.set_payload_length(icmp_echo_request.packet().len() as u16); + ipv6_packet.set_next_header(pnet_packet::ip::IpNextHeaderProtocols::Icmpv6); + ipv6_packet.set_hop_limit(64); + ipv6_packet.set_source(source); + ipv6_packet.set_destination(destination); + ipv6_packet.set_payload(icmp_echo_request.packet()); + + Ok(ipv6_packet.consume_to_immutable()) +} + +// Compute IPv4 checksum: sum all 16-bit words, add carry, take one's complement +pub(crate) fn compute_ipv4_checksum(header: &Ipv4Packet) -> u16 { + // Header length in 16-bit words + let len = header.get_header_length() as usize * 2; + let mut sum = 0u32; + + for i in 0..len { + let word = ((header.packet()[2 * i] as u32) << 8) | header.packet()[2 * i + 1] as u32; + sum += word; + } + + // Add the carry + while (sum >> 16) > 0 { + sum = (sum & 0xFFFF) + (sum >> 16); + } + + // One's complement + !sum as u16 +} + +pub(crate) fn is_icmp_echo_reply(packet: &Bytes) -> Option<(u16, Ipv4Addr, Ipv4Addr)> { + if let Some(ipv4_packet) = Ipv4Packet::new(packet) { + if let Some(icmp_packet) = IcmpPacket::new(ipv4_packet.payload()) { + if let Some(echo_reply) = EchoReplyPacket::new(icmp_packet.packet()) { + return Some(( + echo_reply.get_identifier(), + ipv4_packet.get_source(), + ipv4_packet.get_destination(), + )); + } + } + } + None +} + +pub(crate) fn is_icmp_v6_echo_reply(packet: &Bytes) -> Option<(u16, Ipv6Addr, Ipv6Addr)> { + if let Some(ipv6_packet) = Ipv6Packet::new(packet) { + if let Some(icmp_packet) = IcmpPacket::new(ipv6_packet.payload()) { + if let Some(echo_reply) = + pnet_packet::icmpv6::echo_reply::EchoReplyPacket::new(icmp_packet.packet()) + { + return Some(( + echo_reply.get_identifier(), + ipv6_packet.get_source(), + ipv6_packet.get_destination(), + )); + } + } + } + None +} + +/** + * Types and functions below are from the nym-connection-monitor crate. + * The `send_ping_v4` + `_v6` functions have been modified to work with the IPMixStream wrapper instead of relying on a shared MixnetClient. + */ +#[derive(Debug)] +pub enum ConnectionStatusEvent { + MixnetSelfPing, + Icmpv4IprTunDevicePingReply, + Icmpv6IprTunDevicePingReply, + Icmpv4IprExternalPingReply, + Icmpv6IprExternalPingReply, +} + +#[derive(Debug, Clone, Default)] +pub struct IpPingReplies { + pub ipr_tun_ip_v4: bool, + pub ipr_tun_ip_v6: bool, + pub external_ip_v4: bool, + pub external_ip_v6: bool, +} + +impl IpPingReplies { + pub fn new() -> Self { + Self::default() + } + + pub fn register_event(&mut self, event: &ConnectionStatusEvent) { + match event { + ConnectionStatusEvent::MixnetSelfPing => {} + ConnectionStatusEvent::Icmpv4IprTunDevicePingReply => self.ipr_tun_ip_v4 = true, + ConnectionStatusEvent::Icmpv6IprTunDevicePingReply => self.ipr_tun_ip_v6 = true, + ConnectionStatusEvent::Icmpv4IprExternalPingReply => self.external_ip_v4 = true, + ConnectionStatusEvent::Icmpv6IprExternalPingReply => self.external_ip_v6 = true, + } + } +} + +pub enum IcmpBeaconReply { + TunDeviceReply, + ExternalPingReply(Ipv4Addr), +} + +pub enum Icmpv6BeaconReply { + TunDeviceReply, + ExternalPingReply(Ipv6Addr), +} + +pub fn icmp_identifier() -> u16 { + 8475 +} + +// The only real change here is that we don't have to use the wrap() function to turn the incoming data in an InputMessage as this is done by the stream abstraction's write_bytes() via `send_ip_packet()`. +pub async fn send_ping_v4( + stream: &mut IpMixStream, + our_ips: &IpPair, + sequence_number: u16, + identifier: u16, + destination: Ipv4Addr, +) -> Result<()> { + let icmp_echo_request = create_icmpv4_echo_request(sequence_number, identifier)?; + let ipv4_packet = wrap_icmp_in_ipv4(icmp_echo_request, our_ips.ipv4, destination)?; + + let bundled_packet = + MultiIpPacketCodec::bundle_one_packet(ipv4_packet.packet().to_vec().into()); + + stream.send_ip_packet(&bundled_packet).await?; + Ok(()) +} + +// One difference to note here is that since the IPR address is part of the stream (stored on connection) we don't have to pass it manually as in the original version of this code. The other diff is the same as the v4 function above re: not having to wrap the incoming in an InputMessage manually. +pub async fn send_ping_v6( + stream: &mut IpMixStream, + our_ips: &IpPair, + sequence_number: u16, + destination: Ipv6Addr, +) -> Result<()> { + let icmp_identifier = icmp_identifier(); + let icmp_echo_request = create_icmpv6_echo_request( + sequence_number, + icmp_identifier, + &our_ips.ipv6, + &destination, + )?; + let ipv6_packet = wrap_icmp_in_ipv6(icmp_echo_request, our_ips.ipv6, destination)?; + + // Wrap the IPv6 packet in a MultiIpPacket + let bundled_packet = + MultiIpPacketCodec::bundle_one_packet(ipv6_packet.packet().to_vec().into()); + + stream.send_ip_packet(&bundled_packet).await?; + + Ok(()) +} + +pub fn check_for_icmp_beacon_reply( + packet: &Bytes, + icmp_beacon_identifier: u16, + our_ips: IpPair, +) -> Option { + match is_icmp_beacon_reply(packet, icmp_beacon_identifier, our_ips.ipv4) { + Some(IcmpBeaconReply::TunDeviceReply) => { + tracing::debug!("Received ping response from ipr tun device"); + return Some(ConnectionStatusEvent::Icmpv4IprTunDevicePingReply); + } + Some(IcmpBeaconReply::ExternalPingReply(_source)) => { + tracing::debug!("Received ping response from an external ip through the ipr"); + return Some(ConnectionStatusEvent::Icmpv4IprExternalPingReply); + } + None => {} + } + + match is_icmp_v6_beacon_reply(packet, icmp_beacon_identifier, our_ips.ipv6) { + Some(Icmpv6BeaconReply::TunDeviceReply) => { + tracing::debug!("Received ping v6 response from ipr tun device"); + return Some(ConnectionStatusEvent::Icmpv6IprTunDevicePingReply); + } + Some(Icmpv6BeaconReply::ExternalPingReply(_source)) => { + tracing::debug!("Received ping v6 response from an external ip through the ipr"); + return Some(ConnectionStatusEvent::Icmpv6IprExternalPingReply); + } + None => {} + } + + None +} + +pub fn is_icmp_beacon_reply( + packet: &Bytes, + identifier: u16, + destination: Ipv4Addr, +) -> Option { + if let Some((reply_identifier, reply_source, reply_destination)) = is_icmp_echo_reply(packet) { + if reply_identifier == identifier && reply_destination == destination { + if reply_source == NYM_TUN_DEVICE_ADDRESS_V4 { + return Some(IcmpBeaconReply::TunDeviceReply); + } else { + // For external replies, we check if the source is NOT the TUN device + // and NOT our own IP (since external hosts reply from their own IPs) + return Some(IcmpBeaconReply::ExternalPingReply(reply_source)); + } + } + } + None +} + +pub fn is_icmp_v6_beacon_reply( + packet: &Bytes, + identifier: u16, + destination: Ipv6Addr, +) -> Option { + if let Some((reply_identifier, reply_source, reply_destination)) = is_icmp_v6_echo_reply(packet) + { + if reply_identifier == identifier && reply_destination == destination { + if reply_source == NYM_TUN_DEVICE_ADDRESS_V6 { + return Some(Icmpv6BeaconReply::TunDeviceReply); + } else { + // For external replies, check if source is NOT the TUN device + return Some(Icmpv6BeaconReply::ExternalPingReply(reply_source)); + } + } + } + None +} diff --git a/sdk/rust/nym-sdk/src/ip_packet_client/listener.rs b/sdk/rust/nym-sdk/src/ip_packet_client/listener.rs new file mode 100644 index 00000000000..20be529e8b9 --- /dev/null +++ b/sdk/rust/nym-sdk/src/ip_packet_client/listener.rs @@ -0,0 +1,133 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use bytes::Bytes; +use futures::StreamExt; +use nym_ip_packet_requests::{codec::MultiIpPacketCodec, v8::response::ControlResponse}; +use nym_sphinx::receiver::ReconstructedMessage; +use tokio_util::codec::FramedRead; +use tracing::{debug, error, info, warn}; + +use crate::{ + ip_packet_client::current::{ + request::{ControlRequest, IpPacketRequest, IpPacketRequestData}, + response::{InfoLevel, IpPacketResponse, IpPacketResponseData}, + }, + ip_packet_client::helpers::check_ipr_message_version, +}; + +pub enum MixnetMessageOutcome { + IpPackets(Vec), + MixnetSelfPing, + Disconnect, +} + +pub struct IprListener {} + +#[derive(Debug, thiserror::Error)] +pub enum IprListenerError { + #[error(transparent)] + IprClientError(#[from] crate::Error), +} + +impl From for IprListenerError { + fn from(err: super::error::Error) -> Self { + match err { + super::error::Error::SdkError(sdk_err) => IprListenerError::IprClientError(*sdk_err), + other => IprListenerError::IprClientError(crate::Error::new_unsupported(format!( + "IP packet error: {}", + other + ))), + } + } +} + +impl IprListener { + pub fn new() -> Self { + Self {} + } + + fn is_mix_ping(&self, request: &IpPacketRequest) -> bool { + match request.data { + IpPacketRequestData::Control(ref control) => { + matches!(**control, ControlRequest::Ping(_)) + } + _ => { + debug!("Received unexpected request: {request:?}"); + false + } + } + } + + pub async fn handle_reconstructed_message( + &mut self, + message: ReconstructedMessage, + ) -> Result, IprListenerError> { + check_ipr_message_version(&message)?; + + match IpPacketResponse::from_reconstructed_message(&message) { + Ok(response) => { + match response.data { + IpPacketResponseData::Data(data_response) => { + // Un-bundle the mixnet message and send the individual IP packets + // to the tun device + let framed_reader = FramedRead::new( + data_response.ip_packet.as_ref(), + MultiIpPacketCodec::new(), + ); + let responses: Vec = framed_reader + .filter_map(|res| async { res.ok().map(|packet| packet.into_bytes()) }) + .collect() + .await; + return Ok(Some(MixnetMessageOutcome::IpPackets(responses))); + } + IpPacketResponseData::Control(control_response) => match *control_response { + ControlResponse::Connect(_) => { + info!("Received connect response when already connected - ignoring"); + } + ControlResponse::Disconnect(_) => { + info!("Received disconnect response"); + return Ok(Some(MixnetMessageOutcome::Disconnect)); + } + ControlResponse::UnrequestedDisconnect(_) => { + info!("Received unrequested disconnect response, ignoring for now"); + } + ControlResponse::Pong(_) => { + info!("Received pong response, ignoring for now"); + } + ControlResponse::Health(_) => { + info!("Received health response, ignoring for now"); + } + ControlResponse::Info(info) => { + let msg = + format!("Received info response from the mixnet: {}", info.reply); + match info.level { + InfoLevel::Info => info!("{msg}"), + InfoLevel::Warn => warn!("{msg}"), + InfoLevel::Error => error!("{msg}"), + } + } + }, + } + } + Err(err) => { + // The exception to when we are not expecting a response, is when we + // are sending a ping to ourselves. + if let Ok(request) = IpPacketRequest::from_reconstructed_message(&message) { + if self.is_mix_ping(&request) { + return Ok(Some(MixnetMessageOutcome::MixnetSelfPing)); + } + } else { + warn!("Failed to deserialize reconstructed message: {err}"); + } + } + } + Ok(None) + } +} + +impl Default for IprListener { + fn default() -> Self { + Self::new() + } +} diff --git a/sdk/rust/nym-sdk/src/lib.rs b/sdk/rust/nym-sdk/src/lib.rs index 8b5bb45568d..2655131b9d7 100644 --- a/sdk/rust/nym-sdk/src/lib.rs +++ b/sdk/rust/nym-sdk/src/lib.rs @@ -1,14 +1,17 @@ //! Rust SDK for the Nym platform //! //! The main component currently is [`mixnet`]. -//! [`tcp_proxy`] is probably a good place to start for anyone wanting to integrate with existing app code and read/write from a socket. //! [`client_pool`] is a configurable client pool. +//! [`tcp_proxy`] is a soon to be deprecated wrapper around the mixnet client which exposes a localhost port. +//! [`stream_wrapper`] is the v2 of the tcp_proxy, exposing a socket-like abstraction around the mixnet client. mod error; pub mod bandwidth; pub mod client_pool; +pub mod ip_packet_client; pub mod mixnet; +pub mod stream_wrapper; pub mod tcp_proxy; pub use error::{Error, Result}; diff --git a/sdk/rust/nym-sdk/src/mixnet/native_client.rs b/sdk/rust/nym-sdk/src/mixnet/native_client.rs index d9d19008d1c..04dda121c36 100644 --- a/sdk/rust/nym-sdk/src/mixnet/native_client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/native_client.rs @@ -2,9 +2,11 @@ use crate::mixnet::client::MixnetClientBuilder; use crate::mixnet::traits::MixnetMessageSender; use crate::{Error, Result}; use async_trait::async_trait; -use futures::{ready, Stream, StreamExt}; +use bytes::{Buf as _, BytesMut}; +use futures::{ready, FutureExt, Sink, SinkExt, Stream, StreamExt}; use log::{debug, error}; use nym_client_core::client::base_client::GatewayConnection; +use nym_client_core::client::inbound_messages::InputMessageCodec; use nym_client_core::client::mix_traffic::ClientRequestSender; use nym_client_core::client::{ base_client::{ClientInput, ClientOutput, ClientState}, @@ -15,15 +17,18 @@ use nym_client_core::config::{ForgetMe, RememberMe}; use nym_crypto::asymmetric::ed25519; use nym_gateway_requests::ClientRequest; use nym_sphinx::addressing::clients::Recipient; +use nym_sphinx::receiver::ReconstructedMessageCodec; use nym_sphinx::{params::PacketType, receiver::ReconstructedMessage}; use nym_statistics_common::clients::{ClientStatsEvents, ClientStatsSender}; use nym_task::connections::{ConnectionCommandSender, LaneQueueLengths}; use nym_task::ShutdownTracker; use nym_topology::{NymRouteProvider, NymTopology}; -use std::pin::Pin; +use std::pin::{pin, Pin}; use std::sync::Arc; use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tokio::sync::RwLockReadGuard; +use tokio_util::codec::{Encoder, FramedRead}; use tokio_util::sync::CancellationToken; /// Client connected to the Nym mixnet. @@ -60,6 +65,24 @@ pub struct MixnetClient { _buffered: Vec, pub(crate) forget_me: ForgetMe, pub(crate) remember_me: RememberMe, + + // internal state used for the `AsyncRead` implementation + _read: ReadBuffer, +} + +#[derive(Debug, Default)] +struct ReadBuffer { + buffer: BytesMut, +} + +impl ReadBuffer { + fn clear(&mut self) { + self.buffer.clear(); + } + + fn pending(&self) -> bool { + !self.buffer.is_empty() + } } impl MixnetClient { @@ -90,6 +113,7 @@ impl MixnetClient { _buffered: Vec::new(), forget_me, remember_me, + _read: ReadBuffer::default(), } } @@ -280,6 +304,25 @@ impl MixnetClient { } } } + + fn read_buffer_to_slice( + &mut self, + buf: &mut ReadBuf, + cx: &mut Context<'_>, + ) -> Poll> { + if self._read.buffer.len() < buf.capacity() { + // let written = self._read.buffer.len(); + buf.put_slice(&self._read.buffer); + self._read.clear(); + Poll::Ready(Ok(())) + } else { + let written = buf.capacity(); + buf.put_slice(&self._read.buffer[..written]); + self._read.buffer.advance(written); + cx.waker().wake_by_ref(); + Poll::Ready(Ok(())) + } + } } #[derive(Clone)] @@ -288,6 +331,193 @@ pub struct MixnetClientSender { packet_type: Option, } +impl AsyncRead for MixnetClient { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf, + ) -> Poll> { + let mut codec = ReconstructedMessageCodec {}; + + if self._read.pending() { + return self.read_buffer_to_slice(buf, cx); + } + + let msg = match self.as_mut().poll_next(cx) { + Poll::Ready(Some(msg)) => msg, + Poll::Ready(None) => return Poll::Ready(Ok(())), + Poll::Pending => return Poll::Pending, + }; + + match codec.encode(msg, &mut self._read.buffer) { + Ok(_) => {} + Err(e) => { + error!("failed to encode reconstructed message: {:?}", e); + return Poll::Ready(Err(tokio::io::Error::other( + "failed to encode reconstructed message", + ))); + } + }; + + self.read_buffer_to_slice(buf, cx) + } +} + +impl AsyncWrite for MixnetClient { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let codec = InputMessageCodec {}; + let mut reader = FramedRead::new(buf, codec); + let mut fut = reader.next(); + let msg = match fut.poll_unpin(cx) { + Poll::Ready(Some(Ok(msg))) => msg, + Poll::Ready(Some(Err(_))) => { + return Poll::Ready(Err(std::io::Error::other( + "failed to read message from input", + ))) + } + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(Ok(0)), + }; + + let msg_size = msg.serialized_size(); + + let mut fut = pin!(self.client_input.send(msg)); + match fut.poll_unpin(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(msg_size as usize)), + Poll::Ready(Err(_)) => Poll::Ready(Err(std::io::Error::other( + "failed to send message to mixnet", + ))), + Poll::Pending => Poll::Pending, + } + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Sink::poll_flush(self, cx).map_err(|_| std::io::Error::other("failed to flush the sink")) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + AsyncWrite::poll_flush(self, cx) + } +} + +impl Sink for MixnetClient { + type Error = Error; + + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.sender().poll_ready_unpin(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(_)) => Poll::Ready(Err(Error::MessageSendingFailure)), + Poll::Pending => Poll::Pending, + } + } + + fn start_send(mut self: Pin<&mut Self>, item: InputMessage) -> Result<()> { + self.sender() + .start_send_unpin(item) + .map_err(|_| Error::MessageSendingFailure) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.sender() + .poll_flush_unpin(cx) + .map_err(|_| Error::MessageSendingFailure) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.sender() + .poll_close_unpin(cx) + .map_err(|_| Error::MessageSendingFailure) + } +} + +// TODO: there should be a better way of implementing Sink and AsyncWrite over T: MixnetMessageSender +impl AsyncWrite for MixnetClientSender { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let codec = InputMessageCodec {}; + let mut reader = FramedRead::new(buf, codec); + let mut fut = reader.next(); + let msg = match fut.poll_unpin(cx) { + Poll::Ready(Some(Ok(msg))) => msg, + Poll::Ready(Some(Err(_))) => { + return Poll::Ready(Err(std::io::Error::other( + "failed to read message from input", + ))) + } + Poll::Pending => return Poll::Pending, + Poll::Ready(None) => return Poll::Ready(Ok(0)), + }; + + let msg_size = msg.serialized_size(); + + let mut fut = pin!(self.client_input.send(msg)); + match fut.poll_unpin(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(msg_size as usize)), + Poll::Ready(Err(_)) => Poll::Ready(Err(std::io::Error::other( + "failed to send message to mixnet", + ))), + Poll::Pending => Poll::Pending, + } + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Sink::poll_flush(self, cx).map_err(|_| std::io::Error::other("failed to flush the sink")) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + AsyncWrite::poll_flush(self, cx) + } +} + +impl Sink for MixnetClientSender { + type Error = Error; + + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.sender().poll_ready_unpin(cx) { + Poll::Ready(Ok(())) => Poll::Ready(Ok(())), + Poll::Ready(Err(_)) => Poll::Ready(Err(Error::MessageSendingFailure)), + Poll::Pending => Poll::Pending, + } + } + + fn start_send(mut self: Pin<&mut Self>, item: InputMessage) -> Result<()> { + self.sender() + .start_send_unpin(item) + .map_err(|_| Error::MessageSendingFailure) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.sender() + .poll_flush_unpin(cx) + .map_err(|_| Error::MessageSendingFailure) + } + + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.sender() + .poll_close_unpin(cx) + .map_err(|_| Error::MessageSendingFailure) + } +} + impl Stream for MixnetClient { type Item = ReconstructedMessage; @@ -326,12 +556,16 @@ impl MixnetMessageSender for MixnetClient { self.packet_type } - async fn send(&self, message: InputMessage) -> Result<()> { + async fn send(&mut self, message: InputMessage) -> Result<()> { self.client_input .send(message) .await .map_err(|_| Error::MessageSendingFailure) } + + fn sender(&mut self) -> &mut tokio_util::sync::PollSender { + &mut self.client_input.input_sender + } } #[async_trait] @@ -340,10 +574,14 @@ impl MixnetMessageSender for MixnetClientSender { self.packet_type } - async fn send(&self, message: InputMessage) -> Result<()> { + async fn send(&mut self, message: InputMessage) -> Result<()> { self.client_input .send(message) .await .map_err(|_| Error::MessageSendingFailure) } + + fn sender(&mut self) -> &mut tokio_util::sync::PollSender { + &mut self.client_input.input_sender + } } diff --git a/sdk/rust/nym-sdk/src/mixnet/sink.rs b/sdk/rust/nym-sdk/src/mixnet/sink.rs index a3b8fd8adab..b6f865f7f56 100644 --- a/sdk/rust/nym-sdk/src/mixnet/sink.rs +++ b/sdk/rust/nym-sdk/src/mixnet/sink.rs @@ -175,7 +175,7 @@ where } fn start_sender_task( - mixnet_client_sender: Sender, + mut mixnet_client_sender: Sender, ) -> (mpsc::Sender, JoinHandle<()>) where Sender: MixnetMessageSender + Send + 'static, diff --git a/sdk/rust/nym-sdk/src/mixnet/traits.rs b/sdk/rust/nym-sdk/src/mixnet/traits.rs index 8eb78109f7a..d8649dfa7b2 100644 --- a/sdk/rust/nym-sdk/src/mixnet/traits.rs +++ b/sdk/rust/nym-sdk/src/mixnet/traits.rs @@ -16,9 +16,11 @@ pub trait MixnetMessageSender { None } + fn sender(&mut self) -> &mut tokio_util::sync::PollSender; + /// Sends a [`InputMessage`] to the mixnet. This is the most low-level sending function, for /// full customization. - async fn send(&self, message: InputMessage) -> Result<()>; + async fn send(&mut self, message: InputMessage) -> Result<()>; /// Sends data to the supplied Nym address with the default surb behaviour. /// @@ -35,7 +37,7 @@ pub trait MixnetMessageSender { /// client.send_plain_message(recipient, "hi").await.unwrap(); /// } /// ``` - async fn send_plain_message(&self, address: Recipient, message: M) -> Result<()> + async fn send_plain_message(&mut self, address: Recipient, message: M) -> Result<()> where M: AsRef<[u8]> + Send, { @@ -61,7 +63,7 @@ pub trait MixnetMessageSender { /// } /// ``` async fn send_message( - &self, + &mut self, address: Recipient, message: M, surbs: IncludedSurbs, @@ -103,7 +105,7 @@ pub trait MixnetMessageSender { /// client.send_reply(tag, b"hi").await.unwrap(); /// } /// ``` - async fn send_reply(&self, recipient_tag: AnonymousSenderTag, message: M) -> Result<()> + async fn send_reply(&mut self, recipient_tag: AnonymousSenderTag, message: M) -> Result<()> where M: AsRef<[u8]> + Send, { diff --git a/sdk/rust/nym-sdk/src/stream_wrapper.rs b/sdk/rust/nym-sdk/src/stream_wrapper.rs new file mode 100644 index 00000000000..c13ec4aa682 --- /dev/null +++ b/sdk/rust/nym-sdk/src/stream_wrapper.rs @@ -0,0 +1,10 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-2.0-only + +//! TODO + +mod mixnet_stream_wrapper; +mod mixnet_stream_wrapper_ipr; + +pub use mixnet_stream_wrapper::{MixSocket, MixStream, MixStreamReader, MixStreamWriter}; +pub use mixnet_stream_wrapper_ipr::{IpMixStream, IpMixStreamReader, IpMixStreamWriter}; diff --git a/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper.rs b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper.rs new file mode 100644 index 00000000000..75803d04540 --- /dev/null +++ b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper.rs @@ -0,0 +1,519 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use crate::mixnet::InputMessage; +use crate::mixnet::{MixnetClient, MixnetClientSender, Recipient}; +use crate::Error; +use bytes::BytesMut; +use nym_client_core::client::inbound_messages::InputMessageCodec; +use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag; +use nym_sphinx::receiver::ReconstructedMessage; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt, ReadBuf}; +use tokio::sync::oneshot; +use tokio_util::codec::Encoder; +use tracing::{debug, info, warn}; + +/// MixSocket is following the structure of something like Tokio::net::TcpSocket with regards to setup and interface, breakdown from TcpSocket to TcpStream, etc. +/// However, we can't map this one to one onto the TcpSocket as there isn't really a concept of binding to a port with the MixnetClient; it connects to its +/// Gateway and then just accepts incoming messages from the Gw via the Websocket connection. However, we can stick with the idea of creating a Socket in an unconnected state, +/// either using it to make a new Stream (connecting it to its EntryGw) or connecting it *to* something (once the IPR functionality is enabled, this will mean the creation of +/// a Stream + kicking off the creation of a tunnel to an ExitGw + IPR). +/// +/// The cause for a MixSocket > going striaght to a MixStream is creating a Nym Client disconnected from the Mixnet first, then upgrading to a Stream when connecting it. Once LP +/// is implemented, this will also allow us to follow something like what is implemented for the Tokio::net::UdpFramed abstraction, where we can create multiple MixStream instances +/// from a single MixSocket, all connected to different Recipients (aka a one-to-many setup with a single MixnetClient). +pub struct MixSocket { + pub inner: MixnetClient, +} + +impl MixSocket { + /// Create a new socket that is disconnected from the Mixnet - kick off the Mixnet client with config for builder. + /// Following idea of having single client with multiple concurrent connections represented by per-Recipient MixStream instance. + pub async fn new() -> Result { + // TODO make this take an option, if Some create with builder pattern, if None make ephemeral like we already are. + let inner = MixnetClient::connect_new().await?; + Ok(MixSocket { inner }) + } + + /// Connect to a specific peer (Nym Client) and return a Stream (cf TcpSocket::connect() / TcpStream::new()). + pub async fn connect_to(_recipient: Recipient) -> Result { + todo!() + } + + /// Get our Nym address. + pub fn nym_address(&self) -> &Recipient { + self.inner.nym_address() + } + + pub fn get_ref(&self) -> &MixnetClient { + &self.inner + } + + pub fn get_mut(&mut self) -> &mut MixnetClient { + &mut self.inner + } + + pub fn into_inner(self) -> MixnetClient { + self.inner + } +} + +pub struct MixStream { + pub client: MixnetClient, + peer: Option, // We might be accepting incoming messages and replying, so might not have a Nym addr to talk to.. + peer_surb_tag: Option, // ..since we might just be using SURBs instead +} + +impl MixStream { + /// Create a MixStream instance and immediately connect (convenience method) or pass in a MixSocket (pre-configured DisconnectedMixnetClient) and connect it. + // TODO in future take config from MixSocket if exists in Option<> param, else spin up ephemeral client. Just doing ephemeral for initial sketch. + pub async fn new(socket: Option, peer: Recipient) -> Self { + let client = match socket { + Some(socket) => socket.into_inner(), + None => MixnetClient::connect_new().await.unwrap(), + }; + Self { + client, + peer: Some(peer), + peer_surb_tag: None, + } + } + + /// Nym address of Stream's peer (Nym Client it will communicate with). + pub fn peer_addr(self) -> Option { + self.peer + } + + /// Our Nym address. + pub fn local_addr(self) -> Recipient { + *self.client.nym_address() + } + + /// Store SURB tag in own field to use for Anonymous Replies. + pub fn store_surb_tag(&mut self, surbs: AnonymousSenderTag) { + self.peer_surb_tag = Some(surbs); + } + + /// Get SURB tag (if any). + pub fn surbs(&self) -> Option { + self.peer_surb_tag + } + + /// Split for concurrent read/write (like TcpStream::Split) into MixnetStreamReader and MixnetStreamWriter. + pub fn split(self) -> (MixStreamReader, MixStreamWriter) { + debug!("Splitting MixStream"); + let sender = self.client.split_sender(); + debug!("Split MixStream into Reader and Writer"); + let (surb_tx, surb_rx) = oneshot::channel(); + ( + MixStreamReader { + client: self.client, + peer: self.peer, + peer_surb_tag: self.peer_surb_tag, + surb_tx: Some(surb_tx), + }, + MixStreamWriter { + sender, + peer: self.peer.expect("No Peer set"), + peer_surb_tag: self.peer_surb_tag, + surb_rx: Some(surb_rx), + }, + ) + } + + /// Convenience method for just piping bytes into the Mixnet. + pub async fn write_bytes(&mut self, data: &[u8]) -> Result<(), Error> { + let input_message = if self.peer_surb_tag.is_some() { + info!("Writing {} bytes, sending with SURBs", data.len()); + InputMessage::Reply { + recipient_tag: self.peer_surb_tag.ok_or(Error::MixStreamSurbTagNotSet)?, + data: data.to_owned(), + lane: nym_task::connections::TransmissionLane::General, + max_retransmissions: Some(5), // TODO check with Drazen - guessing here + } + } else { + info!("Writing {} bytes", data.len()); + InputMessage::Anonymous { + recipient: self.peer.ok_or(Error::MixStreamRecipientNotSet)?, + data: (data.to_owned()), + reply_surbs: (10), + lane: (nym_task::connections::TransmissionLane::General), + max_retransmissions: (Some(5)), // TODO check with Drazen - guessing here + } + }; + + let mut codec = InputMessageCodec {}; + let mut serialized_bytes = BytesMut::new(); + codec.encode(input_message, &mut serialized_bytes)?; + self.write_all(&serialized_bytes).await?; + debug!("Wrote serialized bytes"); + self.flush().await?; + debug!("Flushed"); + + Ok(()) + } + + /// Sidesteps the AsyncRead/codec in place of using the Mixnet Client's message-based functionality for getting messages: useful for debugging. + pub async fn wait_for_messages(&mut self) -> Option> { + self.client.wait_for_messages().await + } + + /// Disconnect client from the Mixnet - note that disconnected clients cannot currently be reconnected. + pub async fn disconnect(self) { + debug!("Disconnecting"); + self.client.disconnect().await; + debug!("Disconnected"); + } +} + +impl AsyncRead for MixStream { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.client).poll_read(cx, buf) + } +} + +impl AsyncWrite for MixStream { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.client).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.client).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.client).poll_shutdown(cx) + } +} + +pub struct MixStreamReader { + client: MixnetClient, + peer: Option, // We might be accepting incoming messages and replying, so might not have a Nym addr to talk to.. + peer_surb_tag: Option, // ..since we might just be using SURBs instead + surb_tx: Option>, +} + +impl MixStreamReader { + /// Nym address of Stream's peer (Nym Client it will communicate with). + pub fn peer_addr(self) -> Option { + self.peer + } + + /// Our Nym address. + pub fn local_addr(self) -> Recipient { + *self.client.nym_address() + } + + /// Store SURB tag in self + send to the other side of the split so it can be used for the connection. + pub fn store_surb_tag(&mut self, surbs: AnonymousSenderTag) { + self.peer_surb_tag = Some(surbs); + if let Some(tx) = self.surb_tx.take() { + match tx.send(surbs) { + Ok(()) => debug!("Sent SURBs to MixStreamWriter"), + Err(e) => warn!("Could not send SURBs to MixStreamWriter with err: {}", e), + } + } + } + + /// Stored SURBs (if any). + pub fn surbs(&self) -> Option { + self.peer_surb_tag + } +} + +impl AsyncRead for MixStreamReader { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.client).poll_read(cx, buf) + } +} + +pub struct MixStreamWriter { + sender: MixnetClientSender, + peer: Recipient, + peer_surb_tag: Option, + surb_rx: Option>, +} + +impl MixStreamWriter { + /// Convenience method for just piping bytes into the Mixnet. + pub async fn write_bytes(&mut self, data: &[u8]) -> Result<(), Error> { + if self.peer_surb_tag.is_none() { + if let Some(mut rx) = self.surb_rx.take() { + if let Ok(surbs) = rx.try_recv() { + self.peer_surb_tag = Some(surbs); + } + } + } + + let input_message = if self.peer_surb_tag.is_some() { + InputMessage::Reply { + recipient_tag: (self.peer_surb_tag.expect("No Peer SURBs set")), + data: (data.to_owned()), + lane: (nym_task::connections::TransmissionLane::General), + max_retransmissions: (Some(5)), // TODO check with Drazen - guessing here + } + } else { + InputMessage::Anonymous { + recipient: (self.peer), + data: (data.to_owned()), + reply_surbs: (10), + lane: (nym_task::connections::TransmissionLane::General), + max_retransmissions: (Some(5)), // TODO check with Drazen - guessing here + } + }; + + let mut codec = InputMessageCodec {}; + let mut serialized_bytes = BytesMut::new(); + codec.encode(input_message, &mut serialized_bytes)?; + debug!("Serialized bytes: {:?}", serialized_bytes); + + self.write_all(&serialized_bytes).await?; + info!("Wrote serialized bytes"); + self.flush().await?; + debug!("Flushed"); + + Ok(()) + } +} + +impl AsyncWrite for MixStreamWriter { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.sender).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.sender).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.sender).poll_shutdown(cx) + } +} + +/** + * Tests TODO: + * STREAM + STREAMREADER + STREAMWRITER + * - general tests: create new + various into() fns + */ +#[cfg(test)] +mod tests { + use super::*; + use nym_sphinx::receiver::ReconstructedMessageCodec; + // use std::sync::Once; + use tokio::io::AsyncReadExt; + use tokio_util::codec::Decoder; + + // Quick test fn for easy testing of sending to self before writing Socket impl (see above todo) + impl MixSocket { + pub async fn new_test() -> Result { + let inner = MixnetClient::connect_new().await?; + Ok(MixSocket { inner }) + } + } + + // static INIT: Once = Once::new(); + + // fn init_logging() { + // if tracing::dispatcher::has_been_set() { + // return; + // } + // INIT.call_once(|| { + // nym_bin_common::logging::setup_tracing_logger(); + // }); + // } + + #[tokio::test] + async fn simple_surb_reply_stream() -> Result<(), Box> { + // init_logging(); + + let receiver_socket = MixSocket::new_test().await?; + let receiver_address = *receiver_socket.nym_address(); + let sender_socket = MixSocket::new_test().await?; + let sender_address = *sender_socket.nym_address(); + let mut receiver_stream = MixStream::new(Some(receiver_socket), sender_address).await; + let mut sender_stream = MixStream::new(Some(sender_socket), receiver_address).await; + + sender_stream.write_bytes(b"Hello, Mixnet Split!").await?; + + let mut buffer = [0u8; 1024]; + match receiver_stream.read(&mut buffer).await { + Ok(bytes_read) if bytes_read > 0 => { + let mut codec = ReconstructedMessageCodec {}; + let mut buf = BytesMut::from(&buffer[..bytes_read]); + + if let Ok(Some(decoded_message)) = codec.decode(&mut buf) { + let payload_surbs = decoded_message.sender_tag; + assert!(payload_surbs.is_some()); + receiver_stream.store_surb_tag(payload_surbs.unwrap()); + receiver_stream.write_bytes(b"Hello, Mixnet reply!").await?; + } + } + _ => panic!("Failed to receive initial message"), + } + + let mut reply_buffer = [0u8; 1024]; + let reply_result = tokio::time::timeout( + tokio::time::Duration::from_secs(30), + sender_stream.read(&mut reply_buffer), + ) + .await; + + match reply_result { + Ok(Ok(bytes_read)) if bytes_read > 0 => { + let mut codec = ReconstructedMessageCodec {}; + let mut buf = BytesMut::from(&reply_buffer[..bytes_read]); + + if let Ok(Some(decoded_message)) = codec.decode(&mut buf) { + assert_eq!(decoded_message.message.as_slice(), b"Hello, Mixnet reply!"); + } + info!("Got reply!"); + } + _ => panic!("Failed to receive reply"), + } + + Ok(()) + } + + #[tokio::test] + async fn concurrent_surb_reply_split() -> Result<(), Box> { + // init_logging(); + + let sender_socket = MixSocket::new_test().await?; + let sender_address = *sender_socket.nym_address(); + let receiver_socket = MixSocket::new_test().await?; + let receiver_address = *receiver_socket.nym_address(); + let sender_stream = MixStream::new(Some(sender_socket), receiver_address).await; + let receiver_stream = MixStream::new(Some(receiver_socket), sender_address).await; + + let (mut sender_reader, mut sender_writer) = sender_stream.split(); + let (mut receiver_reader, mut receiver_writer) = receiver_stream.split(); + + let sender_task = tokio::spawn(async move { + for i in 0..5 { + let msg = format!("Message {} requesting SURB reply", i); + sender_writer.write_bytes(msg.as_bytes()).await?; + info!("Sent message {}", i); + } + Ok::<_, Error>((sender_writer, 5)) + }); + + let receiver_task = tokio::spawn(async move { + let mut received_count = 0; + let mut sent_replies = 0; + let mut buffer = [0u8; 1024]; + + while received_count < 5 { + match tokio::time::timeout( + tokio::time::Duration::from_secs(10), + receiver_reader.read(&mut buffer), + ) + .await + { + Ok(Ok(bytes_read)) if bytes_read > 0 => { + let mut codec = ReconstructedMessageCodec {}; + let mut buf = BytesMut::from(&buffer[..bytes_read]); + + if let Ok(Some(decoded_message)) = codec.decode(&mut buf) { + info!( + "Received: {:?}", + String::from_utf8_lossy(&decoded_message.message) + ); + + if received_count == 0 && decoded_message.sender_tag.is_some() { + receiver_reader.store_surb_tag(decoded_message.sender_tag.unwrap()); + info!("Stored SURBs"); + } + + received_count += 1; + + if received_count == 3 && sent_replies == 0 { + for i in 0..3 { + let reply = format!("SURB reply {}", i); + receiver_writer.write_bytes(reply.as_bytes()).await?; + info!("Sent SURB reply {}", i); + sent_replies += 1; + tokio::time::sleep(tokio::time::Duration::from_millis(200)) + .await; + } + } + } + } + _ => break, + } + } + + Ok::<_, Error>(( + receiver_reader, + receiver_writer, + received_count, + sent_replies, + )) + }); + + let reply_reader_task = tokio::spawn(async move { + let mut reply_count = 0; + let mut buffer = [0u8; 1024]; + + while reply_count < 3 { + match tokio::time::timeout( + tokio::time::Duration::from_secs(15), + sender_reader.read(&mut buffer), + ) + .await + { + Ok(Ok(bytes_read)) if bytes_read > 0 => { + let mut codec = ReconstructedMessageCodec {}; + let mut buf = BytesMut::from(&buffer[..bytes_read]); + + if let Ok(Some(decoded_message)) = codec.decode(&mut buf) { + let reply_text = String::from_utf8_lossy(&decoded_message.message); + info!("Received reply: {}", reply_text); + assert!(reply_text.contains("SURB reply")); + reply_count += 1; + } + } + _ => { + if reply_count == 0 { + panic!("No SURB replies received"); + } + break; + } + } + } + + Ok::<_, Error>(reply_count) + }); + + let (_, sent_count) = sender_task.await??; + let (_, _, received_count, sent_replies) = receiver_task.await??; + let reply_count = reply_reader_task.await??; + + info!( + "Sent {} messages, received {} messages, sent {} SURB replies, received {} SURB replies", + sent_count, received_count, sent_replies, reply_count + ); + assert!(received_count == 5, "Didn't receive all messages!"); + assert!(reply_count == 3, "Didn't receive all replies!"); + + Ok(()) + } +} diff --git a/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs new file mode 100644 index 00000000000..6414a721fb1 --- /dev/null +++ b/sdk/rust/nym-sdk/src/stream_wrapper/mixnet_stream_wrapper_ipr.rs @@ -0,0 +1,780 @@ +// Copyright 2024 - Nym Technologies SA +// SPDX-License-Identifier: GPL-3.0-only + +use super::mixnet_stream_wrapper::{MixStream, MixStreamReader, MixStreamWriter}; +use crate::ip_packet_client::{ + helpers::check_ipr_message_version, IprListener, MixnetMessageOutcome, +}; +use crate::UserAgent; +use crate::{mixnet::Recipient, Error}; +use std::collections::HashMap; + +use bytes::Bytes; +use nym_ip_packet_requests::{ + v8::{ + request::IpPacketRequest, + response::{ConnectResponseReply, ControlResponse, IpPacketResponse, IpPacketResponseData}, + }, + IpPair, +}; +use nym_sphinx::receiver::ReconstructedMessageCodec; + +use futures::StreamExt; +use nym_crypto::asymmetric::ed25519; +use nym_network_defaults::ApiUrl; +use nym_validator_client::nym_api::NymApiClientExt; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::time::Duration; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::sync::oneshot; +use tokio_util::codec::FramedRead; +use tracing::{debug, error, info}; + +const IPR_CONNECT_TIMEOUT: Duration = Duration::from_secs(60); + +#[derive(Clone)] +pub struct IprWithPerformance { + pub(crate) address: Recipient, + pub(crate) identity: ed25519::PublicKey, + pub(crate) performance: u8, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum ConnectionState { + Disconnected, + Connecting, + Connected, +} + +fn create_nym_api_client(nym_api_urls: Vec) -> Result { + // TODO do something proper with this + let user_agent = UserAgent { + application: "nym-ipr-streamer".to_string(), + version: "0.0.1".to_string(), + platform: "rust".to_string(), + git_commit: "max/sdk-streamer".to_string(), + }; + + let urls = nym_api_urls + .into_iter() + .map(|url| url.url.parse()) + .collect::, _>>() + .map_err(|err| { + error!("malformed nym-api url: {err}"); + Error::NoNymAPIUrl + })?; + + if urls.is_empty() { + return Err(Error::NoNymAPIUrl); + } + + let client = nym_http_api_client::ClientBuilder::new_with_urls(urls) + .with_user_agent(user_agent) + .build()?; + + Ok(client) +} + +async fn retrieve_exit_nodes_with_performance( + client: nym_http_api_client::Client, +) -> Result, Error> { + // retrieve all nym-nodes on the network + let all_nodes = client + .get_all_described_nodes() + .await? + .into_iter() + .map(|described| (described.ed25519_identity_key(), described)) + .collect::>(); + + // annoyingly there's no convenient way of doing this in a single query + // retrieve performance scores of all exit gateways + let exit_gateways = client + .get_all_basic_exit_assigned_nodes_with_metadata() + .await? + .nodes; + + let mut described = Vec::new(); + + for exit in exit_gateways { + if let Some(ipr_info) = all_nodes + .get(&exit.ed25519_identity_pubkey) + .and_then(|n| n.description.ip_packet_router.clone()) + { + if let Ok(parsed_address) = ipr_info.address.parse() { + described.push(IprWithPerformance { + address: parsed_address, + identity: exit.ed25519_identity_pubkey, + performance: exit.performance.round_to_integer(), + }) + } + } + } + + Ok(described) +} + +async fn get_random_ipr(client: nym_http_api_client::Client) -> Result { + let nodes = retrieve_exit_nodes_with_performance(client).await?; + info!("Found {} Exit Gateways", nodes.len()); + + // JS: I'm leaving the old behaviour here of choosing node with the highest performance, + // but I think you should reconsider making a pseudorandom selection weighted by some scaled performance + // otherwise all clients might end up choosing exactly the same node (I will leave this as PR comment when I get here : D) + let selected_gateway = nodes + .into_iter() + .max_by_key(|gw| gw.performance) + .ok_or_else(|| Error::NoGatewayAvailable)?; + + let ipr_address = selected_gateway.address; + + info!( + "Using IPR: {} (Gateway: {}, Performance: {:?})", + ipr_address, selected_gateway.identity, selected_gateway.performance + ); + + Ok(ipr_address) +} + +/// Unlike the non-IPR MixStream, we do not start with a Socket and then 'connect' to a Stream; seemed too many layers of abstraction for little trade off. +pub struct IpMixStream { + stream: MixStream, + ipr_address: Recipient, + listener: IprListener, + allocated_ips: Option, + connection_state: ConnectionState, +} + +impl IpMixStream { + // TODO be able to pass in DisconnectedMixnetClient to use as MixStream inner client. + pub async fn new() -> Result { + // JS: I think you need some bootstrapping here, something would need to be passed to `new()` + // to indicate what endpoints to use + // again, for now I'm default to mainnet as you did before + let mainnet_network_defaults = crate::NymNetworkDetails::new_mainnet(); + + let api_client = + create_nym_api_client(mainnet_network_defaults.nym_api_urls.unwrap_or_default())?; + let ipr_address = get_random_ipr(api_client).await?; + let stream = MixStream::new(None, Recipient::from(ipr_address)).await; + + Ok(Self { + stream, + ipr_address, + listener: IprListener::new(), + allocated_ips: None, + connection_state: ConnectionState::Disconnected, + }) + } + + pub fn nym_address(&self) -> &Recipient { + self.stream.client.nym_address() + } + + async fn send_ipr_request(&mut self, request: IpPacketRequest) -> Result<(), Error> { + let request_bytes = request.to_bytes()?; + self.stream.write_bytes(&request_bytes).await + } + + pub async fn connect_tunnel(&mut self) -> Result { + if self.connection_state != ConnectionState::Disconnected { + return Err(Error::IprStreamClientAlreadyConnectedOrConnecting); + } + + self.connection_state = ConnectionState::Connecting; + info!("Connecting to IP packet router: {}", self.ipr_address); + + match self.connect_inner().await { + Ok(ip_pair) => { + self.allocated_ips = Some(ip_pair); + self.connection_state = ConnectionState::Connected; + info!( + "Connected to IPv4: {}, IPv6: {}", + ip_pair.ipv4, ip_pair.ipv6 + ); + Ok(ip_pair) + } + Err(e) => { + self.connection_state = ConnectionState::Disconnected; + error!("Failed to connect: {:?}", e); + Err(e) + } + } + } + + async fn connect_inner(&mut self) -> Result { + let (request, request_id) = IpPacketRequest::new_connect_request(None); + debug!("Sending connect request with ID: {}", request_id); + + self.send_ipr_request(request).await?; + self.listen_for_connect_response(request_id).await + } + + async fn listen_for_connect_response(&mut self, request_id: u64) -> Result { + let timeout = tokio::time::sleep(IPR_CONNECT_TIMEOUT); + tokio::pin!(timeout); + + let mut framed = FramedRead::new(&mut self.stream, ReconstructedMessageCodec {}); + + loop { + tokio::select! { + _ = &mut timeout => { + return Err(Error::IPRConnectResponseTimeout); + } + frame = framed.next() => { + match frame { + None => { + return Err(Error::IPRClientStreamClosed); + } + Some(Err(e)) => { + return Err(Error::MessageRecovery(e)); + } + Some(Ok(reconstructed)) => { + if let Err(e) = check_ipr_message_version(&reconstructed) { + return Err(Error::IPRMessageVersionCheckFailed(e.to_string())); + + } + if let Ok(response) = IpPacketResponse::from_reconstructed_message(&reconstructed) { + if response.id() == Some(request_id) { + return self.handle_connect_response(response).await; + } + else { + return Err(Error::IPRNoId) + } + } + } + } + } + } + } + } + + async fn handle_connect_response(&self, response: IpPacketResponse) -> Result { + let control_response = match response.data { + IpPacketResponseData::Control(c) => c, + other => return Err(Error::UnexpectedResponseType(other)), + }; + + match *control_response { + ControlResponse::Connect(connect_resp) => match connect_resp.reply { + ConnectResponseReply::Success(success) => Ok(success.ips), + ConnectResponseReply::Failure(reason) => Err(Error::ConnectDenied(reason)), + }, + _ => Err(Error::UnexpectedResponseType( + IpPacketResponseData::Control(control_response.clone()), + )), + } + } + + pub async fn send_ip_packet(&mut self, packet: &[u8]) -> Result<(), Error> { + if self.connection_state != ConnectionState::Connected { + return Err(Error::IprStreamClientNotConnected); + } + let request = IpPacketRequest::new_data_request(packet.to_vec().into()); + self.send_ipr_request(request).await + } + + pub async fn handle_incoming(&mut self) -> Result, Error> { + let mut framed = FramedRead::new(&mut self.stream, ReconstructedMessageCodec {}); + + match tokio::time::timeout(Duration::from_secs(10), framed.next()).await { + Ok(Some(reconstructed)) => { + match self + .listener + .handle_reconstructed_message(reconstructed?) + .await + { + Ok(Some(MixnetMessageOutcome::IpPackets(packets))) => { + info!("Extracted {} IP packets", packets.len()); + Ok(packets) + } + Ok(Some(MixnetMessageOutcome::Disconnect)) => { + info!("Received disconnect"); + self.connection_state = ConnectionState::Disconnected; + self.allocated_ips = None; + Ok(Vec::new()) + } + Ok(Some(MixnetMessageOutcome::MixnetSelfPing)) => { + debug!("Received mixnet self ping"); + Ok(Vec::new()) + } + Ok(None) => Ok(Vec::new()), + Err(e) => { + error!("Failed to handle message: {}", e); + Ok(Vec::new()) + } + } + } + _ => Ok(Vec::new()), + } + } + + pub fn allocated_ips(&self) -> Option<&IpPair> { + self.allocated_ips.as_ref() + } + + pub fn is_connected(&self) -> bool { + self.connection_state == ConnectionState::Connected + } + + /// Disconnect inner stream client from the Mixnet - note that disconnected clients cannot currently be reconnected. + pub async fn disconnect_stream(self) { + debug!("Disconnecting"); + self.stream.disconnect().await; + debug!("Disconnected"); + } + + /// Split for concurrent read/write (like TcpStream::Split) into IpMixnetStreamReader and IpMixnetStreamWriter. + pub fn split(self) -> (IpMixStreamReader, IpMixStreamWriter) { + debug!("Splitting IpMixStream"); + let local_addr = *self.stream.client.nym_address(); + let (stream_reader, stream_writer) = self.stream.split(); + debug!("Split IpMixStream into Reader and Writer"); + + let (state_tx, state_rx) = oneshot::channel(); + let (ips_tx, ips_rx) = oneshot::channel(); + + ( + IpMixStreamReader { + stream_reader, + listener: self.listener, + allocated_ips: self.allocated_ips, + connection_state: self.connection_state.clone(), + state_tx: Some(state_tx), + ips_tx: Some(ips_tx), + }, + IpMixStreamWriter { + stream_writer, + local_addr, + allocated_ips: self.allocated_ips, + connection_state: self.connection_state, + state_rx: Some(state_rx), + ips_rx: Some(ips_rx), + }, + ) + } +} + +impl AsyncRead for IpMixStream { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll> { + std::pin::Pin::new(&mut self.stream).poll_read(cx, buf) + } +} + +impl AsyncWrite for IpMixStream { + fn poll_write( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> std::task::Poll> { + std::pin::Pin::new(&mut self.stream).poll_write(cx, buf) + } + + fn poll_flush( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::pin::Pin::new(&mut self.stream).poll_flush(cx) + } + + fn poll_shutdown( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + std::pin::Pin::new(&mut self.stream).poll_shutdown(cx) + } +} + +pub struct IpMixStreamReader { + stream_reader: MixStreamReader, + listener: IprListener, + allocated_ips: Option, + connection_state: ConnectionState, + state_tx: Option>, + ips_tx: Option>>, +} + +impl IpMixStreamReader { + pub fn nym_address(self) -> Recipient { + self.stream_reader.local_addr() + } + + pub async fn handle_incoming(&mut self) -> Result, Error> { + let mut framed = FramedRead::new(&mut self.stream_reader, ReconstructedMessageCodec {}); + + match tokio::time::timeout(Duration::from_secs(10), framed.next()).await { + Ok(Some(reconstructed)) => { + match self + .listener + .handle_reconstructed_message(reconstructed?) + .await + { + Ok(Some(MixnetMessageOutcome::IpPackets(packets))) => { + info!("Extracted {} IP packets", packets.len()); + Ok(packets) + } + Ok(Some(MixnetMessageOutcome::Disconnect)) => { + info!("Received disconnect"); + self.connection_state = ConnectionState::Disconnected; + self.allocated_ips = None; + // Send state update to writer + if let Some(tx) = self.state_tx.take() { + let _ = tx.send(ConnectionState::Disconnected); + } + if let Some(tx) = self.ips_tx.take() { + let _ = tx.send(None); + } + Ok(Vec::new()) + } + Ok(Some(MixnetMessageOutcome::MixnetSelfPing)) => { + debug!("Received mixnet self ping"); + Ok(Vec::new()) + } + Ok(None) => Ok(Vec::new()), + Err(e) => { + error!("Failed to handle message: {}", e); + Ok(Vec::new()) + } + } + } + _ => Ok(Vec::new()), + } + } + + pub fn allocated_ips(&self) -> Option<&IpPair> { + self.allocated_ips.as_ref() + } + + pub fn is_connected(&self) -> bool { + self.connection_state == ConnectionState::Connected + } +} + +impl AsyncRead for IpMixStreamReader { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.stream_reader).poll_read(cx, buf) + } +} + +pub struct IpMixStreamWriter { + stream_writer: MixStreamWriter, + local_addr: Recipient, + allocated_ips: Option, + connection_state: ConnectionState, + state_rx: Option>, + ips_rx: Option>>, +} + +impl IpMixStreamWriter { + pub fn nym_address(&self) -> &Recipient { + &self.local_addr + } + + async fn send_ipr_request(&mut self, request: IpPacketRequest) -> Result<(), Error> { + let request_bytes = request.to_bytes()?; + self.stream_writer.write_bytes(&request_bytes).await + } + + pub async fn send_ip_packet(&mut self, packet: &[u8]) -> Result<(), Error> { + // Check for state updates from reader + if let Some(mut rx) = self.state_rx.take() { + if let Ok(new_state) = rx.try_recv() { + self.connection_state = new_state; + } else { + self.state_rx = Some(rx); + } + } + + if let Some(mut rx) = self.ips_rx.take() { + if let Ok(new_ips) = rx.try_recv() { + self.allocated_ips = new_ips; + } else { + self.ips_rx = Some(rx); + } + } + + if self.connection_state != ConnectionState::Connected { + return Err(Error::IprStreamClientNotConnected); + } + + let request = IpPacketRequest::new_data_request(packet.to_vec().into()); + self.send_ipr_request(request).await + } + + pub fn allocated_ips(&self) -> Option<&IpPair> { + self.allocated_ips.as_ref() + } + + pub fn is_connected(&self) -> bool { + self.connection_state == ConnectionState::Connected + } +} + +impl AsyncWrite for IpMixStreamWriter { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.stream_writer).poll_write(cx, buf) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.stream_writer).poll_flush(cx) + } + + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.stream_writer).poll_shutdown(cx) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ip_packet_client::helpers::{ + icmp_identifier, is_icmp_echo_reply, is_icmp_v6_echo_reply, send_ping_v4, send_ping_v6, + }; + use std::net::{Ipv4Addr, Ipv6Addr}; + // use std::sync::Once; + + // static INIT: Once = Once::new(); + + // fn init_logging() { + // if tracing::dispatcher::has_been_set() { + // return; + // } + // INIT.call_once(|| { + // nym_bin_common::logging::setup_tracing_logger(); + // }); + // } + + #[tokio::test] + async fn connect_to_ipr() -> Result<(), Box> { + // init_logging(); + + let mut stream = IpMixStream::new().await?; + let ip_pair = stream.connect_tunnel().await?; + + let ipv4: Ipv4Addr = ip_pair.ipv4; + assert!(!ipv4.is_unspecified(), "IPv4 address should not be 0.0.0.0"); + + let ipv6: Ipv6Addr = ip_pair.ipv6; + assert!(!ipv6.is_unspecified(), "IPv6 address should not be ::"); + + assert!(stream.is_connected(), "Stream should be connected"); + assert!( + stream.allocated_ips().is_some(), + "Should have allocated IPs" + ); + + stream.disconnect_stream().await; + + Ok(()) + } + + #[tokio::test] + async fn dns_ping_checks() -> Result<(), Box> { + // init_logging(); + + let mut stream = IpMixStream::new().await?; + let ip_pair = stream.connect_tunnel().await?; + + info!( + "Connected with IPs - IPv4: {}, IPv6: {}", + ip_pair.ipv4, ip_pair.ipv6 + ); + + let external_v4_targets = vec![ + ("Google DNS", Ipv4Addr::new(8, 8, 8, 8)), + ("Cloudflare DNS", Ipv4Addr::new(1, 1, 1, 1)), + ("Quad9 DNS", Ipv4Addr::new(9, 9, 9, 9)), + ]; + + let external_v6_targets = vec![ + ("Google DNS", "2001:4860:4860::8888".parse::()?), + ( + "Cloudflare DNS", + "2606:4700:4700::1111".parse::()?, + ), + ("Quad9 DNS", "2620:fe::fe".parse::()?), + ]; + + let identifier = icmp_identifier(); + let mut successful_v4_pings = 0; + let mut total_v4_pings = 0; + let mut successful_v6_pings = 0; + let mut total_v6_pings = 0; + + for (name, target) in &external_v4_targets { + info!("Testing IPv4 connectivity to {} ({})", name, target); + + for seq in 0..3 { + send_ping_v4(&mut stream, &ip_pair, seq, identifier, *target).await?; + total_v4_pings += 1; + } + } + + for (name, target) in &external_v6_targets { + info!("Testing IPv6 connectivity to {} ({})", name, target); + + for seq in 0..3 { + send_ping_v6(&mut stream, &ip_pair, seq, *target).await?; + total_v6_pings += 1; + } + } + + let collect_timeout = tokio::time::sleep(Duration::from_secs(10)); + tokio::pin!(collect_timeout); + + loop { + tokio::select! { + _ = &mut collect_timeout => { + info!("Finished collecting replies"); + break; + } + result = stream.handle_incoming() => { + if let Ok(packets) = result { + for packet in packets { + if let Some((reply_id, source, dest)) = is_icmp_echo_reply(&packet) { + if reply_id == identifier && dest == ip_pair.ipv4 { + successful_v4_pings += 1; + debug!("IPv4 reply from {}", source); + } + } + + if let Some((reply_id, source, dest)) = is_icmp_v6_echo_reply(&packet) { + if reply_id == identifier && dest == ip_pair.ipv6 { + successful_v6_pings += 1; + debug!("IPv6 reply from {}", source); + } + } + } + } + } + } + } + + let v4_success_rate = (successful_v4_pings as f64 / total_v4_pings as f64) * 100.0; + let v6_success_rate = (successful_v6_pings as f64 / total_v6_pings as f64) * 100.0; + + info!( + "IPv4 external connectivity: {}/{} pings successful ({:.1}%)", + successful_v4_pings, total_v4_pings, v4_success_rate + ); + info!( + "IPv6 external connectivity: {}/{} pings successful ({:.1}%)", + successful_v6_pings, total_v6_pings, v6_success_rate + ); + + assert!(successful_v4_pings > 0, "No IPv4 pings successful"); + assert!( + v4_success_rate >= 75.0, + "IPv4 success rate < 75% (got {:.1}%)", + v4_success_rate + ); + + assert!(successful_v6_pings > 0, "No IPv6 pings successful"); + assert!( + v6_success_rate >= 75.0, + "IPv6 success rate < 75% (got {:.1}%)", + v6_success_rate + ); + + stream.disconnect_stream().await; + Ok(()) + } + + #[tokio::test] + async fn split_dns_ping_checks() -> Result<(), Box> { + // init_logging(); + + let mut stream = IpMixStream::new().await?; + let ip_pair = stream.connect_tunnel().await?; + + info!( + "Connected with IPs - IPv4: {}, IPv6: {}", + ip_pair.ipv4, ip_pair.ipv6 + ); + + let (mut reader, mut writer) = stream.split(); + + let external_v4_targets = vec![("Google DNS", Ipv4Addr::new(8, 8, 8, 8))]; + + let identifier = icmp_identifier(); + let mut successful_v4_pings = 0; + let mut total_v4_pings = 0; + + for (name, target) in &external_v4_targets { + info!("Testing IPv4 connectivity to {} ({})", name, target); + + for seq in 0..2 { + use crate::ip_packet_client::helpers::{ + create_icmpv4_echo_request, wrap_icmp_in_ipv4, + }; + use nym_ip_packet_requests::codec::MultiIpPacketCodec; + use pnet_packet::Packet; + + let icmp_echo_request = create_icmpv4_echo_request(seq, identifier)?; + let ipv4_packet = wrap_icmp_in_ipv4(icmp_echo_request, ip_pair.ipv4, *target)?; + let bundled_packet = + MultiIpPacketCodec::bundle_one_packet(ipv4_packet.packet().to_vec().into()); + + writer.send_ip_packet(&bundled_packet).await?; + total_v4_pings += 1; + } + } + + let collect_timeout = tokio::time::sleep(Duration::from_secs(10)); + tokio::pin!(collect_timeout); + + loop { + tokio::select! { + _ = &mut collect_timeout => { + info!("Finished collecting responses"); + break; + } + result = reader.handle_incoming() => { + if let Ok(packets) = result { + for packet in packets { + if let Some((reply_id, source, dest)) = is_icmp_echo_reply(&packet) { + if reply_id == identifier && dest == ip_pair.ipv4 { + successful_v4_pings += 1; + debug!("IPv4 reply from {}", source); + } + } + } + } + } + } + } + + let v4_success_rate = if total_v4_pings > 0 { + (successful_v4_pings as f64 / total_v4_pings as f64) * 100.0 + } else { + 0.0 + }; + + info!( + "Split test - IPv4 external connectivity: {}/{} pings successful ({:.1}%)", + successful_v4_pings, total_v4_pings, v4_success_rate + ); + + assert!(successful_v4_pings > 0, "No pings successful"); + assert!( + v4_success_rate >= 75.0, + "IPv4 success rate < 75% (got {:.1}%)", + v4_success_rate + ); + + Ok(()) + } +} diff --git a/sdk/rust/nym-sdk/src/tcp_proxy.rs b/sdk/rust/nym-sdk/src/tcp_proxy.rs index a622e3f6cf1..ddc843adaee 100644 --- a/sdk/rust/nym-sdk/src/tcp_proxy.rs +++ b/sdk/rust/nym-sdk/src/tcp_proxy.rs @@ -163,7 +163,7 @@ //! let codec = BytesCodec::new(); //! let mut framed_read = FramedRead::new(read, codec); //! // Much like the tcpstream, split our Nym client into a sender and receiver for concurrent read/write -//! let sender = client.split_sender(); +//! let mut sender = client.split_sender(); //! // The server / service provider address our client is sending messages to will remain static //! let server_addr = server_address; //! // Store outgoing messages in instance of Dashset abstraction diff --git a/sdk/rust/nym-sdk/src/tcp_proxy/tcp_proxy_client.rs b/sdk/rust/nym-sdk/src/tcp_proxy/tcp_proxy_client.rs index f00a5b30c14..4721f4e4cdc 100644 --- a/sdk/rust/nym-sdk/src/tcp_proxy/tcp_proxy_client.rs +++ b/sdk/rust/nym-sdk/src/tcp_proxy/tcp_proxy_client.rs @@ -161,7 +161,7 @@ impl NymProxyClient { let codec = BytesCodec::new(); let mut framed_read = FramedRead::new(read, codec); // Much like the tcpstream, split our Nym client into a sender and receiver for concurrent read/write - let sender = client.split_sender(); + let mut sender = client.split_sender(); // The server / service provider address our client is sending messages to will remain static let server_addr = server_address; // Store outgoing messages in instance of Dashset abstraction diff --git a/service-providers/ip-packet-router/src/clients/connected_client_handler.rs b/service-providers/ip-packet-router/src/clients/connected_client_handler.rs index ba9f9b32901..d047819d315 100644 --- a/service-providers/ip-packet-router/src/clients/connected_client_handler.rs +++ b/service-providers/ip-packet-router/src/clients/connected_client_handler.rs @@ -219,14 +219,14 @@ impl MixnetMessageSinkTranslator for ToIprDataResponse { #[cfg(test)] mod tests { - use std::sync::{Arc, Mutex}; - use async_trait::async_trait; use bytes::Bytes; use futures::SinkExt; use nym_sdk::mixnet::{AnonymousSenderTag, MixnetMessageSender, MixnetMessageSink}; + use std::sync::{Arc, Mutex}; use tokio::sync::Notify; use tokio_util::codec::FramedWrite; + use tokio_util::sync::PollSender; use super::*; @@ -264,12 +264,15 @@ mod tests { #[async_trait] impl MixnetMessageSender for MockMixnetClientSender { - async fn send(&self, message: InputMessage) -> std::result::Result<(), nym_sdk::Error> { + async fn send(&mut self, message: InputMessage) -> std::result::Result<(), nym_sdk::Error> { let mut sent_messages = self.sent_messages.lock().unwrap(); sent_messages.push(message); self.notify.notify_one(); Ok(()) } + fn sender(&mut self) -> &mut PollSender { + todo!() + } } #[tokio::test] diff --git a/service-providers/ip-packet-router/src/mixnet_listener.rs b/service-providers/ip-packet-router/src/mixnet_listener.rs index c0f36927bcf..e9ee8087b3e 100644 --- a/service-providers/ip-packet-router/src/mixnet_listener.rs +++ b/service-providers/ip-packet-router/src/mixnet_listener.rs @@ -430,7 +430,7 @@ impl MixnetListener { // When an incoming mixnet message triggers a response that we send back, such as during // connect handshake. - async fn handle_response(&self, response: VersionedResponse) -> Result<()> { + async fn handle_response(&mut self, response: VersionedResponse) -> Result<()> { let send_to = response.reply_to.clone(); let response_bytes = response.try_into_bytes()?; let input_message = @@ -445,7 +445,7 @@ impl MixnetListener { // A single incoming request can trigger multiple responses, such as when data requests contain // multiple IP packets. - async fn handle_responses(&self, responses: Vec) { + async fn handle_responses(&mut self, responses: Vec) { for response in responses { match response { Ok(Some(response)) => { diff --git a/service-providers/network-requester/Cargo.toml b/service-providers/network-requester/Cargo.toml index 732c3b7d6cb..848f0327589 100644 --- a/service-providers/network-requester/Cargo.toml +++ b/service-providers/network-requester/Cargo.toml @@ -37,14 +37,23 @@ tap = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true, features = ["net", "rt-multi-thread", "macros"] } tokio-tungstenite = { workspace = true } +tokio-util = { workspace = true } url = { workspace = true } time = { workspace = true } zeroize = { workspace = true } # internal + + nym-async-file-watcher = { path = "../../common/async-file-watcher" } -nym-bin-common = { path = "../../common/bin-common", features = ["output_format", "clap", "basic_tracing"] } -nym-client-core = { path = "../../common/client-core", features = ["cli", "fs-gateways-storage", "fs-surb-storage"] } +nym-client-core = { path = "../../common/client-core", features = [ + "cli", + "fs-gateways-storage", + "fs-surb-storage", +] } +nym-bin-common = { path = "../../common/bin-common", features = [ + "output_format", +] } nym-client-websocket-requests = { path = "../../clients/native/websocket-requests" } nym-config = { path = "../../common/config" } nym-credentials = { path = "../../common/credentials" } diff --git a/service-providers/network-requester/src/core.rs b/service-providers/network-requester/src/core.rs index 6ca9b3b588c..9b6cf866552 100644 --- a/service-providers/network-requester/src/core.rs +++ b/service-providers/network-requester/src/core.rs @@ -7,6 +7,7 @@ use crate::reply::MixnetMessage; use crate::request_filter::RequestFilter; use crate::{reply, socks5}; use async_trait::async_trait; +use futures::SinkExt; use futures::channel::{mpsc, oneshot}; use futures::stream::StreamExt; use log::{debug, error, warn}; @@ -15,7 +16,7 @@ use nym_client_core::HardcodedTopologyProvider; use nym_client_core::client::mix_traffic::transceiver::GatewayTransceiver; use nym_client_core::config::disk_persistence::CommonClientPaths; use nym_network_defaults::NymNetworkDetails; -use nym_sdk::mixnet::{MixnetMessageSender, TopologyProvider}; +use nym_sdk::mixnet::TopologyProvider; use nym_service_providers_common::ServiceProvider; use nym_service_providers_common::interface::{ BinaryInformation, ProviderInterfaceVersion, Request, RequestVersion, @@ -37,6 +38,7 @@ use nym_task::ShutdownTracker; use nym_task::connections::LaneQueueLengths; use std::path::Path; use std::sync::atomic::{AtomicUsize, Ordering}; +use tokio_util::sync::PollSender; // Since it's an atomic, it's safe to be kept static and shared across threads static ACTIVE_PROXIES: AtomicUsize = AtomicUsize::new(0); @@ -240,6 +242,8 @@ impl NRServiceProviderBuilder { // going to be used by `mixnet_response_listener` let (mix_input_sender, mix_input_receiver) = tokio::sync::mpsc::channel::(1); + let mix_input_sender = PollSender::new(mix_input_sender); + // Controller for managing all active connections. let (mut active_connections_controller, controller_sender) = Controller::new( mixnet_client.connection_command_sender(), @@ -337,7 +341,7 @@ impl NRServiceProvider { /// Listens for any messages from `mix_reader` that should be written back to the mix network /// via the `websocket_writer`. async fn mixnet_response_listener( - mixnet_client_sender: nym_sdk::mixnet::MixnetClientSender, + mut mixnet_client_sender: nym_sdk::mixnet::MixnetClientSender, mut mix_input_reader: MixProxyReader, packet_type: PacketType, ) { @@ -346,7 +350,7 @@ impl NRServiceProvider { socks5_msg = mix_input_reader.recv() => { if let Some(msg) = socks5_msg { let response_message = msg.into_input_message(packet_type); - mixnet_client_sender.send(response_message).await.unwrap(); + nym_sdk::mixnet::MixnetMessageSender::send(&mut mixnet_client_sender, response_message).await.unwrap(); } else { log::error!("Exiting: channel closed!"); break; @@ -364,7 +368,7 @@ impl NRServiceProvider { return_address: reply::MixnetAddress, biggest_packet_size: PacketSize, controller_sender: ControllerSender, - mix_input_sender: MixProxySender, + mut mix_input_sender: MixProxySender, lane_queue_lengths: LaneQueueLengths, shutdown: ShutdownTracker, ) { @@ -457,7 +461,7 @@ impl NRServiceProvider { .unwrap_or(traffic_config.primary_packet_size); let controller_sender_clone = self.controller_sender.clone(); - let mix_input_sender_clone = self.mix_input_sender.clone(); + let mut mix_input_sender_clone = self.mix_input_sender.clone(); let lane_queue_lengths_clone = self.mixnet_client.shared_lane_queue_lengths(); // we're just cloning the underlying pointer, nothing expensive is happening here diff --git a/tools/echo-server/src/lib.rs b/tools/echo-server/src/lib.rs index 0fdfc7c0970..5b2fcdfc3cf 100644 --- a/tools/echo-server/src/lib.rs +++ b/tools/echo-server/src/lib.rs @@ -342,7 +342,7 @@ mod tests { let mut client = MixnetClient::connect_new().await?; println!("sending client addr {}", client.nym_address()); - let sender = client.split_sender(); + let mut sender = client.split_sender(); let receiving_task_handle = tokio::spawn(async move { println!("in handle"); diff --git a/wasm/client/Cargo.toml b/wasm/client/Cargo.toml index 6ded8ee2d57..032eba9a9f2 100644 --- a/wasm/client/Cargo.toml +++ b/wasm/client/Cargo.toml @@ -31,6 +31,7 @@ once_cell = { workspace = true } thiserror = { workspace = true } tsify = { workspace = true, features = ["js"] } web-sys = { workspace = true } +tokio = { workspace = true, default-features = false, features = ["sync"] } nym-bin-common = { path = "../../common/bin-common" } wasm-client-core = { path = "../../common/wasm/client-core" } diff --git a/wasm/client/src/client.rs b/wasm/client/src/client.rs index 8c006b7f09f..4d886b7cee3 100644 --- a/wasm/client/src/client.rs +++ b/wasm/client/src/client.rs @@ -16,7 +16,7 @@ use nym_bin_common::bin_info; use nym_gateway_requests::ClientRequest; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use tokio_with_wasm::sync::mpsc; +use tokio_with_wasm::sync::{mpsc, RwLock}; use tsify::Tsify; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; @@ -54,7 +54,7 @@ pub type ClientRequestSender = mpsc::Sender; #[wasm_bindgen] pub struct NymClient { self_address: String, - client_input: Arc, + client_input: Arc>, client_state: Arc, // keep track of the "old" topology for the purposes of node tester @@ -253,7 +253,7 @@ impl NymClientBuilder { Ok(NymClient { self_address, - client_input: Arc::new(client_input), + client_input: Arc::new(RwLock::new(client_input)), client_state: Arc::new(started_client.client_state), _full_topology: None, _task_manager: started_client.shutdown_handle, diff --git a/wasm/client/src/helpers.rs b/wasm/client/src/helpers.rs index b6cdba3d33c..eb98970438b 100644 --- a/wasm/client/src/helpers.rs +++ b/wasm/client/src/helpers.rs @@ -1,8 +1,10 @@ // Copyright 2022-2023 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use futures::SinkExt; use js_sys::Promise; use std::sync::Arc; +use tokio_with_wasm::sync::RwLock; use wasm_bindgen::JsValue; use wasm_bindgen_futures::future_to_promise; use wasm_client_core::client::base_client::{ClientInput, ClientState}; @@ -48,10 +50,11 @@ pub(crate) trait InputSender { fn send_messages(&self, messages: Vec) -> Promise; } -impl InputSender for Arc { +impl InputSender for Arc> { fn send_message(&self, message: InputMessage) -> Promise { let this = Arc::clone(self); future_to_promise(async move { + let mut this = this.write().await; match this.input_sender.send(message).await { Ok(_) => Ok(JsValue::null()), Err(_) => Err(simple_js_error( @@ -64,6 +67,7 @@ impl InputSender for Arc { fn send_messages(&self, messages: Vec) -> Promise { let this = Arc::clone(self); future_to_promise(async move { + let mut this = this.write().await; for message in messages { if this.input_sender.send(message).await.is_err() { return Err(simple_js_error( diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index 736c82c4139..60a51f03c6c 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -8,11 +8,14 @@ use crate::go_bridge::goWasmSetMixFetchRequestTimeout; use crate::request_writer::RequestWriter; use crate::socks_helpers::{socks5_connect_request, socks5_data_request}; use crate::{config, RequestId}; +use futures::SinkExt; use js_sys::Promise; use nym_bin_common::bin_info; use nym_socks5_requests::RemoteAddress; use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use tokio::sync::Mutex; +use tokio::sync::RwLock; use wasm_bindgen::prelude::*; use wasm_bindgen_futures::future_to_promise; use wasm_client_core::client::base_client::storage::GatewaysDetailsStore; @@ -36,7 +39,7 @@ pub struct MixFetchClient { self_address: Recipient, - client_input: ClientInput, + client_input: Arc>, requests: ActiveRequests, @@ -185,7 +188,7 @@ impl MixFetchClientBuilder { invalidated: AtomicBool::new(false), mix_fetch_config: self.config.mix_fetch, self_address, - client_input, + client_input: Arc::new(RwLock::new(client_input)), requests: active_requests, _shutdown_manager: Mutex::new(started_client.shutdown_handle), }) @@ -261,6 +264,8 @@ impl MixFetchClient { // the expect here is fine as it implies an unrecoverable failure since one of the client core // tasks has terminated self.client_input + .write() + .await .input_sender .send(input) .await @@ -288,6 +293,8 @@ impl MixFetchClient { // the expect here is fine as it implies an unrecoverable failure since one of the client core // tasks has terminated self.client_input + .write() + .await .input_sender .send(input) .await