diff --git a/Cargo.lock b/Cargo.lock index 464607e76..36d357f2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,36 @@ dependencies = [ "gimli", ] +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -118,6 +148,18 @@ dependencies = [ "rustversion", ] +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "asn1-rs" version = "0.7.1" @@ -142,7 +184,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -154,7 +196,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -202,7 +244,16 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", ] [[package]] @@ -307,6 +358,9 @@ name = "bitflags" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] [[package]] name = "block-buffer" @@ -326,6 +380,12 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.11.1" @@ -449,6 +509,24 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + [[package]] name = "ciborium" version = "0.2.2" @@ -476,6 +554,16 @@ dependencies = [ "half", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "4.5.60" @@ -507,7 +595,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -539,6 +627,15 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "colored" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "combine" version = "4.6.7" @@ -553,12 +650,41 @@ dependencies = [ "tokio-util", ] +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -788,6 +914,34 @@ dependencies = [ "itertools 0.10.5", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -807,6 +961,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -852,7 +1015,42 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[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.117", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", ] [[package]] @@ -913,6 +1111,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -925,6 +1124,37 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users 0.5.2", + "windows-sys 0.61.2", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -932,7 +1162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", - "redox_users", + "redox_users 0.4.6", "winapi", ] @@ -944,9 +1174,15 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + [[package]] name = "ed25519" version = "2.2.3" @@ -987,6 +1223,12 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -1033,6 +1275,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fallible-iterator" version = "0.3.0" @@ -1068,6 +1331,34 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1080,6 +1371,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -1156,7 +1453,7 @@ checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1259,10 +1556,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" dependencies = [ "fallible-iterator", - "indexmap", + "indexmap 2.13.0", "stable_deref_trait", ] +[[package]] +name = "git-version" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad568aa3db0fcbc81f2f116137f263d7304f512a1209b35b85150d3ef88ad19" +dependencies = [ + "git-version-macro", +] + +[[package]] +name = "git-version-macro" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "h2" version = "0.3.27" @@ -1275,7 +1592,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -1294,7 +1611,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.4.0", - "indexmap", + "indexmap 2.13.0", "slab", "tokio", "tokio-util", @@ -1312,13 +1629,38 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", "serde", ] @@ -1327,6 +1669,35 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash 0.2.0", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin 0.9.8", + "stable_deref_trait", +] [[package]] name = "heck" @@ -1377,6 +1748,13 @@ dependencies = [ "wrpc-transport", ] +[[package]] +name = "hello-component-zclient" +version = "0.1.0" +dependencies = [ + "wit-bindgen 0.45.1", +] + [[package]] name = "hello-http-tcp-proxy" version = "0.1.0" @@ -1522,12 +1900,108 @@ dependencies = [ ] [[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] +name = "hello-zenoh-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "serde_json", + "tokio", + "tracing-subscriber", + "url", + "wit-bindgen-wrpc", + "wrpc-cli", + "wrpc-transport-zenoh", + "zenoh", +] + +[[package]] +name = "hello-zenoh-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "futures", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", + "url", + "wit-bindgen-wrpc", + "wrpc-cli", + "wrpc-transport-zenoh", + "zenoh", +] + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "hotpath" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51462b35dc551217e1d1dd3f8c5eebbb5df7103370b1e55d24c19a3411d290f7" +dependencies = [ + "arc-swap", + "cfg-if", + "clap", + "colored", + "crossbeam-channel", + "eyre", + "futures-util", + "hdrhistogram", + "hotpath-macros", + "libc", + "mach2 0.6.0", + "pin-project-lite", + "prettytable-rs", + "quanta", + "regex", + "serde", + "serde_json", + "tiny_http", + "tokio", +] + +[[package]] +name = "hotpath-macros" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f022365c90cc04455f17a2b9ba5fe0e661cde1ab27434d382f1f8693fe124e7d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] name = "httlib-huffman" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1785,6 +2259,12 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "1.1.0" @@ -1806,6 +2286,23 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -1818,6 +2315,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "io-extras" version = "0.18.4" @@ -1840,6 +2346,15 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" +[[package]] +name = "ipnetwork" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf466541e9d546596ee94f9f69590f89473455f88372423e0008fc1a7daf100e" +dependencies = [ + "serde", +] + [[package]] name = "is-terminal" version = "0.4.17" @@ -1952,6 +2467,35 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keyed-set" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d255a6b6ecd77bb93ce91de984d7039bff7503f500eb4851a1269732f22baf" +dependencies = [ + "hashbrown 0.14.5", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1981,6 +2525,16 @@ version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + [[package]] name = "libm" version = "0.2.16" @@ -2014,6 +2568,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -2041,7 +2604,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn", + "syn 2.0.117", ] [[package]] @@ -2059,6 +2622,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +[[package]] +name = "lz4_flex" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +dependencies = [ + "twox-hash", +] + [[package]] name = "mach2" version = "0.4.3" @@ -2068,6 +2640,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + [[package]] name = "matchers" version = "0.2.0" @@ -2126,6 +2704,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.1.1" @@ -2137,6 +2725,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom 0.2.17", +] + [[package]] name = "nkeys" version = "0.4.5" @@ -2152,6 +2749,12 @@ dependencies = [ "signatory", ] +[[package]] +name = "no-std-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43794a0ace135be66a25d3ae77d41b91615fb68ae937f904090203e81f755b65" + [[package]] name = "nom" version = "7.1.3" @@ -2162,6 +2765,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonempty-collections" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e216d0e8cf9d54fa66e5780f6e1d5dc96d1c1b3c25aeba3b6758548bcbbd8b9d" +dependencies = [ + "serde", +] + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -2214,6 +2826,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "object" version = "0.37.3" @@ -2222,7 +2844,7 @@ checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "crc32fast", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "memchr", ] @@ -2271,6 +2893,47 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pem" version = "3.0.6" @@ -2296,6 +2959,104 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "petgraph" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" +dependencies = [ + "fixedbitset", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "serde", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.11" @@ -2313,7 +3074,7 @@ checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2372,6 +3133,38 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "pnet_base" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc190d4067df16af3aba49b3b74c469e611cad6314676eaf1157f31aa0fb2f7" +dependencies = [ + "no-std-net", +] + +[[package]] +name = "pnet_datalink" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79e70ec0be163102a332e1d2d5586d362ad76b01cec86f830241f2b6452a7b7" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi", +] + +[[package]] +name = "pnet_sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d4643d3d4db6b08741050c2f3afa9a892c4244c085a72fcda93c9c2c9a00f4b" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -2387,6 +3180,7 @@ dependencies = [ "cobs", "embedded-io 0.4.0", "embedded-io 0.6.1", + "heapless", "serde", ] @@ -2421,7 +3215,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "prettytable-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" +dependencies = [ + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width 0.1.14", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", ] [[package]] @@ -2453,7 +3269,22 @@ checksum = "56000349b6896e3d44286eb9c330891237f40b27fd43c1ccc84547d0b463cb40" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", ] [[package]] @@ -2592,6 +3423,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "rayon" version = "1.11.0" @@ -2657,6 +3497,15 @@ dependencies = [ "url", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + [[package]] name = "redox_users" version = "0.4.6" @@ -2668,6 +3517,37 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "regalloc2" version = "0.13.5" @@ -2775,6 +3655,30 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuffer-spsc" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3e7aa0a681b232e7cd7f856a53b10603df88ca74b79a8d8088845185492e35" +dependencies = [ + "array-init", + "crossbeam", +] + +[[package]] +name = "ron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd490c5b18261893f14449cbd28cb9c0b637aebf161cd77900bfdedaff21ec32" +dependencies = [ + "bitflags 2.11.0", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -2953,44 +3857,113 @@ dependencies = [ ] [[package]] -name = "rustls-webpki" -version = "0.103.9" +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", + "dyn-clone", + "either", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", ] [[package]] -name = "rustversion" -version = "1.0.22" +name = "schemars_derive" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] [[package]] -name = "ryu" -version = "1.0.23" +name = "scopeguard" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "same-file" -version = "1.0.6" +name = "sdd" +version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" [[package]] -name = "schannel" -version = "0.1.29" +name = "secrecy" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ - "windows-sys 0.61.2", + "serde", + "zeroize", ] [[package]] @@ -3072,7 +4045,18 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] @@ -3105,7 +4089,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3129,6 +4113,76 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "serial_test" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "sha2" version = "0.10.9" @@ -3140,6 +4194,22 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -3149,6 +4219,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" +dependencies = [ + "dirs", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3187,6 +4266,18 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + [[package]] name = "slab" version = "0.4.12" @@ -3222,6 +4313,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + [[package]] name = "spki" version = "0.7.3" @@ -3232,6 +4338,41 @@ dependencies = [ "der", ] +[[package]] +name = "stabby" +version = "72.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976399a0c48ea769ef7f5dc303bb88240ab8d84008647a6b2303eced3dab3945" +dependencies = [ + "rustversion", + "stabby-abi", +] + +[[package]] +name = "stabby-abi" +version = "72.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7b54832a9a1f92a0e55e74a5c0332744426edc515bb3fbad82f10b874a87f0d" +dependencies = [ + "rustc_version", + "rustversion", + "sha2-const-stable", + "stabby-macros", +] + +[[package]] +name = "stabby-macros" +version = "72.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a768b1e51e4dbfa4fa52ae5c01241c0a41e2938fdffbb84add0c8238092f9091" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "rand 0.8.5", + "syn 1.0.109", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3314,6 +4455,45 @@ dependencies = [ "wrpc-transport-quic", ] +[[package]] +name = "streams-zenoh-client" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "clap", + "futures", + "serde_json", + "tokio", + "tokio-stream", + "tracing", + "tracing-subscriber", + "url", + "wit-bindgen-wrpc", + "wrpc-cli", + "wrpc-transport-zenoh", + "zenoh", +] + +[[package]] +name = "streams-zenoh-server" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "clap", + "futures", + "serde_json", + "tokio", + "tracing", + "tracing-subscriber", + "url", + "wit-bindgen-wrpc", + "wrpc-cli", + "wrpc-transport-zenoh", + "zenoh", +] + [[package]] name = "strsim" version = "0.11.1" @@ -3326,6 +4506,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -3357,7 +4548,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3415,6 +4606,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.4.1" @@ -3452,7 +4654,7 @@ checksum = "be35209fd0781c5401458ab66e4f98accf63553e8fae7425503e92fdd319783b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3481,7 +4683,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3492,7 +4694,7 @@ checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3535,6 +4737,18 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + [[package]] name = "tinystr" version = "0.8.2" @@ -3570,6 +4784,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "token-cell" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb48920ae769b58126c8c93269805011c793201f95fde28b479b81a9a531bbde" +dependencies = [ + "paste", + "portable-atomic", + "rustversion", +] + [[package]] name = "tokio" version = "1.50.0" @@ -3594,7 +4819,7 @@ checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3640,6 +4865,7 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", + "futures-util", "pin-project-lite", "tokio", ] @@ -3671,10 +4897,10 @@ version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap", + "indexmap 2.13.0", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", "winnow", @@ -3689,6 +4915,27 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.0.0+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c2555c699578a4f59f0cc68e5116c8d7cabbd45e1409b989d4be085b53f13e" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.4+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7193cbd0ce53dc966037f54351dbbcf0d5a642c7f0038c382ef9e677ce8c13f2" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 1.0.0+spec-1.1.0", + "toml_parser", + "winnow", +] + [[package]] name = "toml_parser" version = "1.0.9+spec-1.1.0" @@ -3777,7 +5024,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3801,6 +5048,16 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.22" @@ -3811,36 +5068,71 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex-automata", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tryhard" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fe58ebd5edd976e0fe0f8a14d2a04b7c81ef153ea9a54eebc42e67c2c23b4e5" +dependencies = [ + "pin-project-lite", + "tokio", ] [[package]] -name = "try-lock" -version = "0.2.5" +name = "twox-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] -name = "tryhard" -version = "0.5.2" +name = "uhlc" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fe58ebd5edd976e0fe0f8a14d2a04b7c81ef153ea9a54eebc42e67c2c23b4e5" +checksum = "b62a645e3e4e6c85b7abe49b086aa3204119431f42b6123b0070419fb6e9d24e" dependencies = [ - "pin-project-lite", - "tokio", + "humantime", + "lazy_static", + "log", + "rand 0.8.5", + "serde", + "spin 0.10.0", ] -[[package]] -name = "typenum" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" - [[package]] name = "unicase" version = "2.9.0" @@ -3853,6 +5145,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-width" version = "0.2.2" @@ -3865,12 +5163,29 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "unzip-n" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b5bb2756c16fb66f80cfbf5fb0e0c09a7001e739f453c9ec241b9c8b1556fda" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "url" version = "2.5.8" @@ -3916,12 +5231,42 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "validated_struct" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "869a93e8a7286e339e1128630051d82babbcd75d585975af07b9f3327220e60e" +dependencies = [ + "json5", + "serde", + "serde_json", + "validated_struct_macros", +] + +[[package]] +name = "validated_struct_macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c44ce98e7227a04eeb4cf9c784109a5c9710e54849ceb4f09f8597247897f1e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "unzip-n", +] + [[package]] name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" @@ -4212,7 +5557,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -4282,7 +5627,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00094573b000c92134f2ef0f8afa4f6f892de37e78442988c946243a8c44364e" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder 0.238.1", "wasmparser 0.238.1", ] @@ -4294,7 +5639,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder 0.239.0", "wasmparser 0.239.0", ] @@ -4306,7 +5651,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", - "indexmap", + "indexmap 2.13.0", "wasm-encoder 0.244.0", "wasmparser 0.244.0", ] @@ -4330,7 +5675,7 @@ version = "0.243.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bd23c879ec35d708f4eff3456f33c415d113363a2e38420098bf42976bacb31" dependencies = [ - "indexmap", + "indexmap 2.13.0", "logos", "thiserror 2.0.18", "wit-parser 0.243.0", @@ -4343,7 +5688,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d07b6a3b550fefa1a914b6d54fc175dd11c3392da11eee604e6ffc759805d25" dependencies = [ "bitflags 2.11.0", - "indexmap", + "indexmap 2.13.0", "semver", ] @@ -4355,7 +5700,7 @@ checksum = "3fa99c8328024423875ae4a55345cfde8f0371327fb2d0f33b0f52a06fc44408" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] @@ -4367,7 +5712,7 @@ checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] @@ -4379,7 +5724,7 @@ checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", "serde", ] @@ -4392,7 +5737,7 @@ checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags 2.11.0", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "semver", ] @@ -4403,7 +5748,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" dependencies = [ "bitflags 2.11.0", - "indexmap", + "indexmap 2.13.0", "semver", ] @@ -4435,11 +5780,11 @@ dependencies = [ "fxprof-processed-profile", "gimli", "hashbrown 0.15.5", - "indexmap", + "indexmap 2.13.0", "ittapi", "libc", "log", - "mach2", + "mach2 0.4.3", "memfd", "object", "once_cell", @@ -4498,7 +5843,7 @@ dependencies = [ "cranelift-bitset", "cranelift-entity", "gimli", - "indexmap", + "indexmap 2.13.0", "log", "object", "postcard", @@ -4543,7 +5888,7 @@ dependencies = [ "anyhow", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasmtime-internal-component-util", "wasmtime-internal-wit-bindgen", "wit-parser 0.243.0", @@ -4657,7 +6002,7 @@ checksum = "70f8b9796a3f0451a7b702508b303d654de640271ac80287176de222f187a237" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4686,7 +6031,7 @@ dependencies = [ "anyhow", "bitflags 2.11.0", "heck", - "indexmap", + "indexmap 2.13.0", "wit-parser 0.243.0", ] @@ -4766,7 +6111,7 @@ dependencies = [ "bumpalo", "leb128fmt", "memchr", - "unicode-width", + "unicode-width 0.2.2", "wasm-encoder 0.245.1", ] @@ -4898,7 +6243,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -4909,7 +6254,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5238,6 +6583,9 @@ name = "winnow" version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] [[package]] name = "winreg" @@ -5318,9 +6666,9 @@ checksum = "f089b7beb50fdcf0b9dc68be920843d743c00cbbd5ef1fa21c727f9b5089dac5" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata 0.238.1", "wit-bindgen-core 0.45.1", "wit-component 0.238.1", @@ -5334,9 +6682,9 @@ checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", - "indexmap", + "indexmap 2.13.0", "prettyplease", - "syn", + "syn 2.0.117", "wasm-metadata 0.244.0", "wit-bindgen-core 0.51.0", "wit-component 0.244.0", @@ -5352,7 +6700,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core 0.45.1", "wit-bindgen-rust 0.45.1", ] @@ -5367,7 +6715,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core 0.51.0", "wit-bindgen-rust 0.51.0", ] @@ -5412,7 +6760,7 @@ dependencies = [ "prettyplease", "serde", "serde_json", - "syn", + "syn 2.0.117", "test-helpers", "tokio", "wit-bindgen-core 0.36.0", @@ -5429,7 +6777,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wit-bindgen-core 0.36.0", "wit-bindgen-wrpc-rust", ] @@ -5442,7 +6790,7 @@ checksum = "7d31c985f541330d1a809547043ad19dd58739a2f83c7f116aeabcab86aed597" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -5461,7 +6809,7 @@ checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -5480,7 +6828,7 @@ checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags 2.11.0", - "indexmap", + "indexmap 2.13.0", "log", "serde", "serde_derive", @@ -5499,7 +6847,7 @@ checksum = "ae2a7999ed18efe59be8de2db9cb2b7f84d88b27818c79353dfc53131840fe1a" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -5517,7 +6865,7 @@ checksum = "1eea12c964ed423ed14745e51aac2f1e28e5572ca012b0503bdcf65ffee3b44c" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -5535,7 +6883,7 @@ checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -5553,7 +6901,7 @@ checksum = "df983a8608e513d8997f435bb74207bf0933d0e49ca97aa9d8a6157164b9b7fc" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -5571,7 +6919,7 @@ checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", - "indexmap", + "indexmap 2.13.0", "log", "semver", "serde", @@ -5597,6 +6945,7 @@ dependencies = [ "clap", "criterion", "futures", + "serial_test", "tempfile", "test-log", "tokio", @@ -5616,7 +6965,9 @@ dependencies = [ "wrpc-transport-nats", "wrpc-transport-quic", "wrpc-transport-web", + "wrpc-transport-zenoh", "wrpc-wasmtime-cli", + "zenoh", ] [[package]] @@ -5625,8 +6976,10 @@ version = "0.7.0" dependencies = [ "anyhow", "async-nats", + "serde_json", "tokio", "tracing-subscriber", + "zenoh", ] [[package]] @@ -5675,10 +7028,13 @@ dependencies = [ "quinn", "rcgen", "rustls 0.23.37", + "serde_json", "tokio", "tracing", "wrpc-cli", + "wrpc-transport-zenoh", "wtransport", + "zenoh", ] [[package]] @@ -5747,6 +7103,28 @@ dependencies = [ "wtransport", ] +[[package]] +name = "wrpc-transport-zenoh" +version = "0.29.0" +dependencies = [ + "anyhow", + "bytes", + "flume", + "futures", + "hotpath", + "nuid", + "postcard", + "serde", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", + "uuid", + "wasm-tokio", + "wrpc-transport", + "zenoh", +] + [[package]] name = "wrpc-wasi-keyvalue" version = "0.1.1" @@ -5808,6 +7186,8 @@ dependencies = [ "wrpc-runtime-wasmtime", "wrpc-transport", "wrpc-transport-nats", + "wrpc-transport-zenoh", + "zenoh", ] [[package]] @@ -5909,10 +7289,372 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] +[[package]] +name = "zenoh" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9ff8cb89f5267b8486a69466bc42f240f1ee2d5089e72395a23094e7b74f21" +dependencies = [ + "ahash", + "arc-swap", + "async-trait", + "bytes", + "const_format", + "flate2", + "flume", + "futures", + "git-version", + "itertools 0.14.0", + "json5", + "lazy_static", + "nonempty-collections", + "once_cell", + "petgraph", + "phf", + "rand 0.8.5", + "ref-cast", + "rustc_version", + "serde", + "serde_json", + "socket2 0.5.10", + "tokio", + "tokio-util", + "tracing", + "uhlc", + "vec_map", + "zenoh-buffers", + "zenoh-codec", + "zenoh-collections", + "zenoh-config", + "zenoh-core", + "zenoh-keyexpr", + "zenoh-link", + "zenoh-link-commons", + "zenoh-macros", + "zenoh-plugin-trait", + "zenoh-protocol", + "zenoh-result", + "zenoh-runtime", + "zenoh-sync", + "zenoh-task", + "zenoh-transport", + "zenoh-util", +] + +[[package]] +name = "zenoh-buffers" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9216c3d6c84b56f3e3be634e52365022038e1ac1b9f662f10d425cbf6c0fa8" +dependencies = [ + "zenoh-collections", +] + +[[package]] +name = "zenoh-codec" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14bc6747664aa9ecf17becd6e9a29282e535a350cd7c6bd8de7bf2dc662fb93d" +dependencies = [ + "tracing", + "uhlc", + "zenoh-buffers", + "zenoh-protocol", +] + +[[package]] +name = "zenoh-collections" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d642ecfe0d85f0cd846be9bc92805d926c092a6e6c7a575b6346752f8c3ae16" +dependencies = [ + "ahash", +] + +[[package]] +name = "zenoh-config" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39765a5f9975aba204c99f2f65308db4952dbea8e5ac79c78ac1eaf5711e970a" +dependencies = [ + "json5", + "nonempty-collections", + "num_cpus", + "secrecy", + "serde", + "serde_json", + "serde_with", + "serde_yaml", + "tracing", + "uhlc", + "validated_struct", + "zenoh-core", + "zenoh-keyexpr", + "zenoh-macros", + "zenoh-protocol", + "zenoh-result", + "zenoh-util", +] + +[[package]] +name = "zenoh-core" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c0c1388dccf287aec4e9d5e638630dc9d536db9f1da3522889b42697723b9b" +dependencies = [ + "lazy_static", + "tokio", + "zenoh-result", + "zenoh-runtime", +] + +[[package]] +name = "zenoh-crypto" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b433e08df3b03f2af2d23bd29a32aa5f5522c52e66d63e3d135bfa66373736dd" +dependencies = [ + "aes", + "hmac", + "rand 0.8.5", + "rand_chacha 0.3.1", + "sha3", + "zenoh-result", +] + +[[package]] +name = "zenoh-keyexpr" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a3c47c89cb55ea45a1b3fe7d1fe8682ea93530b1fc5245257812db14b55b3d" +dependencies = [ + "getrandom 0.2.17", + "hashbrown 0.16.1", + "keyed-set", + "rand 0.8.5", + "schemars 1.2.1", + "serde", + "token-cell", + "zenoh-result", +] + +[[package]] +name = "zenoh-link" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6218cecab58435f31fb8b2e185f74f35af8aedd96e8bdd3557b333206b1acfda" +dependencies = [ + "zenoh-config", + "zenoh-link-commons", + "zenoh-link-tcp", + "zenoh-protocol", + "zenoh-result", +] + +[[package]] +name = "zenoh-link-commons" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98adc618f7edb570b9333ce583934a7c63e3a619cb49666515bfc06a000d7b6" +dependencies = [ + "async-trait", + "flume", + "futures", + "serde", + "socket2 0.5.10", + "time", + "tokio", + "tokio-util", + "tracing", + "zenoh-buffers", + "zenoh-codec", + "zenoh-core", + "zenoh-protocol", + "zenoh-result", + "zenoh-runtime", + "zenoh-util", +] + +[[package]] +name = "zenoh-link-tcp" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f23bd5d06a0014ce5a205961d6d47c8e8d792d9fd050ae9d0c9b609a187995" +dependencies = [ + "async-trait", + "socket2 0.5.10", + "tokio", + "tokio-util", + "tracing", + "zenoh-config", + "zenoh-core", + "zenoh-link-commons", + "zenoh-protocol", + "zenoh-result", +] + +[[package]] +name = "zenoh-macros" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b760a458cd906ac888b37fd1abdb21a0f58ecc64cc3882f83a976cb5ca8e0632" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "zenoh-keyexpr", +] + +[[package]] +name = "zenoh-plugin-trait" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7325b773c43a86a94f800cb971ab7e4b7e01ce76819c9c100ea783a47c3a25e4" +dependencies = [ + "git-version", + "libloading", + "serde", + "stabby", + "tracing", + "zenoh-config", + "zenoh-keyexpr", + "zenoh-macros", + "zenoh-result", + "zenoh-util", +] + +[[package]] +name = "zenoh-protocol" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4d3dad7aeeea780495692b195cd56515569c32b76b9dd077cc408c3ebca03f" +dependencies = [ + "const_format", + "rand 0.8.5", + "serde", + "uhlc", + "zenoh-buffers", + "zenoh-keyexpr", + "zenoh-result", +] + +[[package]] +name = "zenoh-result" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b4dbfea68b947a790d5525bcf061e91e2fdc2798bce619851919b353a8580fa" +dependencies = [ + "anyhow", +] + +[[package]] +name = "zenoh-runtime" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "760a1f7880f98427ad849d600257d1455a18afe981681f362684a3f91042537e" +dependencies = [ + "lazy_static", + "ron", + "serde", + "tokio", + "tracing", + "zenoh-macros", + "zenoh-result", +] + +[[package]] +name = "zenoh-sync" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98f132137bb003f10b7fff086cb18addf8e8273b9c0d2722a53b5074c8a79965" +dependencies = [ + "arc-swap", + "event-listener", + "futures", + "tokio", + "zenoh-buffers", + "zenoh-collections", + "zenoh-core", +] + +[[package]] +name = "zenoh-task" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b17d10136fdabec7e21a3fcef568c210ee6a2d71cde6adcde99e9236584f3a1" +dependencies = [ + "futures", + "tokio", + "tokio-util", + "tracing", + "zenoh-core", + "zenoh-runtime", +] + +[[package]] +name = "zenoh-transport" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50739b4c45e0963df8377abddb74701a4b708b178590eae92f27a604e25daf44" +dependencies = [ + "async-trait", + "crossbeam-utils", + "flume", + "lazy_static", + "lz4_flex", + "rand 0.8.5", + "ringbuffer-spsc", + "serde", + "sha3", + "tokio", + "tokio-util", + "tracing", + "zenoh-buffers", + "zenoh-codec", + "zenoh-config", + "zenoh-core", + "zenoh-crypto", + "zenoh-link", + "zenoh-link-commons", + "zenoh-protocol", + "zenoh-result", + "zenoh-runtime", + "zenoh-sync", + "zenoh-task", + "zenoh-util", +] + +[[package]] +name = "zenoh-util" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9512987c13925d32d3331507c8807853d5b682ea8da94d0ba6534c7a8ace48aa" +dependencies = [ + "async-trait", + "const_format", + "flume", + "home", + "humantime", + "lazy_static", + "libc", + "libloading", + "pnet_datalink", + "schemars 1.2.1", + "serde", + "serde_json", + "shellexpand", + "tokio", + "tracing", + "tracing-subscriber", + "winapi", + "zenoh-core", + "zenoh-result", +] + [[package]] name = "zerocopy" version = "0.8.42" @@ -5930,7 +7672,7 @@ checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -5950,7 +7692,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -5990,7 +7732,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b2ae54b12..0461bf99d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ members = [ ] [features] -default = ["bin", "nats", "net", "quic", "wasmtime", "web-transport"] +default = ["bin", "nats", "net", "quic", "wasmtime", "web-transport", "zenoh-transport"] bin = ["bin-bindgen", "bin-wasmtime"] bin-bindgen = [ @@ -43,6 +43,7 @@ net = ["wrpc-transport/net"] quic = ["dep:wrpc-transport-quic"] wasmtime = ["dep:wrpc-runtime-wasmtime"] web-transport = ["dep:wrpc-transport-web"] +zenoh-transport = ["dep:zenoh", "dep:wrpc-transport-zenoh", "wrpc-cli/zenoh-transport"] [[bin]] name = "wit-bindgen-wrpc" @@ -84,6 +85,8 @@ wrpc-transport-nats = { workspace = true, optional = true } wrpc-transport-quic = { workspace = true, optional = true } wrpc-transport-web = { workspace = true, optional = true } wrpc-wasmtime-cli = { workspace = true, optional = true } +wrpc-transport-zenoh = { workspace = true, optional = true } +zenoh = { workspace = true, optional = true } [dev-dependencies] anyhow = { workspace = true } @@ -114,8 +117,10 @@ wasmtime-cli-flags = { workspace = true, features = [ "pooling-allocator", "threads", ] } -wrpc-test = { workspace = true, features = ["nats", "quic", "web-transport"] } +wrpc-test = { workspace = true, features = ["nats", "quic", "web-transport", "zenoh-transport"] } wrpc-transport = { workspace = true, features = ["net"] } +serial_test = "*" + [workspace.dependencies] anyhow = { version = "1", default-features = false } @@ -182,8 +187,12 @@ wrpc-transport = { version = "0.29", path = "./crates/transport", default-featur wrpc-transport-nats = { version = "0.31", path = "./crates/transport-nats", default-features = false } wrpc-transport-quic = { version = "0.6", path = "./crates/transport-quic", default-features = false } wrpc-transport-web = { version = "0.3", path = "./crates/transport-web", default-features = false } +wrpc-transport-zenoh = { version = "0.29", path = "./crates/transport-zenoh", default-features = false } wrpc-wasi-keyvalue = { version = "0.1.1", path = "./crates/wasi-keyvalue", default-features = false } wrpc-wasi-keyvalue-mem = { version = "0.2", path = "./crates/wasi-keyvalue-mem", default-features = false } wrpc-wasi-keyvalue-redis = { version = "0.2", path = "./crates/wasi-keyvalue-redis", default-features = false } wrpc-wasmtime-cli = { version = "0.9", path = "./crates/wasmtime-cli", default-features = false } wtransport = { version = "0.6.1", default-features = false } +zenoh = {version = "1.7", default-features = false} +flume = {version = "0.11", default-features = false} +postcard = {version = "1.1"} diff --git a/README.md b/README.md index d74e8b996..50dac8b33 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,128 @@ We will use the following two Rust wRPC applications using [NATS.io] transport: wrpc-wasmtime nats run --import native ./target/wasm32-wasip2/release/hello-component-client.wasm ``` +#### Using [zenoh.io] transport + +We will use the following two Rust wRPC applications using [zenoh.io] transport: +- [examples/rust/hello-zenoh-client](examples/rust/hello-zenoh-client) +- [examples/rust/hello-zenoh-server](examples/rust/hello-zenoh-server) + +1. Run [zenoh.io] (more thorough documentation available [here](https://docs.nats.io/running-a-nats-service/introduction/running)): + + - using standalone binary: + ```sh + zenohd + ``` + + - using nix flake develop environment: + ```sh + nix develop + zenohd + ``` + +2. Serve Wasm `hello` server via [zenoh.io] + + ```sh + wrpc-wasmtime zenoh serve --export rust ./target/wasm32-wasip2/release/hello_component_server.wasm + ``` + + - Sample output: + > INFO async_nats: event: connected + > + > INFO wrpc_wasmtime_cli: serving instance function name="hello" + +3. Call Wasm `hello` server using a Wasm `hello` client via [zenoh.io]: + + ```sh + wrpc-wasmtime zenoh run --import rust ./target/wasm32-wasip2/release/hello-component-client.wasm + ``` + + - Sample output in the client: + > INFO async_nats: event: connected + > + >hello from Rust + + - Sample output in the server: + > INFO wrpc_wasmtime_cli: serving instance function invocation + > + > INFO wrpc_wasmtime_cli: successfully served instance function invocation + +4. Call the Wasm `hello` server using a native wRPC `hello` client via [zenoh.io]: + + ```sh + cargo run -p hello-zenoh-client rust + ``` + +5. Serve native wRPC `hello` server via [zenoh.io]: + + ```sh + cargo run -p hello-zenoh-server native + ``` + +6. Call both the native wRPC `hello` server and Wasm `hello` server using native wRPC `hello` client via [zenoh.io]: + + ```sh + cargo run -p hello-zenoh-client rust native + ``` + +7. Call native wRPC `hello` server using Wasm `hello` client via [zenoh.io]: + + ```sh + wrpc-wasmtime zenoh run --import native ./target/wasm32-wasip2/release/hello-component-client.wasm + ``` + + +To test the transport-zenoh package run: + + ```sh + cargo test zenoh -- --nocapture + ``` + + - Sample output: + > test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 13 filtered out; finished in 6.17s + +### Zenoh Configuration +By default we set Zenoh to run in **client mode**. In this mode it behaves +similarly to a typical NATS client: the application connects to a local +Zenoh router (`zenohd`) that acts as the message broker. + +The client attempts to connect to a `zenohd` instance running on the +**default Zenoh port (`7447`) on the same machine**. + +Zenohd has been +added to the flake nix file and is available after running nix develop in the +command lnine. + +This behavior can be altered by providing a **custom Zenoh configuration +file** and setting the `ZENOH_CONFIG` environment variable to its path. + +Example: + +``` bash +export ZENOH_CONFIG="/path/to/config/zenoh_conf.json5" +``` + + +When this environment variable is set, Zenoh will load the configuration +from the specified file instead of using the default settings. + +Example configuration: + +``` json +{ + "mode": "client", + "listen": { + "endpoints": ["tcp/0.0.0.0:7447"] + } +} +``` + + +More information about available configuration options and how to confige zenoh in peer mode can be found in +the official Zenoh configuration manual: + +https://zenoh.io/docs/manual/configuration/ + ## Repository structure This repository contains (for all supported languages): @@ -256,3 +378,4 @@ Whether you're a seasoned developer or just getting started, your contributions [Docker]: https://www.docker.com/ [NATS.io]: https://nats.io/ +[zenoh.io]: https://zenoh.io/ diff --git a/benches/reactor/Cargo.toml b/benches/reactor/Cargo.toml index da8745740..279ec6c91 100644 --- a/benches/reactor/Cargo.toml +++ b/benches/reactor/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true repository.workspace = true [lib] -crate-type = ["rlib", "cdylib"] +crate-type = ["rlib"] [dependencies] wit-bindgen = { workspace = true, features = ["macros", "realloc"] } diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index b2d37fd3f..ba7a3ff87 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -11,8 +11,9 @@ license.workspace = true repository.workspace = true [features] -default = ["nats"] +default = ["nats", "zenoh-transport"] nats = ["async-nats/ring", "dep:async-nats", "dep:tokio", "tokio/sync"] +zenoh-transport = ["dep:zenoh", "dep:tokio", "tokio/sync"] [dependencies] anyhow = { workspace = true, features = ["std"] } @@ -25,3 +26,5 @@ tracing-subscriber = { workspace = true, features = [ "smallvec", "tracing-log", ] } +zenoh = { workspace = true, optional = true, features = ["transport_tcp"] } +serde_json = { workspace = true, default-features = false } \ No newline at end of file diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index f6e19896d..34ac85549 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,3 +1,5 @@ #[cfg(feature = "nats")] pub mod nats; pub mod tracing; +#[cfg(feature = "zenoh-transport")] +pub mod zenoh; diff --git a/crates/cli/src/zenoh.rs b/crates/cli/src/zenoh.rs new file mode 100644 index 000000000..9c8b9efe7 --- /dev/null +++ b/crates/cli/src/zenoh.rs @@ -0,0 +1,24 @@ +use serde_json::json; +use zenoh::{Config, Session}; + +/// Open a regular Zenoh session with configs supplied by an environment variable. +pub async fn connect() -> anyhow::Result { + let cfg = if let Ok(cfg) = Config::from_env() { cfg } else { + let mut config = Config::default(); + // Set mode + config + .insert_json5("mode", &json!("client").to_string()) + .unwrap(); + config + .insert_json5( + "connect/endpoints", + &json!(["tcp/0.0.0.0:7447"]).to_string(), + ) + .unwrap(); + + config + }; + + let session = zenoh::open(cfg).await.unwrap(); + Ok(session) +} diff --git a/crates/test/Cargo.toml b/crates/test/Cargo.toml index 899055684..c73654106 100644 --- a/crates/test/Cargo.toml +++ b/crates/test/Cargo.toml @@ -11,7 +11,7 @@ license.workspace = true repository.workspace = true [features] -default = ["nats", "quic", "web-transport"] +default = ["nats", "quic", "web-transport", "zenoh-transport"] nats = ["dep:async-nats", "async-nats/ring", "wrpc-cli/nats"] quic = [ "dep:quinn", @@ -22,6 +22,7 @@ quic = [ "quinn/rustls", ] web-transport = ["dep:wtransport", "wtransport/self-signed"] +zenoh-transport = ["dep:zenoh", "dep:wrpc-transport-zenoh", "wrpc-cli/zenoh-transport"] [dependencies] anyhow = { workspace = true } @@ -33,3 +34,6 @@ tokio = { workspace = true, features = ["net", "process", "rt-multi-thread"] } tracing = { workspace = true } wrpc-cli = { workspace = true } wtransport = { workspace = true, features = ["ring"], optional = true } +wrpc-transport-zenoh = { workspace = true, optional = true } +zenoh = { workspace = true, optional = true } +serde_json = { workspace = true } \ No newline at end of file diff --git a/crates/test/src/lib.rs b/crates/test/src/lib.rs index b71d6f407..a005ca701 100644 --- a/crates/test/src/lib.rs +++ b/crates/test/src/lib.rs @@ -7,10 +7,13 @@ use rcgen::{generate_simple_self_signed, CertifiedKey}; use rustls::pki_types::{CertificateDer, PrivatePkcs8KeyDer}; use rustls::version::TLS13; use rustls::{ClientConfig, RootCertStore, ServerConfig}; -use tokio::net::TcpListener; +use std::sync::Arc; +use std::time::Duration; +use tokio::net::{TcpListener, TcpStream}; use tokio::process::Command; use tokio::sync::oneshot; use tokio::task::JoinHandle; +use tokio::time::sleep; use tokio::{select, spawn}; pub async fn free_port() -> anyhow::Result { @@ -100,11 +103,9 @@ pub async fn start_nats() -> anyhow::Result<( "failed to execute nats-server" }; anyhow::bail!( - "{}. Please install nats-server >= 2.10.20. \ + "{error_msg}. Please install nats-server >= 2.10.20. \ See https://docs.nats.io/running-a-nats-service/introduction/installation for installation instructions. \ - Original error: {}", - error_msg, - e + Original error: {e}" ); } @@ -120,6 +121,101 @@ pub async fn start_nats() -> anyhow::Result<( Ok((port, client, server, stop_tx)) } +#[cfg(feature = "zenoh-transport")] +pub async fn start_zenoh() -> anyhow::Result<( + u16, + Arc, + JoinHandle>, + oneshot::Sender<()>, +)> { + // Check if nats-server is available + + use zenoh::Config; + let nats_server_check = Command::new("zenohd").arg("--version").output().await; + if let Err(e) = nats_server_check { + let error_msg = if e.kind() == std::io::ErrorKind::NotFound { + "zenohd is not installed or not in PATH" + } else if e.kind() == std::io::ErrorKind::PermissionDenied { + "zenohd is not executable or permission denied" + } else { + "failed to execute zenohd" + }; + anyhow::bail!( + "{error_msg}. Please install zenohd \ + See https://zenoh.io/docs/getting-started/installation/ for installation instructions. \ + Original error: {e}" + ); + } + + let port = free_port().await?; // not used in setup -- just return + + let listen = format!("tcp/0.0.0.0:{port}"); + + let (server, stop_tx) = spawn_server(Command::new("zenohd").args(["-l", &listen])) + .await + .context("failed to start zenohd server")?; + + let mut ready = false; + + // Check that zenohd is ready + for _ in 0..50 { + if TcpStream::connect(("127.0.0.1", port)).await.is_ok() { + ready = true; + break; + } + sleep(Duration::from_millis(100)).await; + } + + if !ready { + anyhow::bail!("zenohd did not open port {port}"); + } + + let cfg = if let Ok(cfg) = Config::from_env() { cfg } else { + use serde_json::json; + + let mut config = Config::default(); + + // Set mode + config + .insert_json5("mode", &json!("client").to_string()) + .unwrap(); + config + .insert_json5( + "connect/endpoints", + &json!([format!("tcp/127.0.0.1:{port}")]).to_string(), + ) + .unwrap(); + + config + }; + + let session = zenoh::open(cfg).await.unwrap(); + + let arc_session = Arc::new(session); + + Ok((port, arc_session, server, stop_tx)) +} + +#[cfg(feature = "zenoh-transport")] +pub async fn with_zenoh( + f: impl FnOnce(u16, Arc) -> Fut, +) -> anyhow::Result +where + Fut: core::future::Future>, +{ + let (port, zenoh_session, zenoh_server, stop_tx) = start_zenoh() + .await + .context("failed to start Zenoh server")?; + let res = f(port, zenoh_session).await.context("closure failed")?; + + stop_tx.send(()).expect("failed to stop Zenoh server"); + zenoh_server + .await + .context("failed to await Zenoh server stop")? + .context("Zenoh server failed to stop")?; + Ok(res) +} + #[cfg(feature = "nats")] pub async fn with_nats(f: impl FnOnce(u16, async_nats::Client) -> Fut) -> anyhow::Result where diff --git a/crates/transport-zenoh/Cargo.toml b/crates/transport-zenoh/Cargo.toml new file mode 100644 index 000000000..500aa3c05 --- /dev/null +++ b/crates/transport-zenoh/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "wrpc-transport-zenoh" +version = "0.29.0" +description = "wRPC Zenoh transport" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[features] +hotpath = ["hotpath/hotpath"] +hotpath-alloc = ["hotpath/hotpath-alloc"] + +[dependencies] +anyhow = { workspace = true, features = ["std"] } +bytes = { workspace = true, features = ["serde"] } +futures = { workspace = true, features = ["async-await"] } +nuid = { workspace = true } +tokio = { workspace = true, features = ["io-util", "rt-multi-thread"] } +tokio-stream = { workspace = true, features = ["sync"] } +tokio-util = { workspace = true, features = ["codec", "io"] } +tracing = { workspace = true, features = ["attributes"] } +wasm-tokio = { workspace = true } +wrpc-transport = { workspace = true } +zenoh = { workspace = true } +serde = { workspace = true } +uuid = { workspace = true } +flume = { workspace = true } +postcard = {workspace = true, features = ["use-std"]} +hotpath = "0.13.0" diff --git a/crates/transport-zenoh/src/lib.rs b/crates/transport-zenoh/src/lib.rs new file mode 100644 index 000000000..86c7763a0 --- /dev/null +++ b/crates/transport-zenoh/src/lib.rs @@ -0,0 +1,1239 @@ +//! wRPC Zenoh transport + +#![allow(clippy::type_complexity)] + +use anyhow::{anyhow, ensure, Context as _}; +use wrpc_transport::Index; +use zenoh::bytes::ZBytes; +use zenoh::sample::Sample; + +use core::ops::{Deref, DerefMut}; +use std::iter::zip; +use std::{io, mem}; +use zenoh::{Session, Wait}; + +use core::future::Future; +use core::pin::{pin, Pin}; +use core::str; +use core::task::{Context, Poll}; + +use std::collections::HashMap; +use std::sync::Arc; + +use bytes::{Buf as _, Bytes}; +use futures::{Stream, StreamExt}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::select; +use tokio::sync::{mpsc, oneshot}; +use tokio::task::JoinSet; +use tokio_stream::wrappers::ReceiverStream; + +use tracing::{debug, error, instrument, trace, warn}; + +pub const PROTOCOL: &str = "wrpc.0.0.1"; + +fn send_sync(session: &Session, key: &str, payload: &[u8]) -> io::Result<()> { + // Using the synchronous (blocking) API due to some synchronization issue that nats handles + session.put(key, payload).wait().map_err(io::Error::other)?; + Ok(()) +} + +fn spawn_async(fut: impl Future + Send + 'static) { + match tokio::runtime::Handle::try_current() { + Ok(rt) => { + rt.spawn(fut); + } + Err(_) => match tokio::runtime::Runtime::new() { + Ok(rt) => { + rt.spawn(fut); + } + Err(err) => error!(?err, "failed to create a new Tokio runtime"), + }, + } +} + +fn zbytes_as_bytes(zbytes: &ZBytes) -> Bytes { + // Get &ZBytes from the sample + let bytes = match zbytes.to_bytes() { + std::borrow::Cow::Borrowed(slice) => Bytes::copy_from_slice(slice), + std::borrow::Cow::Owned(vec) => Bytes::from(vec), + }; + + bytes +} + +fn child_inbox(base: &str) -> String { + let base = if base.ends_with('/') { + base + } else { + &format!("{base}/") + }; + format!("{base}{}", nuid::next()) +} + +#[must_use] +#[inline] +pub fn param_subject(prefix: &str) -> String { + format!("{prefix}/params") +} + +#[must_use] +#[inline] +pub fn result_subject(prefix: &str) -> String { + format!("{prefix}/results") +} + +#[must_use] +#[inline] +pub fn index_path(prefix: &str, path: &[usize]) -> String { + let mut s = String::with_capacity(prefix.len() + path.len() * 2); + if !prefix.is_empty() { + s.push_str(prefix); + } + for p in path { + if !s.is_empty() { + s.push('/'); + } + s.push_str(&p.to_string()); + } + s +} + +#[must_use] +#[inline] +pub fn subscribe_path(prefix: &str, path: &[Option]) -> String { + let mut s = String::with_capacity(prefix.len() + path.len() * 2); + if !prefix.is_empty() { + s.push_str(prefix); + } + for p in path { + if !s.is_empty() { + s.push('/'); + } + if let Some(p) = p { + s.push_str(&p.to_string()); + } else { + s.push('*'); + } + } + s +} + +#[must_use] +#[inline] +pub fn invocation_subject(prefix: &str, instance: &str, func: &str) -> String { + let mut s = + String::with_capacity(prefix.len() + PROTOCOL.len() + instance.len() + func.len() + 3); + if !prefix.is_empty() { + s.push_str(prefix); + s.push('/'); + } + s.push_str(PROTOCOL); + s.push('/'); + if !instance.is_empty() { + s.push_str(instance); + s.push('/'); + } + s.push_str(func); + s +} + +fn corrupted_memory_error() -> std::io::Error { + std::io::Error::other("corrupted memory state") +} + +/// Transport subscriber +pub struct Subscriber { + rx: ReceiverStream, + subject: String, + commands: mpsc::Sender, + tasks: Arc>, +} + +impl Drop for Subscriber { + fn drop(&mut self) { + let commands = self.commands.clone(); + let subject = self.subject.clone(); + let tasks = Arc::clone(&self.tasks); + spawn_async(async move { + trace!(?subject, "shutting down subscriber"); + if let Err(err) = commands.send(Command::Unsubscribe(subject)).await { + warn!(?err, "failed to shutdown subscriber"); + } + drop(tasks); + }); + } +} + +impl Deref for Subscriber { + type Target = ReceiverStream; + + fn deref(&self) -> &Self::Target { + &self.rx + } +} + +impl DerefMut for Subscriber { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.rx + } +} + +enum Command { + Subscribe(String, mpsc::Sender), + Unsubscribe(String), + Batch(Box<[Command]>, oneshot::Sender<()>), +} + +#[derive(Clone, Debug)] +pub struct Client { + session: Arc, + prefix: Arc, + inbox: Arc, + //queue_group: Option>, + commands: mpsc::Sender, + tasks: Arc>, +} + +impl Client { + pub async fn new( + session: impl Into>, + prefix: impl Into>, + //queue_group: Option>, + ) -> anyhow::Result { + let session: Arc = session.into(); + let root = format!("_inbox/{}/", nuid::next()); + let wildcard = format!("{root}**"); + let sub = session + .declare_subscriber(wildcard) + .with(flume::bounded(8192)) + .await + .unwrap(); + + let mut tasks = JoinSet::new(); + let (cmd_tx, mut cmd_rx) = mpsc::channel(8192); + tasks.spawn({ + async move { + fn handle_command(subs: &mut HashMap>, cmd: Command) { + match cmd { + Command::Subscribe(s, tx) => { + subs.insert(s, tx); + } + Command::Unsubscribe(s) => { + subs.remove(&s); + } + Command::Batch(cmds, ack) => { + for cmd in cmds { + handle_command(subs, cmd); + } + let _ = ack.send(()); + } + } + } + + async fn handle_message( + subs: &mut HashMap>, + sample: Sample, + ) { + let key = sample.key_expr().clone().as_str().to_string(); + + let Some(sub) = subs.get_mut(&key) else { + debug!(?key, "drop message with no subscriber"); + return; + }; + let Ok(sub) = sub.reserve().await else { + debug!(?key, "drop message with closed subscriber"); + subs.remove(&key); + return; + }; + + sub.send(sample); + } + + let mut subs = HashMap::new(); + loop { + select! { + Ok(msg) = sub.recv_async() => handle_message(&mut subs, msg).await, + Some(cmd) = cmd_rx.recv() => handle_command(&mut subs, cmd), + else => return, + } + } + } + }); + Ok(Self { + session, + prefix: prefix.into(), + inbox: root.into(), + commands: cmd_tx, + tasks: Arc::new(tasks), + }) + } +} + +pub struct ByteSubscription(Subscriber); + +impl Stream for ByteSubscription { + type Item = std::io::Result; + + #[instrument(level = "trace", skip_all)] + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.0.poll_next_unpin(cx) { + Poll::Ready(Some(sample)) => Poll::Ready(Some(Ok(zbytes_as_bytes(sample.payload())))), + Poll::Ready(None) => Poll::Ready(None), + Poll::Pending => Poll::Pending, + } + } +} + +#[derive(Default)] +enum IndexTrie { + #[default] + Empty, + Leaf(Subscriber), + IndexNode { + subscriber: Option, + nested: Vec>, + }, + WildcardNode { + subscriber: Option, + nested: Option>, + }, +} + +impl<'a> From<(&'a [Option], Subscriber)> for IndexTrie { + fn from((path, sub): (&'a [Option], Subscriber)) -> Self { + match path { + [] => Self::Leaf(sub), + [None, path @ ..] => Self::WildcardNode { + subscriber: None, + nested: Some(Box::new(Self::from((path, sub)))), + }, + [Some(i), path @ ..] => Self::IndexNode { + subscriber: None, + nested: { + let n = i.saturating_add(1); + let mut nested = Vec::with_capacity(n); + nested.resize_with(n, Option::default); + nested[*i] = Some(Self::from((path, sub))); + nested + }, + }, + } + } +} + +impl]>> FromIterator<(P, Subscriber)> for IndexTrie { + fn from_iter>(iter: T) -> Self { + let mut root = Self::Empty; + for (path, sub) in iter { + if !root.insert(path.as_ref(), sub) { + return Self::Empty; + } + } + root + } +} + +impl IndexTrie { + #[inline] + fn is_empty(&self) -> bool { + matches!(self, IndexTrie::Empty) + } + + #[instrument(level = "trace", skip_all)] + fn take(&mut self, path: &[usize]) -> Option { + let Some((i, path)) = path.split_first() else { + return match mem::take(self) { + // TODO: Demux the subscription + //IndexTrie::WildcardNode { subscriber, nested } => { + // if let Some(nested) = nested { + // *self = IndexTrie::WildcardNode { + // subscriber: None, + // nested: Some(nested), + // } + // } + // subscriber + //} + IndexTrie::Empty | IndexTrie::WildcardNode { .. } => None, + IndexTrie::Leaf(subscriber) => Some(subscriber), + IndexTrie::IndexNode { subscriber, nested } => { + if !nested.is_empty() { + *self = IndexTrie::IndexNode { + subscriber: None, + nested, + } + } + subscriber + } + }; + }; + match self { + // TODO: Demux the subscription + //Self::WildcardNode { ref mut nested, .. } => { + // nested.as_mut().and_then(|nested| nested.take(path)) + //} + Self::Empty | Self::Leaf(..) | Self::WildcardNode { .. } => None, + Self::IndexNode { ref mut nested, .. } => nested + .get_mut(*i) + .and_then(|nested| nested.as_mut().and_then(|nested| nested.take(path))), + } + } + + /// Inserts `sub` under a `path` - returns `false` if it failed and `true` if it succeeded. + /// Tree state after `false` is returned in undefined + #[instrument(level = "trace", skip_all)] + fn insert(&mut self, path: &[Option], sub: Subscriber) -> bool { + match self { + Self::Empty => { + *self = Self::from((path, sub)); + true + } + Self::Leaf(..) => { + let Some((i, path)) = path.split_first() else { + return false; + }; + let Self::Leaf(subscriber) = mem::take(self) else { + return false; + }; + if let Some(i) = i { + let n = i.saturating_add(1); + let mut nested = Vec::with_capacity(n); + nested.resize_with(n, Option::default); + nested[*i] = Some(Self::from((path, sub))); + *self = Self::IndexNode { + subscriber: Some(subscriber), + nested, + }; + } else { + *self = Self::WildcardNode { + subscriber: Some(subscriber), + nested: Some(Box::new(Self::from((path, sub)))), + }; + } + true + } + Self::WildcardNode { + ref mut subscriber, + ref mut nested, + } => match (&subscriber, path) { + (None, []) => { + *subscriber = Some(sub); + true + } + (_, [None, path @ ..]) => { + if let Some(nested) = nested { + nested.insert(path, sub) + } else { + *nested = Some(Box::new(Self::from((path, sub)))); + true + } + } + _ => false, + }, + Self::IndexNode { + ref mut subscriber, + ref mut nested, + } => match (&subscriber, path) { + (None, []) => { + *subscriber = Some(sub); + true + } + (_, [Some(i), path @ ..]) => { + let cap = i.saturating_add(1); + if nested.len() < cap { + nested.resize_with(cap, Option::default); + } + let nested = &mut nested[*i]; + if let Some(nested) = nested { + nested.insert(path, sub) + } else { + *nested = Some(Self::from((path, sub))); + true + } + } + _ => false, + }, + } + } +} + +pub struct Reader { + buffer: Bytes, + incoming: Option, + nested: Arc>, + path: Box<[usize]>, +} + +impl wrpc_transport::Index for Reader { + #[instrument(level = "trace", skip(self))] + fn index(&self, path: &[usize]) -> anyhow::Result { + ensure!(!path.is_empty()); + trace!("locking index tree"); + let mut nested = self + .nested + .lock() + .map_err(|err| anyhow!(err.to_string()).context("failed to lock map"))?; + trace!("taking index subscription"); + let mut p = self.path.to_vec(); + p.extend_from_slice(path); + let incoming = nested.take(&p); + Ok(Self { + buffer: Bytes::default(), + incoming, + nested: Arc::clone(&self.nested), + path: p.into_boxed_slice(), + }) + } +} + +impl AsyncRead for Reader { + #[instrument(level = "trace", skip_all, ret)] + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let cap = buf.remaining(); + if cap == 0 { + trace!("attempt to read empty buffer"); + return Poll::Ready(Ok(())); + } + + if !self.buffer.is_empty() { + if self.buffer.len() > cap { + trace!(cap, len = self.buffer.len(), "reading part of buffer"); + buf.put_slice(&self.buffer.split_to(cap)); + } else { + trace!(cap, len = self.buffer.len(), "reading full buffer"); + buf.put_slice(&mem::take(&mut self.buffer)); + } + return Poll::Ready(Ok(())); + } + let Some(incoming) = self.incoming.as_mut() else { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + format!("subscription not found for path {:?}", self.path), + ))); + }; + trace!("polling for next message"); + match incoming.poll_next_unpin(cx) { + Poll::Ready(Some(sample)) => { + let mut payload = zbytes_as_bytes(sample.payload()); + trace!(?payload, "received message"); + if payload.is_empty() { + trace!("received stream shutdown message"); + return Poll::Ready(Ok(())); + } + if payload.len() > cap { + trace!(len = payload.len(), cap, "partially reading the message"); + buf.put_slice(&payload.split_to(cap)); + self.buffer = payload; + } else { + trace!(len = payload.len(), cap, "filling the buffer with payload"); + buf.put_slice(&payload); + } + Poll::Ready(Ok(())) + } + Poll::Ready(None) => { + trace!("subscription finished"); + Poll::Ready(Ok(())) + } + Poll::Pending => Poll::Pending, + } + } +} + +#[derive(Clone, Debug)] +pub struct SubjectWriter { + session: Arc, + tx: String, + shutdown: bool, + tasks: Arc>, +} + +impl SubjectWriter { + fn new(session: Arc, tx: String, tasks: Arc>) -> Self { + Self { + session, + tx, + shutdown: false, + tasks, + } + } +} + +impl wrpc_transport::Index for SubjectWriter { + #[instrument(level = "trace", skip(self))] + fn index(&self, path: &[usize]) -> anyhow::Result { + ensure!(!path.is_empty()); + let tx = index_path(&self.tx, path); + Ok(Self { + session: self.session.clone(), + tx, + shutdown: false, + tasks: Arc::clone(&self.tasks), + }) + } +} + +impl AsyncWrite for SubjectWriter { + #[instrument(level = "trace", skip_all, ret, fields(subject = self.tx, buf = format!("{buf:02x?}")))] + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + trace!("starting send"); + + // let Ok(payload) = prepare_attachment_bytes(self.tx.clone(), None, buf, None, None) else { + // return Poll::Ready(Err(io::Error::new( + // io::ErrorKind::InvalidData, + // "Couldn't prepare payload as bytes", + // ))); + // }; + + let subject = &self.tx; + let session = &self.session; + + trace!( + "writing message synchronously: key={} size={}", + subject, + buf.len() + ); + + match send_sync(session, subject, buf) { + Ok(()) => { + trace!("put completed"); + Poll::Ready(Ok(buf.len())) + } + Err(e) => { + warn!(?e, "failed to publish sync put"); + Poll::Ready(Err(e)) + } + } + } + + #[instrument(level = "trace", skip_all, ret, fields(subject = self.tx))] + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + trace!("flushing"); + Poll::Ready(Ok(())) + } + + #[instrument(level = "trace", skip_all, ret, fields(subject = self.tx))] + fn poll_shutdown(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + trace!("writing stream shutdown message"); + if !self.shutdown { + let session = self.session.clone(); + let key = self.tx.clone(); + self.shutdown = true; + + spawn_async(async move { + trace!("writing stream shutdown message (explicit)"); + if let Err(err) = session.put(key, &[]).await { + warn!(?err, "failed to publish stream shutdown message"); + } + }); + } + Poll::Ready(Ok(())) + } +} + +impl Drop for SubjectWriter { + fn drop(&mut self) { + if !self.shutdown { + let session = self.session.clone(); + let key = self.tx.clone(); + let tasks = Arc::clone(&self.tasks); + spawn_async(async move { + trace!("writing stream shutdown message"); + // let Ok(payload) = prepare_attachment_bytes(key.clone(), None, &[], None, None) else { + // warn!("Couldn't prepare payload as bytes"); + // return (); + // }; + if let Err(err) = session.put(key, &[]).await { + warn!(?err, "failed to publish stream shutdown message"); + } + drop(tasks); + }); + } + } +} + +#[derive(Default)] +pub enum RootParamWriter { + #[default] + Corrupted, + Handshaking { + session: Arc, + sub: Subscriber, + indexed: std::sync::Mutex, oneshot::Sender)>>, + buffer: Bytes, + tasks: Arc>, + }, + Draining { + tx: SubjectWriter, + buffer: Bytes, + }, + Active(SubjectWriter), +} + +impl RootParamWriter { + fn new( + session: Arc, + sub: Subscriber, + buffer: Bytes, + tasks: Arc>, + ) -> Self { + Self::Handshaking { + session, + sub, + indexed: std::sync::Mutex::default(), + buffer, + tasks, + } + } +} + +impl RootParamWriter { + #[instrument(level = "trace", skip_all, ret)] + fn poll_active(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + Self::Corrupted => Poll::Ready(Err(corrupted_memory_error())), + Self::Handshaking { sub, .. } => { + trace!("polling for handshake response 1"); + match sub.poll_next_unpin(cx) { + // Poll::Ready(Some(sample) => { + // if let Some(kind) = map_status_to_error_kind(&code) { + // return Poll::Ready(Err(kind.into())); + // } + // if !code.is_empty() { + // let msg = match description { + // Some(desc) if !desc.is_empty() => { + // format!("received a response with code `{code}` ({desc})") + // } + // _ => format!("received a response with code `{code}`"), + // }; + // return Poll::Ready(Err(io::Error::other(msg))); + // } + // // Empty status string: fall through to the generic error below + // return Poll::Ready(Err(io::Error::new( + // io::ErrorKind::InvalidInput, + // "empty status string in handshake response", + // ))); + // } + Poll::Ready(Some(sample)) => { + let Self::Handshaking { + session, + indexed, + buffer, + tasks, + .. + } = mem::take(&mut *self) + else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + + let tx = match sample.attachment() { + Some(zbytes) => zbytes.try_to_string().unwrap().into_owned(), + None => { + return Poll::Ready(Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "peer did not specify a reply subject", + ))) + } + }; + + let tx = SubjectWriter::new(session, param_subject(&tx), tasks); + let indexed = indexed + .into_inner() + .map_err(|err| std::io::Error::other(err.to_string()))?; + for (path, tx_tx) in indexed { + let tx = tx.index(&path).map_err(std::io::Error::other)?; + tx_tx.send(tx).map_err(|_| { + std::io::Error::from(std::io::ErrorKind::BrokenPipe) + })?; + } + trace!("handshake succeeded"); + if buffer.is_empty() { + *self = Self::Active(tx); + Poll::Ready(Ok(())) + } else { + *self = Self::Draining { tx, buffer }; + self.poll_active(cx) + } + } + Poll::Ready(None) => { + *self = Self::Corrupted; + Poll::Ready(Err(std::io::Error::from(std::io::ErrorKind::BrokenPipe))) + } + Poll::Pending => Poll::Pending, + } + } + Self::Draining { tx, buffer } => { + let mut tx = pin!(tx); + while !buffer.is_empty() { + trace!(?tx.tx, "draining parameter buffer"); + match tx.as_mut().poll_write(cx, buffer) { + Poll::Ready(Ok(n)) => { + buffer.advance(n); + } + Poll::Ready(Err(e)) => return Poll::Ready(Err(e)), + Poll::Pending => return Poll::Pending, + } + } + let Self::Draining { tx, .. } = mem::take(&mut *self) else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + trace!("parameter buffer draining succeeded"); + *self = Self::Active(tx); + Poll::Ready(Ok(())) + } + Self::Active(..) => Poll::Ready(Ok(())), + } + } +} + +impl wrpc_transport::Index for RootParamWriter { + #[instrument(level = "trace", skip(self))] + fn index(&self, path: &[usize]) -> anyhow::Result { + ensure!(!path.is_empty()); + match self { + Self::Corrupted => Err(anyhow!(corrupted_memory_error())), + Self::Handshaking { indexed, .. } => { + let (tx_tx, tx_rx) = oneshot::channel(); + let mut indexed = indexed + .lock() + .map_err(|err| std::io::Error::other(err.to_string()))?; + indexed.push((path.to_vec(), tx_tx)); + Ok(IndexedParamWriter::Handshaking { + tx_rx, + indexed: std::sync::Mutex::default(), + }) + } + Self::Draining { tx, .. } | Self::Active(tx) => { + tx.index(path).map(IndexedParamWriter::Active) + } + } + } +} + +impl AsyncWrite for RootParamWriter { + #[instrument(level = "trace", skip_all, ret, fields(buf = format!("{buf:02x?}")))] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match self.as_mut().poll_active(cx)? { + Poll::Ready(()) => { + let Self::Active(tx) = &mut *self else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + trace!("writing buffer"); + pin!(tx).poll_write(cx, buf) + } + Poll::Pending => Poll::Pending, + } + } + + #[instrument(level = "trace", skip_all, ret)] + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.as_mut().poll_active(cx)? { + Poll::Ready(()) => { + let Self::Active(tx) = &mut *self else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + trace!("flushing"); + pin!(tx).poll_flush(cx) + } + Poll::Pending => Poll::Pending, + } + } + + #[instrument(level = "trace", skip_all, ret)] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.as_mut().poll_active(cx)? { + Poll::Ready(()) => { + let Self::Active(tx) = &mut *self else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + trace!("shutting down"); + pin!(tx).poll_shutdown(cx) + } + Poll::Pending => Poll::Pending, + } + } +} + +#[derive(Debug, Default)] +pub enum IndexedParamWriter { + #[default] + Corrupted, + Handshaking { + tx_rx: oneshot::Receiver, + indexed: std::sync::Mutex, oneshot::Sender)>>, + }, + Active(SubjectWriter), +} + +impl IndexedParamWriter { + #[instrument(level = "trace", skip_all, ret)] + fn poll_active(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + Self::Corrupted => Poll::Ready(Err(corrupted_memory_error())), + Self::Handshaking { tx_rx, .. } => { + trace!("polling for handshake"); + match pin!(tx_rx).poll(cx) { + Poll::Ready(Ok(tx)) => { + let Self::Handshaking { indexed, .. } = mem::take(&mut *self) else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + let indexed = indexed + .into_inner() + .map_err(|err| std::io::Error::other(err.to_string()))?; + for (path, tx_tx) in indexed { + let tx = tx.index(&path).map_err(std::io::Error::other)?; + tx_tx.send(tx).map_err(|_| { + std::io::Error::from(std::io::ErrorKind::BrokenPipe) + })?; + } + *self = Self::Active(tx); + Poll::Ready(Ok(())) + } + Poll::Ready(Err(..)) => Poll::Ready(Err(std::io::ErrorKind::BrokenPipe.into())), + Poll::Pending => Poll::Pending, + } + } + Self::Active(..) => Poll::Ready(Ok(())), + } + } +} + +impl wrpc_transport::Index for IndexedParamWriter { + #[instrument(level = "trace", skip_all)] + fn index(&self, path: &[usize]) -> anyhow::Result { + ensure!(!path.is_empty()); + match self { + Self::Corrupted => Err(anyhow!(corrupted_memory_error())), + Self::Handshaking { indexed, .. } => { + let (tx_tx, tx_rx) = oneshot::channel(); + let mut indexed = indexed + .lock() + .map_err(|err| std::io::Error::other(err.to_string()))?; + indexed.push((path.to_vec(), tx_tx)); + Ok(Self::Handshaking { + tx_rx, + indexed: std::sync::Mutex::default(), + }) + } + Self::Active(tx) => tx.index(path).map(Self::Active), + } + } +} + +impl AsyncWrite for IndexedParamWriter { + #[instrument(level = "trace", skip_all, ret, fields(buf = format!("{buf:02x?}")))] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match self.as_mut().poll_active(cx)? { + Poll::Ready(()) => { + let Self::Active(tx) = &mut *self else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + trace!("writing buffer"); + pin!(tx).poll_write(cx, buf) + } + Poll::Pending => Poll::Pending, + } + } + + #[instrument(level = "trace", skip_all, ret)] + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.as_mut().poll_active(cx)? { + Poll::Ready(()) => { + let Self::Active(tx) = &mut *self else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + trace!("flushing"); + pin!(tx).poll_flush(cx) + } + Poll::Pending => Poll::Pending, + } + } + + #[instrument(level = "trace", skip_all, ret)] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match self.as_mut().poll_active(cx)? { + Poll::Ready(()) => { + let Self::Active(tx) = &mut *self else { + return Poll::Ready(Err(corrupted_memory_error())); + }; + trace!("shutting down"); + pin!(tx).poll_shutdown(cx) + } + Poll::Pending => Poll::Pending, + } + } +} + +pub enum ParamWriter { + Root(RootParamWriter), + Nested(IndexedParamWriter), +} + +impl wrpc_transport::Index for ParamWriter { + fn index(&self, path: &[usize]) -> anyhow::Result { + ensure!(!path.is_empty()); + match self { + ParamWriter::Root(w) => w.index(path), + ParamWriter::Nested(w) => w.index(path), + } + .map(Self::Nested) + } +} + +impl AsyncWrite for ParamWriter { + #[instrument(level = "trace", skip_all, ret, fields(buf = format!("{buf:02x?}")))] + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + match &mut *self { + ParamWriter::Root(w) => pin!(w).poll_write(cx, buf), + ParamWriter::Nested(w) => pin!(w).poll_write(cx, buf), + } + } + + #[instrument(level = "trace", skip_all, ret)] + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + ParamWriter::Root(w) => pin!(w).poll_flush(cx), + ParamWriter::Nested(w) => pin!(w).poll_flush(cx), + } + } + + #[instrument(level = "trace", skip_all, ret)] + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match &mut *self { + ParamWriter::Root(w) => pin!(w).poll_shutdown(cx), + ParamWriter::Nested(w) => pin!(w).poll_shutdown(cx), + } + } +} + +impl wrpc_transport::Invoke for Client { + type Context = (); + type Outgoing = ParamWriter; + type Incoming = Reader; + + #[instrument(level = "trace", skip(self, paths, params), fields(params = format!("{params:02x?}")))] + async fn invoke]> + Send + Sync>( + &self, + cx: Self::Context, + instance: &str, + func: &str, + params: Bytes, + paths: impl AsRef<[P]> + Send, + ) -> anyhow::Result<(Self::Outgoing, Self::Incoming)> { + let paths = paths.as_ref(); + let mut cmds = Vec::with_capacity(paths.len().saturating_add(2)); + + let rx = child_inbox(&self.inbox); + let (handshake_tx, handshake_rx) = mpsc::channel(1); + cmds.push(Command::Subscribe(rx.clone(), handshake_tx)); + + let result = result_subject(&rx); + let (result_tx, result_rx) = mpsc::channel(16); + cmds.push(Command::Subscribe(result.clone(), result_tx)); + + let nested = paths.iter().map(|path| { + let (tx, rx) = mpsc::channel(16); + let subject = subscribe_path(&result, path.as_ref()); + cmds.push(Command::Subscribe(subject.clone(), tx)); + Subscriber { + rx: ReceiverStream::new(rx), + commands: self.commands.clone(), + subject, + tasks: Arc::clone(&self.tasks), + } + }); + let nested: IndexTrie = zip(paths.iter(), nested).collect(); + ensure!( + paths.is_empty() == nested.is_empty(), + "failed to construct subscription tree" + ); + + let (ack_tx, ack_rx) = oneshot::channel(); + + self.commands + .send(Command::Batch(cmds.into_boxed_slice(), ack_tx)) + .await + .context("failed to subscribe")?; + + ack_rx.await?; + + let param_tx = invocation_subject(&self.prefix, instance, func); + trace!("publishing handshake"); + self.session + .put(param_tx, params) + .attachment(rx.clone()) + .await + .unwrap(); + Ok(( + ParamWriter::Root(RootParamWriter::new( + (*self.session).clone().into(), + Subscriber { + rx: ReceiverStream::new(handshake_rx), + commands: self.commands.clone(), + subject: rx, + tasks: Arc::clone(&self.tasks), + }, + Bytes::new(), + Arc::clone(&self.tasks), + )), + Reader { + buffer: Bytes::default(), + incoming: Some(Subscriber { + rx: ReceiverStream::new(result_rx), + commands: self.commands.clone(), + subject: result, + tasks: Arc::clone(&self.tasks), + }), + nested: Arc::new(std::sync::Mutex::new(nested)), + path: Box::default(), + }, + )) + } +} + +async fn handle_message( + session: &zenoh::Session, + rx: String, + commands: mpsc::Sender, + sample: Sample, + paths: &[Box<[Option]>], + tasks: Arc>, +) -> anyhow::Result<((), SubjectWriter, Reader)> { + let tx = match sample.attachment() { + Some(zbytes) => zbytes.try_to_string().unwrap().into_owned(), + None => todo!(), + }; + + let mut cmds = Vec::with_capacity(paths.len().saturating_add(1)); + + let param = param_subject(&rx); + let (param_tx, param_rx) = mpsc::channel(16); + cmds.push(Command::Subscribe(param.clone(), param_tx)); + + let nested = paths.iter().map(|path| { + let (tx, rx) = mpsc::channel(16); + let subject = subscribe_path(¶m, path.as_ref()); + cmds.push(Command::Subscribe(subject.clone(), tx)); + Subscriber { + rx: ReceiverStream::new(rx), + commands: commands.clone(), + subject, + tasks: Arc::clone(&tasks), + } + }); + let nested: IndexTrie = zip(paths.iter(), nested).collect(); + ensure!( + paths.is_empty() == nested.is_empty(), + "failed to construct subscription tree" + ); + + let (ack_tx, ack_rx) = oneshot::channel(); + + commands + .send(Command::Batch(cmds.into_boxed_slice(), ack_tx)) + .await + .context("failed to subscribe")?; + + ack_rx.await?; + + trace!("publishing handshake response"); + + session + .put(tx.clone(), Bytes::default()) + .attachment(rx.clone()) + .await + .unwrap(); + Ok(( + (), + SubjectWriter::new( + session.clone().into(), + result_subject(&tx), + Arc::clone(&tasks), + ), + Reader { + buffer: zbytes_as_bytes(sample.payload()), + incoming: Some(Subscriber { + rx: ReceiverStream::new(param_rx), + commands, + subject: param, + tasks, + }), + nested: Arc::new(std::sync::Mutex::new(nested)), + path: Box::default(), + }, + )) +} + +impl wrpc_transport::Serve for Client { + type Context = (); + type Outgoing = SubjectWriter; + type Incoming = Reader; + + #[instrument(level = "trace", skip(self, paths))] + async fn serve( + &self, + instance: &str, + func: &str, + paths: impl Into]>]>> + Send, + ) -> anyhow::Result< + impl Stream> + 'static, + > { + let subject = invocation_subject(&self.prefix, instance, func); + + let sub = self.session.declare_subscriber(subject).await.unwrap(); + let session = Arc::clone(&self.session); + let paths = paths.into(); + let commands = self.commands.clone(); + let inbox_root = self.inbox.clone(); + let tasks = Arc::clone(&self.tasks); + + let stream = futures::stream::unfold( + (sub, session, paths, commands, tasks, inbox_root), + |(sub, session, paths, commands, tasks, inbox_root)| async move { + match sub.recv_async().await { + Ok(sample) => { + // let Ok(message) = parse_bytes_message(sample) else { + // return None; + // }; + + let rx = child_inbox(inbox_root.as_ref()); + let item = handle_message( + &session, + rx, + commands.clone(), + sample, + &paths, + Arc::clone(&tasks), + ) + .await; + + // put `inboxroot` back into state for the next loop + Some((item, (sub, session, paths, commands, tasks, inbox_root))) + } + Err(_) => None, + } + }, + ); + + Ok(stream) + } +} diff --git a/crates/wasmtime-cli/Cargo.toml b/crates/wasmtime-cli/Cargo.toml index eee5e0b5a..3e232547a 100644 --- a/crates/wasmtime-cli/Cargo.toml +++ b/crates/wasmtime-cli/Cargo.toml @@ -69,4 +69,6 @@ wit-component = { workspace = true } wrpc-cli = { workspace = true, features = ["nats"] } wrpc-transport-nats = { workspace = true } wrpc-transport = { workspace = true, features = ["net"] } +wrpc-transport-zenoh = { workspace = true } wrpc-runtime-wasmtime = { workspace = true } +zenoh = { workspace = true } \ No newline at end of file diff --git a/crates/wasmtime-cli/src/lib.rs b/crates/wasmtime-cli/src/lib.rs index 4f310adc7..a89eef419 100644 --- a/crates/wasmtime-cli/src/lib.rs +++ b/crates/wasmtime-cli/src/lib.rs @@ -32,6 +32,7 @@ use wrpc_transport::{Invoke, Serve}; mod nats; mod tcp; +mod zenoh; const DEFAULT_TIMEOUT: &str = "10s"; @@ -42,6 +43,8 @@ enum Command { Nats(nats::Command), #[command(subcommand)] Tcp(tcp::Command), + #[command(subcommand)] + Zenoh(zenoh::Command), } pub enum Workload { @@ -495,12 +498,15 @@ where name, ) .await?; + + let name_copy = name.to_owned(); + handlers.spawn(async move { let mut invocations = pin!(invocations); while let Some(invocation) = invocations.next().await { match invocation { Ok((_, fut)) => { - info!("serving instance function invocation"); + info!(?name_copy, "serving instance function invocation"); if let Err(err) = fut.await { warn!( ?err, @@ -508,7 +514,7 @@ where ); } else { info!( - "successfully served instance function invocation" + ?name_copy, "successfully served instance function invocation" ); } } @@ -651,6 +657,7 @@ where name, ) .await?; + handlers.spawn(async move { let mut invocations = pin!(invocations); while let Some(invocation) = invocations.next().await { @@ -664,7 +671,7 @@ where ); } else { info!( - "successfully served instance function invocation" + "successfully served instance function invocation: " ); } } @@ -766,5 +773,6 @@ pub async fn run() -> anyhow::Result<()> { match Command::parse() { Command::Nats(args) => nats::run(args).await, Command::Tcp(args) => tcp::run(args).await, + Command::Zenoh(args) => zenoh::run(args).await, } } diff --git a/crates/wasmtime-cli/src/zenoh.rs b/crates/wasmtime-cli/src/zenoh.rs new file mode 100644 index 000000000..e6ef400c7 --- /dev/null +++ b/crates/wasmtime-cli/src/zenoh.rs @@ -0,0 +1,98 @@ +use std::sync::Arc; + +use anyhow::Context as _; +use clap::Parser; +use tracing::instrument; + +/// Zenoh transport +#[derive(Parser, Debug)] +pub enum Command { + Run(RunArgs), + Serve(ServeArgs), +} + +/// Run a command component +#[derive(Parser, Debug)] +pub struct RunArgs { + /// Invocation timeout + #[arg(long, default_value = crate::DEFAULT_TIMEOUT)] + timeout: humantime::Duration, + + /// Prefix to send import invocations to + #[arg(long, default_value = "")] + import: String, + + /// Path or URL to Wasm command component + workload: String, +} + +/// Serve a reactor component +#[derive(Parser, Debug)] +pub struct ServeArgs { + /// Invocation timeout + #[arg(long, default_value = crate::DEFAULT_TIMEOUT)] + timeout: humantime::Duration, + + /// Prefix to send import invocations to + #[arg(long, default_value = "")] + import: String, + + /// Prefix to listen for export invocations on + #[arg(long, default_value = "")] + export: String, + + /// Path or URL to Wasm command component + workload: String, +} + +#[instrument(level = "trace", ret(level = "trace"))] +pub async fn handle_run( + RunArgs { + timeout, + import, + ref workload, + }: RunArgs, +) -> anyhow::Result<()> { + let zenoh = wrpc_cli::zenoh::connect() + .await + .context("failed to connect to zenoh")?; + let zenoh_client = wrpc_transport_zenoh::Client::new(zenoh, import) + .await + .context("failed to construct zenoh transport")?; + let res = crate::handle_run(zenoh_client, (), *timeout, workload).await; + //println!("foooooo run"); + res +} + +#[instrument(level = "trace", ret(level = "trace"))] +pub async fn handle_serve( + ServeArgs { + timeout, + export, + import, + ref workload, + }: ServeArgs, +) -> anyhow::Result<()> { + let zenoh = wrpc_cli::zenoh::connect() + .await + .context("failed to connect to zenoh")?; + let zenoh = Arc::new(zenoh); + let exports = wrpc_transport_zenoh::Client::new(zenoh.clone(), export) + .await + .context("failed to construct zenoh transport export client")?; + let imports = wrpc_transport_zenoh::Client::new(zenoh.clone(), import) + .await + .context("failed to construct zenoh transport import client")?; + //println!("foooooo serve"); + // future::pending::<()>().await; + // res + crate::handle_serve(exports, imports, (), *timeout, workload).await +} + +#[instrument(level = "trace", ret(level = "trace"))] +pub async fn run(cmd: Command) -> anyhow::Result<()> { + match cmd { + Command::Run(args) => handle_run(args).await, + Command::Serve(args) => handle_serve(args).await, + } +} diff --git a/examples/rust/hello-component-zclient/Cargo.toml b/examples/rust/hello-component-zclient/Cargo.toml new file mode 100644 index 000000000..e2854e185 --- /dev/null +++ b/examples/rust/hello-component-zclient/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "hello-component-zclient" +version = "0.1.0" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +homepage.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +wit-bindgen = { workspace = true, features = ["realloc", "macros"] } diff --git a/examples/rust/hello-component-zclient/src/main.rs b/examples/rust/hello-component-zclient/src/main.rs new file mode 100644 index 000000000..045b3518e --- /dev/null +++ b/examples/rust/hello-component-zclient/src/main.rs @@ -0,0 +1,19 @@ +use std::time::Instant; + +//WRPC wasm/client to work with hello-zenoh-server(host) + +mod bindings { + wit_bindgen::generate!({ + with: { + "wrpc-examples:hello/handler": generate, + } + }); +} + +fn main() { + let start = Instant::now(); + let response_str = bindings::wrpc_examples::hello::handler::hello(); + println!("*** Response: {response_str}"); + let elapsed = start.elapsed(); + println!("*** Elapsed get: {elapsed:?}"); +} diff --git a/examples/rust/hello-component-zclient/wit/deps.lock b/examples/rust/hello-component-zclient/wit/deps.lock new file mode 100644 index 000000000..275cb66c0 --- /dev/null +++ b/examples/rust/hello-component-zclient/wit/deps.lock @@ -0,0 +1,4 @@ +[hello] +path = "../../../wit/hello" +sha256 = "3680bb734f3fa9f7325674142a2a9b558efd34ea2cb2df7ccb651ad869078d27" +sha512 = "688fdae594dc43bd65bd15ea66b77a8f97cb4bc1c3629719e91d6c1391c66f7c8c6517d096f686cca996188f64f075c4ccb0d70a40097ce76b8b4bcc71dc7506" diff --git a/examples/rust/hello-component-zclient/wit/deps.toml b/examples/rust/hello-component-zclient/wit/deps.toml new file mode 100644 index 000000000..084f03eb0 --- /dev/null +++ b/examples/rust/hello-component-zclient/wit/deps.toml @@ -0,0 +1 @@ +hello = "../../../wit/hello" diff --git a/examples/rust/hello-component-zclient/wit/deps/hello/hello.wit b/examples/rust/hello-component-zclient/wit/deps/hello/hello.wit new file mode 100644 index 000000000..6c84d66cc --- /dev/null +++ b/examples/rust/hello-component-zclient/wit/deps/hello/hello.wit @@ -0,0 +1,13 @@ +package wrpc-examples:hello; + +interface handler { + hello: func() -> string; +} + +world client { + import handler; +} + +world server { + export handler; +} diff --git a/examples/rust/hello-component-zclient/wit/world.wit b/examples/rust/hello-component-zclient/wit/world.wit new file mode 100644 index 000000000..97aae5a62 --- /dev/null +++ b/examples/rust/hello-component-zclient/wit/world.wit @@ -0,0 +1,5 @@ +package wrpc-examples:hello-component-client; + +world client { + include wrpc-examples:hello/client; +} diff --git a/examples/rust/hello-zenoh-client/Cargo.toml b/examples/rust/hello-zenoh-client/Cargo.toml new file mode 100644 index 000000000..49421b22e --- /dev/null +++ b/examples/rust/hello-zenoh-client/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "hello-zenoh-client" +version = "0.1.0" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = { workspace = true } +zenoh = { workspace = true, features = ["transport_tcp"] } +clap = { workspace = true, features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] } +tokio = { workspace = true, features = ["rt-multi-thread"] } +tracing-subscriber = { workspace = true, features = ["ansi", "fmt"] } +url = { workspace = true } +wit-bindgen-wrpc = { workspace = true } +wrpc-transport-zenoh = { workspace = true } +serde_json = {workspace = true} +wrpc-cli = { workspace = true, features = ["zenoh-transport"] } \ No newline at end of file diff --git a/examples/rust/hello-zenoh-client/src/main.rs b/examples/rust/hello-zenoh-client/src/main.rs new file mode 100644 index 000000000..c87ee2e41 --- /dev/null +++ b/examples/rust/hello-zenoh-client/src/main.rs @@ -0,0 +1,44 @@ +use anyhow::Context as _; +use clap::Parser; +use std::sync::Arc; + +mod bindings { + wit_bindgen_wrpc::generate!({ + with: { + "wrpc-examples:hello/handler": generate + } + }); +} + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Prefixes to invoke `wrpc-examples:hello/handler.hello` on + #[arg(default_value = "rust")] + prefixes: Vec, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + + let Args { prefixes } = Args::parse(); + + let session = wrpc_cli::zenoh::connect() + .await + .context("failed to connect to zenoh")?; + + let arc_session = Arc::new(session); + + for prefix in prefixes { + let wrpc = wrpc_transport_zenoh::Client::new(arc_session.clone(), prefix.clone()) + .await + .context("failed to construct transport client")?; + let hello = bindings::wrpc_examples::hello::handler::hello(&wrpc, ()) + .await + .context("failed to invoke `wrpc-examples.hello/handler.hello`")?; + eprintln!("{prefix}: {hello}"); + } + + Ok(()) +} diff --git a/examples/rust/hello-zenoh-client/wit/deps.lock b/examples/rust/hello-zenoh-client/wit/deps.lock new file mode 100644 index 000000000..275cb66c0 --- /dev/null +++ b/examples/rust/hello-zenoh-client/wit/deps.lock @@ -0,0 +1,4 @@ +[hello] +path = "../../../wit/hello" +sha256 = "3680bb734f3fa9f7325674142a2a9b558efd34ea2cb2df7ccb651ad869078d27" +sha512 = "688fdae594dc43bd65bd15ea66b77a8f97cb4bc1c3629719e91d6c1391c66f7c8c6517d096f686cca996188f64f075c4ccb0d70a40097ce76b8b4bcc71dc7506" diff --git a/examples/rust/hello-zenoh-client/wit/deps.toml b/examples/rust/hello-zenoh-client/wit/deps.toml new file mode 100644 index 000000000..084f03eb0 --- /dev/null +++ b/examples/rust/hello-zenoh-client/wit/deps.toml @@ -0,0 +1 @@ +hello = "../../../wit/hello" diff --git a/examples/rust/hello-zenoh-client/wit/deps/hello/hello.wit b/examples/rust/hello-zenoh-client/wit/deps/hello/hello.wit new file mode 100644 index 000000000..6c84d66cc --- /dev/null +++ b/examples/rust/hello-zenoh-client/wit/deps/hello/hello.wit @@ -0,0 +1,13 @@ +package wrpc-examples:hello; + +interface handler { + hello: func() -> string; +} + +world client { + import handler; +} + +world server { + export handler; +} diff --git a/examples/rust/hello-zenoh-client/wit/world.wit b/examples/rust/hello-zenoh-client/wit/world.wit new file mode 100644 index 000000000..f9a08b60d --- /dev/null +++ b/examples/rust/hello-zenoh-client/wit/world.wit @@ -0,0 +1,5 @@ +package wrpc-examples:hello-rust-client; + +world client { + include wrpc-examples:hello/client; +} diff --git a/examples/rust/hello-zenoh-server/Cargo.toml b/examples/rust/hello-zenoh-server/Cargo.toml new file mode 100644 index 000000000..b0dbedfaf --- /dev/null +++ b/examples/rust/hello-zenoh-server/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "hello-zenoh-server" +version = "0.1.0" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = { workspace = true } +zenoh = { workspace = true, features = ["transport_tcp"] } +clap = { workspace = true, features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] } +futures = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "signal"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["ansi", "fmt"] } +url = { workspace = true } +wit-bindgen-wrpc = { workspace = true } +wrpc-transport-zenoh = { workspace = true } +serde_json = {workspace = true} +wrpc-cli = { workspace = true, features = ["zenoh-transport"] } diff --git a/examples/rust/hello-zenoh-server/src/main.rs b/examples/rust/hello-zenoh-server/src/main.rs new file mode 100644 index 000000000..8deba2d41 --- /dev/null +++ b/examples/rust/hello-zenoh-server/src/main.rs @@ -0,0 +1,100 @@ +use core::pin::pin; +use std::sync::Arc; + +use anyhow::Context as _; +use clap::Parser; +use futures::stream::select_all; +use futures::StreamExt as _; +use tokio::task::JoinSet; +use tokio::{select, signal}; +use tracing::{debug, error, info, warn}; + +mod bindings { + wit_bindgen_wrpc::generate!({ + with: { + "wrpc-examples:hello/handler": generate, + } + }); +} + +#[derive(Clone, Copy)] +struct Server; + +impl bindings::exports::wrpc_examples::hello::handler::Handler<()> for Server { + async fn hello(&self, (): ()) -> anyhow::Result { + Ok("hello from Rust".to_string()) + } +} + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Prefix to serve `wrpc-examples:hello/handler.hello` on + #[arg(default_value = "rust")] + prefix: String, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + + let Args { prefix } = Args::parse(); + + let session = wrpc_cli::zenoh::connect() + .await + .context("failed to connect to zenoh")?; + + let arc_session = Arc::new(session); + + let wrpc = wrpc_transport_zenoh::Client::new(arc_session, prefix) + .await + .context("failed to construct transport client")?; + let invocations = bindings::serve(&wrpc, Server) + .await + .context("failed to serve `wrpc-examples.hello/handler.hello`")?; + // NOTE: This will conflate all invocation streams into a single stream via `futures::stream::SelectAll`, + // to customize this, iterate over the returned `invocations` and set up custom handling per export + let mut invocations = select_all( + invocations + .into_iter() + .map(|(instance, name, invocations)| invocations.map(move |res| (instance, name, res))), + ); + let shutdown = signal::ctrl_c(); + let mut shutdown = pin!(shutdown); + let mut tasks = JoinSet::new(); + loop { + select! { + Some((instance, name, res)) = invocations.next() => { + match res { + Ok(fut) => { + debug!(instance, name, "invocation accepted"); + tasks.spawn(async move { + if let Err(err) = fut.await { + warn!(?err, "failed to handle invocation"); + } else { + info!(instance, name, "invocation successfully handled"); + } + }); + } + Err(err) => { + warn!(?err, instance, name, "failed to accept invocation"); + } + } + } + Some(res) = tasks.join_next() => { + if let Err(err) = res { + error!(?err, "failed to join task"); + } + } + res = &mut shutdown => { + // wait for all invocations to complete + while let Some(res) = tasks.join_next().await { + if let Err(err) = res { + error!(?err, "failed to join task"); + } + } + return res.context("failed to listen for ^C") + } + } + } +} diff --git a/examples/rust/hello-zenoh-server/wit/deps.lock b/examples/rust/hello-zenoh-server/wit/deps.lock new file mode 100644 index 000000000..275cb66c0 --- /dev/null +++ b/examples/rust/hello-zenoh-server/wit/deps.lock @@ -0,0 +1,4 @@ +[hello] +path = "../../../wit/hello" +sha256 = "3680bb734f3fa9f7325674142a2a9b558efd34ea2cb2df7ccb651ad869078d27" +sha512 = "688fdae594dc43bd65bd15ea66b77a8f97cb4bc1c3629719e91d6c1391c66f7c8c6517d096f686cca996188f64f075c4ccb0d70a40097ce76b8b4bcc71dc7506" diff --git a/examples/rust/hello-zenoh-server/wit/deps.toml b/examples/rust/hello-zenoh-server/wit/deps.toml new file mode 100644 index 000000000..084f03eb0 --- /dev/null +++ b/examples/rust/hello-zenoh-server/wit/deps.toml @@ -0,0 +1 @@ +hello = "../../../wit/hello" diff --git a/examples/rust/hello-zenoh-server/wit/deps/hello/hello.wit b/examples/rust/hello-zenoh-server/wit/deps/hello/hello.wit new file mode 100644 index 000000000..6c84d66cc --- /dev/null +++ b/examples/rust/hello-zenoh-server/wit/deps/hello/hello.wit @@ -0,0 +1,13 @@ +package wrpc-examples:hello; + +interface handler { + hello: func() -> string; +} + +world client { + import handler; +} + +world server { + export handler; +} diff --git a/examples/rust/hello-zenoh-server/wit/world.wit b/examples/rust/hello-zenoh-server/wit/world.wit new file mode 100644 index 000000000..8fffd81fd --- /dev/null +++ b/examples/rust/hello-zenoh-server/wit/world.wit @@ -0,0 +1,5 @@ +package wrpc-examples:hello-rust-server; + +world server { + include wrpc-examples:hello/server; +} diff --git a/examples/rust/streams-zenoh-client/Cargo.toml b/examples/rust/streams-zenoh-client/Cargo.toml new file mode 100644 index 000000000..f0f792a7c --- /dev/null +++ b/examples/rust/streams-zenoh-client/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "streams-zenoh-client" +version = "0.1.0" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = { workspace = true } +zenoh = { workspace = true, features = ["transport_tcp"] } +bytes = { workspace = true } +clap = { workspace = true, features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] } +futures = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread"] } +tokio-stream = { workspace = true, features = ["time"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = [ + "ansi", + "env-filter", + "fmt", +] } +url = { workspace = true } +wit-bindgen-wrpc = { workspace = true } +wrpc-transport-zenoh = { workspace = true } +serde_json = {workspace = true} +wrpc-cli = { workspace = true, features = ["zenoh-transport"] } \ No newline at end of file diff --git a/examples/rust/streams-zenoh-client/src/main.rs b/examples/rust/streams-zenoh-client/src/main.rs new file mode 100644 index 000000000..fc835315f --- /dev/null +++ b/examples/rust/streams-zenoh-client/src/main.rs @@ -0,0 +1,91 @@ +use anyhow::Context as _; +use bytes::Bytes; +use clap::Parser; +use core::time::Duration; +use futures::{stream, StreamExt as _}; +use std::sync::Arc; +use tokio::{time, try_join}; +use tokio_stream::wrappers::IntervalStream; +use tracing::debug; + +mod bindings { + wit_bindgen_wrpc::generate!({ + with: { + "wrpc-examples:streams/handler": generate + } + }); +} + +use bindings::wrpc_examples::streams::handler::{echo, Req}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Prefixes to invoke `wrpc-examples:streams/handler.echo` on + #[arg(default_value = "rust")] + prefixes: Vec, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + + let Args { prefixes } = Args::parse(); + + let session = wrpc_cli::zenoh::connect() + .await + .context("failed to connect to zenoh")?; + + let arc_session = Arc::new(session); + + for prefix in prefixes { + let numbers = Box::pin( + stream::iter(1..) + .take(10) + .zip(IntervalStream::new(time::interval(Duration::from_secs(1)))) + .map(|(i, _)| i) + .ready_chunks(10), + ); + + // `stream` items are chunked using [`Bytes`] + let bytes = Box::pin( + stream::iter(b"foo bar baz") + .zip(IntervalStream::new(time::interval(Duration::from_secs(1)))) + .map(|(i, _)| *i) + .ready_chunks(10) + .map(Bytes::from), + ); + + // Client creation moved here from top + let wrpc = wrpc_transport_zenoh::Client::new(arc_session.clone(), prefix) + .await + .context("failed to construct transport client")?; + + let (mut numbers, mut bytes, io) = echo(&wrpc, (), Req { numbers, bytes }) + .await + .context("failed to invoke `wrpc-examples:streams/handler.echo`")?; + try_join!( + async { + if let Some(io) = io { + debug!("performing async I/O"); + io.await.context("failed to complete async I/O") + } else { + Ok(()) + } + }, + async { + while let Some(item) = numbers.next().await { + eprintln!("numbers: {item:?}"); + } + Ok(()) + }, + async { + while let Some(item) = bytes.next().await { + eprintln!("bytes: {item:?}"); + } + Ok(()) + } + )?; + } + Ok(()) +} diff --git a/examples/rust/streams-zenoh-client/wit/deps.lock b/examples/rust/streams-zenoh-client/wit/deps.lock new file mode 100644 index 000000000..640642729 --- /dev/null +++ b/examples/rust/streams-zenoh-client/wit/deps.lock @@ -0,0 +1,4 @@ +[streams] +path = "../../../wit/streams" +sha256 = "5064bee90ebea73f1695987191fbbfea71ed2dbb69839814009490b4fbe8e96f" +sha512 = "dfca3844d91c6c8e83fefd7b9511a366b464cf69d017c61b671409cb26dc9490a0e59a8e60ef15b77fdeb4fc1b8d9e6efa11c2fb1a1dabd0141e5e6afe8a59b9" diff --git a/examples/rust/streams-zenoh-client/wit/deps.toml b/examples/rust/streams-zenoh-client/wit/deps.toml new file mode 100644 index 000000000..7ad7d00c7 --- /dev/null +++ b/examples/rust/streams-zenoh-client/wit/deps.toml @@ -0,0 +1 @@ +streams = "../../../wit/streams" diff --git a/examples/rust/streams-zenoh-client/wit/deps/streams/streams.wit b/examples/rust/streams-zenoh-client/wit/deps/streams/streams.wit new file mode 100644 index 000000000..8520741c2 --- /dev/null +++ b/examples/rust/streams-zenoh-client/wit/deps/streams/streams.wit @@ -0,0 +1,17 @@ +package wrpc-examples:streams; + +interface handler { + record req { + numbers: stream, + bytes: stream, + } + echo: func(r: req) -> (numbers: stream, bytes: stream); +} + +world client { + import handler; +} + +world server { + export handler; +} diff --git a/examples/rust/streams-zenoh-client/wit/world.wit b/examples/rust/streams-zenoh-client/wit/world.wit new file mode 100644 index 000000000..5f6997fdc --- /dev/null +++ b/examples/rust/streams-zenoh-client/wit/world.wit @@ -0,0 +1,5 @@ +package wrpc-examples:streams-rust-client; + +world client { + include wrpc-examples:streams/client; +} diff --git a/examples/rust/streams-zenoh-server/Cargo.toml b/examples/rust/streams-zenoh-server/Cargo.toml new file mode 100644 index 000000000..e3583d171 --- /dev/null +++ b/examples/rust/streams-zenoh-server/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "streams-zenoh-server" +version = "0.1.0" + +authors.workspace = true +categories.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true + +[dependencies] +anyhow = { workspace = true } +zenoh = { workspace = true, features = ["transport_tcp"] } +bytes = { workspace = true } +clap = { workspace = true, features = [ + "color", + "derive", + "error-context", + "help", + "std", + "suggestions", + "usage", +] } +futures = { workspace = true } +tokio = { workspace = true, features = ["rt-multi-thread", "signal"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = [ + "ansi", + "env-filter", + "fmt", +] } +url = { workspace = true } +wit-bindgen-wrpc = { workspace = true } +wrpc-transport-zenoh = { workspace = true } +serde_json = {workspace = true} +wrpc-cli = { workspace = true, features = ["zenoh-transport"] } diff --git a/examples/rust/streams-zenoh-server/src/main.rs b/examples/rust/streams-zenoh-server/src/main.rs new file mode 100644 index 000000000..af05d96ee --- /dev/null +++ b/examples/rust/streams-zenoh-server/src/main.rs @@ -0,0 +1,110 @@ +use anyhow::Context as _; +use bytes::Bytes; +use clap::Parser; +use core::pin::{pin, Pin}; +use futures::stream::select_all; +use futures::{Stream, StreamExt as _}; +use std::sync::Arc; +use tokio::task::JoinSet; +use tokio::{select, signal}; +use tracing::{debug, error, info, warn}; + +mod bindings { + wit_bindgen_wrpc::generate!({ + with: { + "wrpc-examples:streams/handler": generate, + } + }); +} + +use bindings::exports::wrpc_examples::streams::handler::Req; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Prefix to serve `wrpc-examples:streams/handler.echo` on + #[arg(default_value = "rust")] + prefix: String, +} + +#[derive(Clone, Copy)] +struct Server; + +impl bindings::exports::wrpc_examples::streams::handler::Handler<()> for Server { + async fn echo( + &self, + _cx: (), + Req { numbers, bytes }: Req, + ) -> anyhow::Result<( + Pin> + Send>>, + Pin + Send>>, + )> { + Ok((numbers, bytes)) + } +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt().init(); + + let Args { prefix } = Args::parse(); + + let session = wrpc_cli::zenoh::connect() + .await + .context("failed to connect to zenoh")?; + + let arc_session = Arc::new(session); + + let wrpc = wrpc_transport_zenoh::Client::new(arc_session, prefix) + .await + .context("failed to construct transport client")?; + let invocations = bindings::serve(&wrpc, Server) + .await + .context("failed to serve `wrpc-examples:streams/handler.echo`")?; + + // NOTE: This will conflate all invocation streams into a single stream via `futures::stream::SelectAll`, + // to customize this, iterate over the returned `invocations` and set up custom handling per export + let mut invocations = select_all( + invocations + .into_iter() + .map(|(instance, name, invocations)| invocations.map(move |res| (instance, name, res))), + ); + let shutdown = signal::ctrl_c(); + let mut shutdown = pin!(shutdown); + let mut tasks = JoinSet::new(); + loop { + select! { + Some((instance, name, res)) = invocations.next() => { + match res { + Ok(fut) => { + debug!(instance, name, "invocation accepted"); + tasks.spawn(async move { + if let Err(err) = fut.await { + warn!(?err, "failed to handle invocation"); + } else { + info!(instance, name, "invocation successfully handled"); + } + }); + } + Err(err) => { + warn!(?err, instance, name, "failed to accept invocation"); + } + } + } + Some(res) = tasks.join_next() => { + if let Err(err) = res { + error!(?err, "failed to join task"); + } + } + res = &mut shutdown => { + // wait for all invocations to complete + while let Some(res) = tasks.join_next().await { + if let Err(err) = res { + error!(?err, "failed to join task"); + } + } + return res.context("failed to listen for ^C") + } + } + } +} diff --git a/examples/rust/streams-zenoh-server/wit/deps.lock b/examples/rust/streams-zenoh-server/wit/deps.lock new file mode 100644 index 000000000..640642729 --- /dev/null +++ b/examples/rust/streams-zenoh-server/wit/deps.lock @@ -0,0 +1,4 @@ +[streams] +path = "../../../wit/streams" +sha256 = "5064bee90ebea73f1695987191fbbfea71ed2dbb69839814009490b4fbe8e96f" +sha512 = "dfca3844d91c6c8e83fefd7b9511a366b464cf69d017c61b671409cb26dc9490a0e59a8e60ef15b77fdeb4fc1b8d9e6efa11c2fb1a1dabd0141e5e6afe8a59b9" diff --git a/examples/rust/streams-zenoh-server/wit/deps.toml b/examples/rust/streams-zenoh-server/wit/deps.toml new file mode 100644 index 000000000..7ad7d00c7 --- /dev/null +++ b/examples/rust/streams-zenoh-server/wit/deps.toml @@ -0,0 +1 @@ +streams = "../../../wit/streams" diff --git a/examples/rust/streams-zenoh-server/wit/deps/streams/streams.wit b/examples/rust/streams-zenoh-server/wit/deps/streams/streams.wit new file mode 100644 index 000000000..8520741c2 --- /dev/null +++ b/examples/rust/streams-zenoh-server/wit/deps/streams/streams.wit @@ -0,0 +1,17 @@ +package wrpc-examples:streams; + +interface handler { + record req { + numbers: stream, + bytes: stream, + } + echo: func(r: req) -> (numbers: stream, bytes: stream); +} + +world client { + import handler; +} + +world server { + export handler; +} diff --git a/examples/rust/streams-zenoh-server/wit/world.wit b/examples/rust/streams-zenoh-server/wit/world.wit new file mode 100644 index 000000000..ba3ffa1b6 --- /dev/null +++ b/examples/rust/streams-zenoh-server/wit/world.wit @@ -0,0 +1,5 @@ +package wrpc-examples:streams-rust-server; + +world server { + include wrpc-examples:streams/server; +} diff --git a/flake.nix b/flake.nix index cbdabaf4f..1c7b6e48d 100644 --- a/flake.nix +++ b/flake.nix @@ -122,6 +122,8 @@ pkgs.nats-server pkgs.pkgsUnstable.go + + pkgs.zenoh ]; }; @@ -210,6 +212,7 @@ pkgs.cargo-audit pkgs.cargo-nextest pkgs.wit-deps + pkgs.zenoh pkgs.pkgsUnstable.go pkgs.pkgsUnstable.nats-server diff --git a/tests/rust.rs b/tests/rust.rs index 8807c4994..0c6b1a248 100644 --- a/tests/rust.rs +++ b/tests/rust.rs @@ -8,6 +8,8 @@ use core::pin::{pin, Pin}; use core::str; use core::time::Duration; +use std::future::Future; +use std::pin::Pin; use std::sync::Arc; use std::thread; @@ -1268,6 +1270,51 @@ async fn rust_dynamic_web_transport() -> anyhow::Result<()> { .await } +#[cfg(feature = "zenoh-transport")] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +#[instrument(ret)] +async fn rust_bindgen_zenoh_sync() -> anyhow::Result<()> { + wrpc_test::with_zenoh(|_, zenoh_client| async { + let clt = wrpc_transport_zenoh::Client::new(zenoh_client, "rust-bindgen-sync") + .await + .context("failed to construct client")?; + let clt = Arc::new(clt); + assert_bindgen_sync(Arc::clone(&clt), clt).await + }) + .await +} + +#[cfg(feature = "zenoh-transport")] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +#[instrument(ret)] +async fn rust_bindgen_zenoh_async() -> anyhow::Result<()> { + wrpc_test::with_zenoh(|_, zenoh_client| { + async { + let clt = wrpc_transport_zenoh::Client::new(zenoh_client, "rust-bindgen-sync") + .await + .context("failed to construct client")?; + let clt = Arc::new(clt); + assert_bindgen_async(Arc::clone(&clt), clt).await + } + .in_current_span() + }) + .await +} + +#[cfg(feature = "zenoh-transport")] +#[test_log::test(tokio::test(flavor = "multi_thread"))] +#[instrument(ret)] +async fn rust_dynamic_zenoh() -> anyhow::Result<()> { + wrpc_test::with_zenoh(|_, zenoh_client| async { + let clt = wrpc_transport_zenoh::Client::new(zenoh_client, "rust-bindgen-dynamic") + .await + .unwrap(); + let clt = Arc::new(clt); + assert_dynamic(Arc::clone(&clt), clt).await + }) + .await +} + #[test_log::test(tokio::test(flavor = "multi_thread"))] #[instrument(ret)] async fn rust_bindgen_tcp_sync() -> anyhow::Result<()> {