From 813667093eca0fcc16c4f4e6b8182084a444f35b Mon Sep 17 00:00:00 2001 From: Uncle Stretch Date: Sun, 21 Dec 2025 16:15:11 +0300 Subject: [PATCH] initial commit; draft implementation Signed-off-by: Uncle Stretch --- .gitignore | 1 + Cargo.lock | 5242 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 31 + src/bin/main.rs | 110 + src/chatpeer.rs | 57 + src/file_exchange.rs | 202 ++ src/generated/mod.rs | 2 + src/generated/peer.proto | 10 + src/generated/peer.rs | 52 + src/lib.rs | 55 + src/log.rs | 71 + src/message.rs | 25 + src/options.rs | 71 + src/peer.rs | 1114 ++++++++ src/ui/headless.rs | 114 + src/ui/mod.rs | 15 + src/ui/tui.rs | 508 ++++ src/util.rs | 189 ++ 18 files changed, 7869 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/bin/main.rs create mode 100644 src/chatpeer.rs create mode 100644 src/file_exchange.rs create mode 100644 src/generated/mod.rs create mode 100644 src/generated/peer.proto create mode 100644 src/generated/peer.rs create mode 100644 src/lib.rs create mode 100644 src/log.rs create mode 100644 src/message.rs create mode 100644 src/options.rs create mode 100644 src/peer.rs create mode 100644 src/ui/headless.rs create mode 100644 src/ui/mod.rs create mode 100644 src/ui/tui.rs create mode 100644 src/util.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f358aef --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5242 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[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 = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.60.2", +] + +[[package]] +name = "anyhow" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "asn1-rs" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" +dependencies = [ + "asn1-rs-derive 0.5.1", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.16", + "time", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "asn1-rs-impl" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asn1_der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "155a5a185e42c6b77ac7b88a15143d930a9e9727a5b7b77eed417404ab15c247" + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-io" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca" +dependencies = [ + "async-lock", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix 1.0.8", + "slab", + "windows-sys 0.60.2", +] + +[[package]] +name = "async-lock" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "asynchronous-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" +dependencies = [ + "bytes", + "futures-sink", + "futures-util", + "memchr", + "pin-project-lite", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "attohttpc" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" +dependencies = [ + "base64", + "http", + "log", + "url", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42bc4aea80032b7bf409b0bc7ccad88853858911b7713a8062fdc0623867bedc" +dependencies = [ + "shlex", +] + +[[package]] +name = "ccm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae3c82e4355234767756212c570e29833699ab63e6ffd161887314cc5b43847" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + +[[package]] +name = "cfg-if" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + +[[package]] +name = "chrono" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-link 0.2.1", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + +[[package]] +name = "clap" +version = "4.5.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c5e4fcf9c21d2e544ca1ee9d8552de13019a42aa7dbf32747fa7aaf1df76e57" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecb53a0e6fcfb055f686001bc2e2592fa527efaf38dbe81a6a9563562e57d41" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[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 = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core2" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505" +dependencies = [ + "memchr", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags 2.9.3", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core 0.6.4", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "data-encoding-macro" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47ce6c96ea0102f01122a185683611bd5ac8d99e62bc59dd12e6bda344ee673d" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d162beedaa69905488a8da94f5ac3edb4dd4788b732fadb7bd120b2625c1976" +dependencies = [ + "data-encoding", + "syn", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "der-parser" +version = "9.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +dependencies = [ + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "der-parser" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs 0.7.1", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dtoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-as-inner" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" +dependencies = [ + "libc", + "windows-sys 0.60.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 = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-bounded" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" +dependencies = [ + "futures-timer", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generator" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "605183a538e3e2a9c1038635cc5c2d194e2ee8fd0d1b66b8349fad7dbacce5a2" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows 0.61.3", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.3+wasi-0.2.4", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "ghost-gossiper" +version = "0.0.1" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "clap", + "crossterm", + "futures", + "futures-timer", + "hex", + "libp2p", + "libp2p-webrtc", + "quick-protobuf", + "rand 0.9.2", + "rand_core 0.6.4", + "ratatui", + "serde_json", + "signal-hook", + "tokio", + "tokio-util", + "tracing", + "tracing-subscriber", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +dependencies = [ + "hashbrown 0.14.5", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[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 = "hex_fmt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" + +[[package]] +name = "hickory-proto" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502" +dependencies = [ + "async-trait", + "cfg-if", + "data-encoding", + "enum-as-inner", + "futures-channel", + "futures-io", + "futures-util", + "idna", + "ipnet", + "once_cell", + "rand 0.9.2", + "ring", + "socket2 0.5.10", + "thiserror 2.0.16", + "tinyvec", + "tokio", + "tracing", + "url", +] + +[[package]] +name = "hickory-resolver" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a" +dependencies = [ + "cfg-if", + "futures-util", + "hickory-proto", + "ipconfig", + "moka", + "once_cell", + "parking_lot", + "rand 0.9.2", + "resolv-conf", + "smallvec", + "thiserror 2.0.16", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2 0.6.0", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.61.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "if-addrs" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cabb0019d51a643781ff15c9c8a3e5dedc365c47211270f4e8f82812fedd8f0a" +dependencies = [ + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "if-watch" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf9d64cfcf380606e64f9a0bcf493616b65331199f984151a6fa11a7b3cde38" +dependencies = [ + "async-io", + "core-foundation", + "fnv", + "futures", + "if-addrs", + "ipnet", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-proto", + "netlink-sys", + "rtnetlink", + "system-configuration", + "tokio", + "windows 0.53.0", +] + +[[package]] +name = "igd-next" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" +dependencies = [ + "async-trait", + "attohttpc", + "bytes", + "futures", + "http", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.2", + "tokio", + "url", + "xmltree", +] + +[[package]] +name = "indexmap" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" +dependencies = [ + "equivalent", + "hashbrown 0.15.5", +] + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "instability" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "interceptor" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ab04c530fd82e414e40394cabe5f0ebfe30d119f10fe29d6e3561926af412e" +dependencies = [ + "async-trait", + "bytes", + "log", + "portable-atomic", + "rand 0.8.5", + "rtcp", + "rtp", + "thiserror 1.0.69", + "tokio", + "waitgroup", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "io-uring" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b" +dependencies = [ + "bitflags 2.9.3", + "cfg-if", + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" +dependencies = [ + "socket2 0.5.10", + "widestring", + "windows-sys 0.48.0", + "winreg", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.175" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" + +[[package]] +name = "libp2p" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce71348bf5838e46449ae240631117b487073d5f347c06d434caddcb91dceb5a" +dependencies = [ + "bytes", + "either", + "futures", + "futures-timer", + "getrandom 0.2.16", + "libp2p-allow-block-list", + "libp2p-autonat", + "libp2p-connection-limits", + "libp2p-core", + "libp2p-dcutr", + "libp2p-dns", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-mdns", + "libp2p-memory-connection-limits", + "libp2p-metrics", + "libp2p-noise", + "libp2p-ping", + "libp2p-quic", + "libp2p-relay", + "libp2p-request-response", + "libp2p-swarm", + "libp2p-tcp", + "libp2p-tls", + "libp2p-upnp", + "libp2p-yamux", + "multiaddr", + "pin-project", + "rw-stream-sink", + "thiserror 2.0.16", +] + +[[package]] +name = "libp2p-allow-block-list" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16ccf824ee859ca83df301e1c0205270206223fd4b1f2e512a693e1912a8f4a" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-autonat" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fab5e25c49a7d48dac83d95d8f3bac0a290d8a5df717012f6e34ce9886396c0b" +dependencies = [ + "async-trait", + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-request-response", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "rand_core 0.6.4", + "thiserror 2.0.16", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-connection-limits" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18b8b607cf3bfa2f8c57db9c7d8569a315d5cc0a282e6bfd5ebfc0a9840b2a0" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] + +[[package]] +name = "libp2p-core" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d28e2d2def7c344170f5c6450c0dbe3dfef655610dbfde2f6ac28a527abbe36" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-identity", + "multiaddr", + "multihash", + "multistream-select", + "parking_lot", + "pin-project", + "quick-protobuf", + "rand 0.8.5", + "rw-stream-sink", + "thiserror 2.0.16", + "tracing", + "unsigned-varint 0.8.0", + "web-time", +] + +[[package]] +name = "libp2p-dcutr" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4f0eec23bc79cabfdf6934718f161fc42a1d98e2c9d44007c80eb91534200c" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "lru", + "quick-protobuf", + "quick-protobuf-codec", + "thiserror 2.0.16", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-dns" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b770c1c8476736ca98c578cba4b505104ff8e842c2876b528925f9766379f9a" +dependencies = [ + "async-trait", + "futures", + "hickory-resolver", + "libp2p-core", + "libp2p-identity", + "parking_lot", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-gossipsub" +version = "0.49.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f58e37d8d6848e5c4c9e3c35c6f61133235bff2960c9c00a663b0849301221" +dependencies = [ + "async-channel", + "asynchronous-codec", + "base64", + "byteorder", + "bytes", + "either", + "fnv", + "futures", + "futures-timer", + "getrandom 0.2.16", + "hashlink", + "hex_fmt", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "regex", + "sha2", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-identify" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab792a8b68fdef443a62155b01970c81c3aadab5e659621b063ef252a8e65e8" +dependencies = [ + "asynchronous-codec", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "smallvec", + "thiserror 2.0.16", + "tracing", +] + +[[package]] +name = "libp2p-identity" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3104e13b51e4711ff5738caa1fb54467c8604c2e94d607e27745bcf709068774" +dependencies = [ + "asn1_der", + "bs58", + "ed25519-dalek", + "hkdf", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "ring", + "sha2", + "thiserror 2.0.16", + "tracing", + "zeroize", +] + +[[package]] +name = "libp2p-kad" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13d3fd632a5872ec804d37e7413ceea20588f69d027a0fa3c46f82574f4dee60" +dependencies = [ + "asynchronous-codec", + "bytes", + "either", + "fnv", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "sha2", + "smallvec", + "thiserror 2.0.16", + "tracing", + "uint", + "web-time", +] + +[[package]] +name = "libp2p-mdns" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66872d0f1ffcded2788683f76931be1c52e27f343edb93bc6d0bcd8887be443" +dependencies = [ + "futures", + "hickory-proto", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-memory-connection-limits" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d052a767edd0235d5c29dacf46013955eabce1085781ce0d12a4fc66bf87cd" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "memory-stats", + "sysinfo", + "tracing", +] + +[[package]] +name = "libp2p-metrics" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "805a555148522cb3414493a5153451910cb1a146c53ffbf4385708349baf62b7" +dependencies = [ + "futures", + "libp2p-core", + "libp2p-dcutr", + "libp2p-gossipsub", + "libp2p-identify", + "libp2p-identity", + "libp2p-kad", + "libp2p-ping", + "libp2p-relay", + "libp2p-swarm", + "pin-project", + "prometheus-client", + "web-time", +] + +[[package]] +name = "libp2p-noise" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc73eacbe6462a0eb92a6527cac6e63f02026e5407f8831bde8293f19217bfbf" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "libp2p-core", + "libp2p-identity", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "snow", + "static_assertions", + "thiserror 2.0.16", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "libp2p-ping" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74bb7fcdfd9fead4144a3859da0b49576f171a8c8c7c0bfc7c541921d25e60d3" +dependencies = [ + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-quic" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dc448b2de9f4745784e3751fe8bc6c473d01b8317edd5ababcb0dec803d843f" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-tls", + "quinn", + "rand 0.8.5", + "ring", + "rustls", + "socket2 0.5.10", + "thiserror 2.0.16", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-relay" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551b24ae04c63859bf5e25644acdd6aa469deb5c5cd872ca21c2c9b45a5a5192" +dependencies = [ + "asynchronous-codec", + "bytes", + "either", + "futures", + "futures-bounded", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "static_assertions", + "thiserror 2.0.16", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-request-response" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9f1cca83488b90102abac7b67d5c36fc65bc02ed47620228af7ed002e6a1478" +dependencies = [ + "async-trait", + "futures", + "futures-bounded", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", + "rand 0.8.5", + "smallvec", + "tracing", +] + +[[package]] +name = "libp2p-swarm" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa762e5215919a34e31c35d4b18bf2e18566ecab7f8a3d39535f4a3068f8b62" +dependencies = [ + "either", + "fnv", + "futures", + "futures-timer", + "libp2p-core", + "libp2p-identity", + "libp2p-swarm-derive", + "lru", + "multistream-select", + "rand 0.8.5", + "smallvec", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "libp2p-swarm-derive" +version = "0.35.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd297cf53f0cb3dee4d2620bb319ae47ef27c702684309f682bdb7e55a18ae9c" +dependencies = [ + "heck", + "quote", + "syn", +] + +[[package]] +name = "libp2p-tcp" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65b4e030c52c46c8d01559b2b8ca9b7c4185f10576016853129ca1fe5cd1a644" +dependencies = [ + "futures", + "futures-timer", + "if-watch", + "libc", + "libp2p-core", + "socket2 0.5.10", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-tls" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" +dependencies = [ + "futures", + "futures-rustls", + "libp2p-core", + "libp2p-identity", + "rcgen", + "ring", + "rustls", + "rustls-webpki", + "thiserror 2.0.16", + "x509-parser 0.17.0", + "yasna", +] + +[[package]] +name = "libp2p-upnp" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4757e65fe69399c1a243bbb90ec1ae5a2114b907467bf09f3575e899815bb8d3" +dependencies = [ + "futures", + "futures-timer", + "igd-next", + "libp2p-core", + "libp2p-swarm", + "tokio", + "tracing", +] + +[[package]] +name = "libp2p-webrtc" +version = "0.9.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57bc51d86236d33762bccf5015e4ece458c549476c362040d4e1e6f3615e41b0" +dependencies = [ + "async-trait", + "futures", + "futures-timer", + "hex", + "if-watch", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "libp2p-webrtc-utils", + "multihash", + "rand 0.8.5", + "rcgen", + "stun", + "thiserror 2.0.16", + "tokio", + "tokio-util", + "tracing", + "webrtc", +] + +[[package]] +name = "libp2p-webrtc-utils" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490abff5ee5f9a7a77f0145c79cc97c76941231a3626f4dee18ebf2abb95618f" +dependencies = [ + "asynchronous-codec", + "bytes", + "futures", + "hex", + "libp2p-core", + "libp2p-identity", + "libp2p-noise", + "quick-protobuf", + "quick-protobuf-codec", + "rand 0.8.5", + "serde", + "sha2", + "tinytemplate", + "tracing", +] + +[[package]] +name = "libp2p-yamux" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f15df094914eb4af272acf9adaa9e287baa269943f32ea348ba29cfb9bfc60d8" +dependencies = [ + "either", + "futures", + "libp2p-core", + "thiserror 2.0.16", + "tracing", + "yamux 0.12.1", + "yamux 0.13.6", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory-stats" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c73f5c649995a115e1a0220b35e4df0a1294500477f97a91d0660fb5abeb574a" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "minimal-lexical" +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", +] + +[[package]] +name = "mio" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +dependencies = [ + "libc", + "log", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", +] + +[[package]] +name = "moka" +version = "0.12.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926" +dependencies = [ + "crossbeam-channel", + "crossbeam-epoch", + "crossbeam-utils", + "loom", + "parking_lot", + "portable-atomic", + "rustc_version", + "smallvec", + "tagptr", + "thiserror 1.0.69", + "uuid", +] + +[[package]] +name = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.8.0", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b3539ec3c1f04ac9748a260728e855f261b4977f5c3406612c884564f329404" +dependencies = [ + "base-x", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b430e7953c29dd6a09afc29ff0bb69c6e306329ee6794700aee27b76a1aea8d" +dependencies = [ + "core2", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "log", + "pin-project", + "smallvec", + "unsigned-varint 0.7.2", +] + +[[package]] +name = "netlink-packet-core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" +dependencies = [ + "anyhow", + "byteorder", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-route" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" +dependencies = [ + "anyhow", + "bitflags 1.3.2", + "byteorder", + "libc", + "netlink-packet-core", + "netlink-packet-utils", +] + +[[package]] +name = "netlink-packet-utils" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" +dependencies = [ + "anyhow", + "byteorder", + "paste", + "thiserror 1.0.69", +] + +[[package]] +name = "netlink-proto" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72452e012c2f8d612410d89eea01e2d9b56205274abb35d53f60200b2ec41d60" +dependencies = [ + "bytes", + "futures", + "log", + "netlink-packet-core", + "netlink-sys", + "thiserror 2.0.16", +] + +[[package]] +name = "netlink-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16c903aa70590cb93691bf97a767c8d1d6122d2cc9070433deb3bbf36ce8bd23" +dependencies = [ + "bytes", + "futures", + "libc", + "log", + "tokio", +] + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", + "memoffset", + "pin-utils", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "ntapi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +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.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "oid-registry" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" +dependencies = [ + "asn1-rs 0.6.2", +] + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.1", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polling" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix 1.0.8", + "windows-sys 0.60.2", +] + +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84df19adbe5b5a0782edcab45899906947ab039ccf4573713735ee7de1e6b08a" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prometheus-client" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf41c1a7c32ed72abe5082fb19505b969095c12da9f5732a4bc9878757fd087c" +dependencies = [ + "dtoa", + "itoa", + "parking_lot", + "prometheus-client-derive-encode", +] + +[[package]] +name = "prometheus-client-derive-encode" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + +[[package]] +name = "quick-protobuf-codec" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" +dependencies = [ + "asynchronous-codec", + "bytes", + "quick-protobuf", + "thiserror 1.0.69", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "futures-io", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.0", + "thiserror 2.0.16", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.16", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2 0.6.0", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags 2.9.3", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rcgen" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +dependencies = [ + "pem", + "ring", + "rustls-pki-types", + "time", + "x509-parser 0.16.0", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" +dependencies = [ + "bitflags 2.9.3", +] + +[[package]] +name = "regex" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.10", + "regex-syntax 0.8.6", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.6", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001" + +[[package]] +name = "resolv-conf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rtcp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8306430fb118b7834bbee50e744dc34826eca1da2158657a3d6cbc70e24c2096" +dependencies = [ + "bytes", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rtnetlink" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a552eb82d19f38c3beed3f786bd23aa434ceb9ac43ab44419ca6d67a7e186c0" +dependencies = [ + "futures", + "log", + "netlink-packet-core", + "netlink-packet-route", + "netlink-packet-utils", + "netlink-proto", + "netlink-sys", + "nix", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "rtp" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e68baca5b6cb4980678713f0d06ef3a432aa642baefcbfd0f4dd2ef9eb5ab550" +dependencies = [ + "bytes", + "memchr", + "portable-atomic", + "rand 0.8.5", + "serde", + "thiserror 1.0.69", + "webrtc-util", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rusticata-macros" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" +dependencies = [ + "nom", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.3", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +dependencies = [ + "bitflags 2.9.3", + "errno", + "libc", + "linux-raw-sys 0.9.4", + "windows-sys 0.60.2", +] + +[[package]] +name = "rustls" +version = "0.23.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +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 = "rw-stream-sink" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" +dependencies = [ + "futures", + "pin-project", + "static_assertions", +] + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a526161f474ae94b966ba622379d939a8fe46c930eebbadb73e339622599d5" +dependencies = [ + "rand 0.8.5", + "substring", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.143" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +dependencies = [ + "libc", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] + +[[package]] +name = "snow" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850948bee068e713b8ab860fe1adc4d109676ab4c3b621fd8147f06b261f2f85" +dependencies = [ + "aes-gcm", + "blake2", + "chacha20poly1305", + "curve25519-dalek", + "rand_core 0.6.4", + "ring", + "rustc_version", + "sha2", + "subtle", +] + +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "stun" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea256fb46a13f9204e9dee9982997b2c3097db175a9fddaa8350310d03c4d5a3" +dependencies = [ + "base64", + "crc", + "lazy_static", + "md-5", + "rand 0.8.5", + "ring", + "subtle", + "thiserror 1.0.69", + "tokio", + "url", + "webrtc-util", +] + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows 0.57.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.3", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl 2.0.16", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.47.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +dependencies = [ + "backtrace", + "bytes", + "io-uring", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "slab", + "socket2 0.6.0", + "tokio-macros", + "windows-sys 0.59.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" +dependencies = [ + "bytes", + "futures-core", + "futures-io", + "futures-sink", + "futures-util", + "hashbrown 0.15.5", + "pin-project-lite", + "slab", + "tokio", +] + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "turn" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0044fdae001dd8a1e247ea6289abf12f4fcea1331a2364da512f9cd680bbd8cb" +dependencies = [ + "async-trait", + "base64", + "futures", + "log", + "md-5", + "portable-atomic", + "rand 0.8.5", + "ring", + "stun", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "webrtc-util", +] + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", +] + +[[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.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsigned-varint" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" + +[[package]] +name = "unsigned-varint" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f33196643e165781c20a5ead5582283a7dacbb87855d867fbc2df3f81eddc1be" +dependencies = [ + "getrandom 0.3.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "waitgroup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1f50000a783467e6c0200f9d10642f4bc424e39efc1b770203e88b488f79292" +dependencies = [ + "atomic-waker", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.3+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51ae83037bdd272a9e28ce236db8c07016dd0d50c27038b3f407533c030c95" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webrtc" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30367074d9f18231d28a74fab0120856b2b665da108d71a12beab7185a36f97b" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "cfg-if", + "hex", + "interceptor", + "lazy_static", + "log", + "pem", + "portable-atomic", + "rand 0.8.5", + "rcgen", + "regex", + "ring", + "rtcp", + "rtp", + "rustls", + "sdp", + "serde", + "serde_json", + "sha2", + "smol_str", + "stun", + "thiserror 1.0.69", + "time", + "tokio", + "turn", + "url", + "waitgroup", + "webrtc-data", + "webrtc-dtls", + "webrtc-ice", + "webrtc-mdns", + "webrtc-media", + "webrtc-sctp", + "webrtc-srtp", + "webrtc-util", +] + +[[package]] +name = "webrtc-data" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec93b991efcd01b73c5b3503fa8adba159d069abe5785c988ebe14fcf8f05d1" +dependencies = [ + "bytes", + "log", + "portable-atomic", + "thiserror 1.0.69", + "tokio", + "webrtc-sctp", + "webrtc-util", +] + +[[package]] +name = "webrtc-dtls" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c9b89fc909f9da0499283b1112cd98f72fec28e55a54a9e352525ca65cd95c" +dependencies = [ + "aes", + "aes-gcm", + "async-trait", + "bincode", + "byteorder", + "cbc", + "ccm", + "der-parser 9.0.0", + "hkdf", + "hmac", + "log", + "p256", + "p384", + "pem", + "portable-atomic", + "rand 0.8.5", + "rand_core 0.6.4", + "rcgen", + "ring", + "rustls", + "sec1", + "serde", + "sha1", + "sha2", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", + "x25519-dalek", + "x509-parser 0.16.0", +] + +[[package]] +name = "webrtc-ice" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348b28b593f7709ac98d872beb58c0009523df652c78e01b950ab9c537ff17d" +dependencies = [ + "arc-swap", + "async-trait", + "crc", + "log", + "portable-atomic", + "rand 0.8.5", + "serde", + "serde_json", + "stun", + "thiserror 1.0.69", + "tokio", + "turn", + "url", + "uuid", + "waitgroup", + "webrtc-mdns", + "webrtc-util", +] + +[[package]] +name = "webrtc-mdns" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dfe9686c6c9c51428da4de415cb6ca2dc0591ce2b63212e23fd9cccf0e316b" +dependencies = [ + "log", + "socket2 0.5.10", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-media" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e153be16b8650021ad3e9e49ab6e5fa9fb7f6d1c23c213fd8bbd1a1135a4c704" +dependencies = [ + "byteorder", + "bytes", + "rand 0.8.5", + "rtp", + "thiserror 1.0.69", +] + +[[package]] +name = "webrtc-sctp" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5faf3846ec4b7e64b56338d62cbafe084aa79806b0379dff5cc74a8b7a2b3063" +dependencies = [ + "arc-swap", + "async-trait", + "bytes", + "crc", + "log", + "portable-atomic", + "rand 0.8.5", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-srtp" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771db9993712a8fb3886d5be4613ebf27250ef422bd4071988bf55f1ed1a64fa" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "byteorder", + "bytes", + "ctr", + "hmac", + "log", + "rtcp", + "rtp", + "sha1", + "subtle", + "thiserror 1.0.69", + "tokio", + "webrtc-util", +] + +[[package]] +name = "webrtc-util" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1438a8fd0d69c5775afb4a71470af92242dbd04059c61895163aa3c1ef933375" +dependencies = [ + "async-trait", + "bitflags 1.3.2", + "bytes", + "ipnet", + "lazy_static", + "libc", + "log", + "nix", + "portable-atomic", + "rand 0.8.5", + "thiserror 1.0.69", + "tokio", + "winapi", +] + +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc5cf48f83140dcaab716eeaea345f9e93d0018fb81162753a3f76c3397b538" +dependencies = [ + "windows-core 0.53.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core 0.57.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc5b895a6377f1ab9fa55acedab1fd5ac0db66ad1e6c7f47e28a22e446a5dd" +dependencies = [ + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement 0.57.0", + "windows-interface 0.57.0", + "windows-result 0.1.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link 0.1.3", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + +[[package]] +name = "x509-parser" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" +dependencies = [ + "asn1-rs 0.6.2", + "data-encoding", + "der-parser 9.0.0", + "lazy_static", + "nom", + "oid-registry 0.7.1", + "ring", + "rusticata-macros", + "thiserror 1.0.69", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs 0.7.1", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "rusticata-macros", + "thiserror 2.0.16", + "time", +] + +[[package]] +name = "xml-rs" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" + +[[package]] +name = "xmltree" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" +dependencies = [ + "xml-rs", +] + +[[package]] +name = "yamux" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed0164ae619f2dc144909a9f082187ebb5893693d8c0196e8085283ccd4b776" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.8.5", + "static_assertions", +] + +[[package]] +name = "yamux" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2dd50a6d6115feb3e5d7d0efd45e8ca364b6c83722c1e9c602f5764e0e9597" +dependencies = [ + "futures", + "log", + "nohash-hasher", + "parking_lot", + "pin-project", + "rand 0.9.2", + "static_assertions", + "web-time", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2e9bf88 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "ghost-echo" +version = "0.0.1" +edition = "2021" + +[[bin]] +name = "ghost-gossiper" +path = "src/bin/main.rs" + +[dependencies] +anyhow = "1.0.97" +async-trait = "0.1.88" +chrono = "0.4.42" +clap = { version = "4.5.32", features = ["derive", "env"] } +crossterm = "0.28.1" +futures = "0.3.31" +futures-timer = "3.0.3" +hex = "0.4.3" +libp2p = { version = "0.56", features = ["identify", "ping", "tokio", "gossipsub", "macros", "relay", "kad", "rsa", "ed25519", "quic", "request-response", "dns", "memory-connection-limits", "tcp", "noise", "yamux", "autonat", "tls", "dcutr"] } +libp2p-webrtc = { version = "0.9.0-alpha", features = ["tokio", "pem"] } +quick-protobuf = "0.8.1" +rand = "0.9.0" +rand_core = { version = "0.6.4", features = ["getrandom"] } +ratatui = "0.29.0" +serde_json = "1.0.140" +signal-hook = "0.3.17" +tokio = { version = "1.47.1", features = ["full"] } +tokio-util = { version = "0.7.14", features = ["full"] } +tracing = "0.1.41" +tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } +unsigned-varint = "0.8.0" diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..730ac89 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,110 @@ +use ghost_gossiper::prelude::*; + +use anyhow::Result; +use clap::Parser; +use libp2p::{identity, PeerId}; +use libp2p_webrtc::tokio::Certificate; +use std::path::{Path, PathBuf}; +use tokio::{fs, task::JoinHandle}; +use tokio_util::sync::CancellationToken; +use tracing::info; + +#[tokio::main] +async fn main() -> Result<()> { + // parse the command line arguments + let opt = Options::parse(); + + // initialize the tracing logger and get the receiver for log messages + let from_log = Log::init(); + + // create a shutdown token + let shutdown = CancellationToken::new(); + + // load the identity and certificate + let local_key = read_or_create_identity(&opt.local_key_path).await?; + let webrtc_cert = read_or_create_certificate(&opt.local_cert_path).await?; + + // create the ui and the channels to communicate with it + let (mut ui, to_ui, from_ui) = if opt.headless { + Headless::build(local_key.public().into(), from_log, shutdown.clone()) + } else { + Tui::build(local_key.public().into(), from_log, shutdown.clone()) + }; + + // create the peer, connecting it to the ui + let mut peer = Peer::new(local_key, webrtc_cert, to_ui, from_ui, shutdown.clone()).await?; + + // spawn tasks for both the swarm and the ui + let peer_task: JoinHandle> = tokio::spawn(async move { peer.run().await }); + let ui_task: JoinHandle> = tokio::spawn(async move { ui.run().await }); + + // wait for the tasks to finish + let (ui_result, peer_result) = tokio::try_join!(peer_task, ui_task)?; + + // check the inner results + ui_result?; + peer_result?; + + Ok(()) +} + +async fn read_or_create_certificate(path: &Path) -> Result { + if path.exists() { + let pem = fs::read_to_string(&path).await?; + + info!("Using existing certificate from {}", path.display()); + + return Ok(Certificate::from_pem(&pem)?); + } + + let cert = Certificate::generate(&mut rand_core::OsRng)?; + fs::write(&path, &cert.serialize_pem().as_bytes()).await?; + + info!( + "Generated new certificate and wrote it to {}", + path.display() + ); + + Ok(cert) +} + +async fn read_or_create_identity(path: &Path) -> Result { + let mut key_path = PathBuf::from(path); + let is_key = key_path + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext == "key") + .unwrap_or(false); + if !is_key { + key_path.set_extension("key"); + } + + let mut peer_id_path = PathBuf::from(path); + let is_peer_id = peer_id_path + .extension() + .and_then(|ext| ext.to_str()) + .map(|ext| ext == "peerid") + .unwrap_or(false); + if !is_peer_id { + peer_id_path.set_extension("peerid"); + } + + if key_path.exists() { + let bytes = fs::read(&key_path).await?; + info!("Using existing identity from {}", key_path.display()); + // This only works for ed25519 but that is what we are using + return Ok(identity::Keypair::from_protobuf_encoding(&bytes)?); + } + + let identity = identity::Keypair::generate_ed25519(); + fs::write(&key_path, &identity.to_protobuf_encoding()?).await?; + let peer_id: PeerId = identity.public().into(); + fs::write(&peer_id_path, peer_id.to_string()).await?; + + info!( + "Generated new identity and wrote it to {}", + key_path.display() + ); + + Ok(identity) +} diff --git a/src/chatpeer.rs b/src/chatpeer.rs new file mode 100644 index 0000000..4d29970 --- /dev/null +++ b/src/chatpeer.rs @@ -0,0 +1,57 @@ +use libp2p::PeerId; +use std::fmt; + +/// A wrapper for PeerId for chat peers +/// TODO: expand this to include a user-set name, and possibly a user-set avatar +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct ChatPeer(PeerId); + +impl ChatPeer { + /// Get the peer id + pub fn id(&self) -> PeerId { + self.0 + } + + /// Get the peer name + pub fn name(&self) -> String { + short_id(&self.0) + } +} + +impl From for PeerId { + fn from(peer: ChatPeer) -> PeerId { + peer.0 + } +} + +impl From<&PeerId> for ChatPeer { + fn from(peer: &PeerId) -> Self { + ChatPeer(peer.to_owned()) + } +} + +impl From for ChatPeer { + fn from(peer: PeerId) -> Self { + ChatPeer(peer) + } +} + +impl fmt::Debug for ChatPeer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} ({})", &self.0, short_id(&self.0)) + } +} + +impl fmt::Display for ChatPeer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", short_id(&self.0)) + } +} + +// Get the last 8 characters of a PeerId +fn short_id(peer: &PeerId) -> String { + let s = peer.to_string(); + s.chars() + .skip(s.chars().count().saturating_sub(7)) + .collect() +} diff --git a/src/file_exchange.rs b/src/file_exchange.rs new file mode 100644 index 0000000..7a9750b --- /dev/null +++ b/src/file_exchange.rs @@ -0,0 +1,202 @@ +use async_trait::async_trait; +use futures::{io, AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; +use libp2p::{request_response, StreamProtocol}; + +// Simple file exchange protocol. The format that the peers support consists of two different +// messages, one to request a file and one to receive the file. +// +// To request a file a peer sends the varuint encoded length of the file id string followed by the +// file id string itself. +// +// Request: +// varuint - file id length +// bytes - file id +// +// The file response message consists of a varuint length followed by the contents of the file. +// +// Response: +// varuint - file contents length +// bytes - file contents +// + +/// The codec for the file exchange protocol. +#[derive(Default, Clone)] +pub struct Codec; + +/// The request message for the file exchange protocol. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Request { + /// The identifier of the file that is being requested. + pub file_id: String, +} + +/// The response message for the file exchange protocol. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Response { + /// The contents of the file that is being sent. + pub file_body: Vec, +} + +#[async_trait] +impl request_response::Codec for Codec { + type Protocol = StreamProtocol; + type Request = Request; + type Response = Response; + + async fn read_request(&mut self, _: &StreamProtocol, io: &mut T) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + let vec = read_length_prefixed(io, 1_000_000).await?; + + if vec.is_empty() { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + + Ok(Request { + file_id: String::from_utf8(vec).unwrap(), + }) + } + + async fn read_response( + &mut self, + _: &StreamProtocol, + io: &mut T, + ) -> io::Result + where + T: AsyncRead + Unpin + Send, + { + let vec = read_length_prefixed(io, 500_000_000).await?; // update transfer maximum + + if vec.is_empty() { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + + Ok(Response { file_body: vec }) + } + + async fn write_request( + &mut self, + _: &StreamProtocol, + io: &mut T, + Request { file_id }: Request, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + write_length_prefixed(io, file_id).await?; + + Ok(()) + } + + async fn write_response( + &mut self, + _: &StreamProtocol, + io: &mut T, + Response { file_body }: Response, + ) -> io::Result<()> + where + T: AsyncWrite + Unpin + Send, + { + write_length_prefixed(io, file_body).await?; + + Ok(()) + } +} + +/// Writes a message to the given socket with a length prefix appended to it. Also flushes the socket. +/// +/// > **Note**: Prepends a variable-length prefix indicate the length of the message. This is +/// > compatible with what [`read_length_prefixed`] expects. +pub async fn write_length_prefixed( + socket: &mut (impl AsyncWrite + Unpin), + data: impl AsRef<[u8]>, +) -> Result<(), io::Error> { + write_varint(socket, data.as_ref().len()).await?; + socket.write_all(data.as_ref()).await?; + socket.flush().await?; + + Ok(()) +} + +/// Writes a variable-length integer to the `socket`. +/// +/// > **Note**: Does **NOT** flush the socket. +pub async fn write_varint( + socket: &mut (impl AsyncWrite + Unpin), + len: usize, +) -> Result<(), io::Error> { + let mut len_data = unsigned_varint::encode::usize_buffer(); + let encoded_len = unsigned_varint::encode::usize(len, &mut len_data).len(); + socket.write_all(&len_data[..encoded_len]).await?; + + Ok(()) +} + +/// Reads a variable-length integer from the `socket`. +/// +/// As a special exception, if the `socket` is empty and EOFs right at the beginning, then we +/// return `Ok(0)`. +/// +/// > **Note**: This function reads bytes one by one from the `socket`. It is therefore encouraged +/// > to use some sort of buffering mechanism. +async fn read_varint(socket: &mut (impl AsyncRead + Unpin)) -> Result { + let mut buffer = unsigned_varint::encode::usize_buffer(); + let mut buffer_len = 0; + + loop { + match socket.read(&mut buffer[buffer_len..buffer_len + 1]).await? { + 0 => { + // Reaching EOF before finishing to read the length is an error, unless the EOF is + // at the very beginning of the substream, in which case we assume that the data is + // empty. + if buffer_len == 0 { + return Ok(0); + } else { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + } + n => debug_assert_eq!(n, 1), + } + + buffer_len += 1; + + match unsigned_varint::decode::usize(&buffer[..buffer_len]) { + Ok((len, _)) => return Ok(len), + Err(unsigned_varint::decode::Error::Overflow) => { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "overflow in variable-length integer", + )); + } + // TODO: why do we have a `__Nonexhaustive` variant in the error? I don't know how to process it + // Err(unsigned_varint::decode::Error::Insufficient) => {} + Err(_) => {} + } + } +} + +/// Reads a length-prefixed message from the given socket. +/// +/// The `max_size` parameter is the maximum size in bytes of the message that we accept. This is +/// necessary in order to avoid DoS attacks where the remote sends us a message of several +/// gigabytes. +/// +/// > **Note**: Assumes that a variable-length prefix indicates the length of the message. This is +/// > compatible with what [`write_length_prefixed`] does. +async fn read_length_prefixed( + socket: &mut (impl AsyncRead + Unpin), + max_size: usize, +) -> io::Result> { + let len = read_varint(socket).await?; + if len > max_size { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("Received data size ({len} bytes) exceeds maximum ({max_size} bytes)"), + )); + } + let mut buf = vec![0; len]; + socket.read_exact(&mut buf).await?; + + Ok(buf) +} diff --git a/src/generated/mod.rs b/src/generated/mod.rs new file mode 100644 index 0000000..6cae45b --- /dev/null +++ b/src/generated/mod.rs @@ -0,0 +1,2 @@ +// Automatically generated mod.rs +pub mod peer; diff --git a/src/generated/peer.proto b/src/generated/peer.proto new file mode 100644 index 0000000..dec0e19 --- /dev/null +++ b/src/generated/peer.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package peer; + +message Peer { + // public key of the peer + bytes publicKey = 1; + // array of multiaddrs for the peer + repeated bytes multiAddrs = 2; +} diff --git a/src/generated/peer.rs b/src/generated/peer.rs new file mode 100644 index 0000000..11b93a2 --- /dev/null +++ b/src/generated/peer.rs @@ -0,0 +1,52 @@ +// Automatically generated rust module for 'peer.proto' file + +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(unused_imports)] +#![allow(unknown_lints)] +#![allow(clippy::all)] +#![cfg_attr(rustfmt, rustfmt_skip)] + + +use std::borrow::Cow; +use quick_protobuf::{MessageInfo, MessageRead, MessageWrite, BytesReader, Writer, WriterBackend, Result}; +use quick_protobuf::sizeofs::*; +use super::*; + +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Peer<'a> { + pub publicKey: Cow<'a, [u8]>, + pub multiAddrs: Vec>, +} + +impl<'a> MessageRead<'a> for Peer<'a> { + fn from_reader(r: &mut BytesReader, bytes: &'a [u8]) -> Result { + let mut msg = Self::default(); + while !r.is_eof() { + match r.next_tag(bytes) { + Ok(10) => msg.publicKey = r.read_bytes(bytes).map(Cow::Borrowed)?, + Ok(18) => msg.multiAddrs.push(r.read_bytes(bytes).map(Cow::Borrowed)?), + Ok(t) => { r.read_unknown(bytes, t)?; } + Err(e) => return Err(e), + } + } + Ok(msg) + } +} + +impl<'a> MessageWrite for Peer<'a> { + fn get_size(&self) -> usize { + 0 + + if self.publicKey == Cow::Borrowed(b"") { 0 } else { 1 + sizeof_len((&self.publicKey).len()) } + + self.multiAddrs.iter().map(|s| 1 + sizeof_len((s).len())).sum::() + } + + fn write_message(&self, w: &mut Writer) -> Result<()> { + if self.publicKey != Cow::Borrowed(b"") { w.write_with_tag(10, |w| w.write_bytes(&**&self.publicKey))?; } + for s in &self.multiAddrs { w.write_with_tag(18, |w| w.write_bytes(&**s))?; } + Ok(()) + } +} + diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..75eefc6 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,55 @@ +//! rust-libp2p-webrtc-peer crate +#![warn(missing_docs)] +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications +)] + +/// The chat peer module +pub mod chatpeer; +pub use chatpeer::ChatPeer; + +/// The peer file transfer protocol +pub mod file_exchange; +pub use file_exchange::{Codec, Request, Response}; + +/// The peer logging module +pub mod log; +pub use log::Log; + +/// The peer message module +pub mod message; +pub use message::Message; + +/// The command line options module +pub mod options; +pub use options::Options; + +/// The peer module +pub mod peer; +pub use peer::Peer; + +/// The protobuf generated module +mod proto { + #![allow(unreachable_pub)] + include!("generated/mod.rs"); + pub(crate) use self::peer::Peer; +} + +/// The peer ui module +pub mod ui; +pub use ui::{Headless, Tui, Ui}; + +/// The misc util module +pub mod util; +pub use util::{ + decode_unknown_protobuf, extract_ip_multiaddr, ipaddr_to_multiaddr, is_private_ip, + pretty_print_fields, split_peer_id, WireType, +}; + +/// Prelude module +pub mod prelude { + pub use super::*; +} diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..ebb4257 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,71 @@ +use std::fmt; +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tracing::{ + field::{Field, Visit}, + Event, Level, Subscriber, +}; +use tracing_subscriber::{ + filter::EnvFilter, layer::Context, prelude::*, registry::LookupSpan, Layer, +}; + +// Custom tracing layer to send log events over mpsc +struct MpscLayer { + sender: Sender, +} + +/// Custom tracing event that is send and sync +#[derive(Clone, Debug)] +pub struct Message { + /// The log level of the event + pub level: Level, + /// The log message of the event + pub message: String, +} + +// Implement a visitor to extract fields from the event +struct FieldVisitor { + message: Option, +} + +impl Visit for FieldVisitor { + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + if field.name() == "message" { + self.message = Some(format!("{:?}", value)); + } + } +} + +impl Layer for MpscLayer +where + S: Subscriber + for<'a> LookupSpan<'a>, +{ + fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) { + let mut visitor = FieldVisitor { message: None }; + event.record(&mut visitor); + + let event_data = Message { + level: *event.metadata().level(), + message: visitor.message.unwrap_or_default(), + }; + + let _ = self.sender.try_send(event_data); + } +} + +/// Async tracing logger wrapper that filters and feeds log messages over an mpsc channel for +/// integration into the TUI gui. +pub struct Log; + +impl Log { + /// Starts the logger and returns the task handle and receiver for the log messages. + pub fn init() -> Receiver { + let (sender, receiver) = mpsc::channel(16); + + let filter = EnvFilter::from_default_env(); + let layer = MpscLayer { sender }.with_filter(filter); + + tracing_subscriber::registry().with(layer).init(); + + receiver + } +} diff --git a/src/message.rs b/src/message.rs new file mode 100644 index 0000000..2228d59 --- /dev/null +++ b/src/message.rs @@ -0,0 +1,25 @@ +use crate::ChatPeer; +use libp2p::core::PeerId; + +/// The different types of messages sent between the UI and the Peer +#[derive(Debug)] +pub enum Message { + /// Send chat message + Chat { + /// The peer sending the message + from: Option, + /// The message sent + data: Vec, + }, + /// All gossipsub peers and their topics + AllPeers { + /// The peers and their topics + peers: Vec<(PeerId, Vec)>, + }, + /// Add a peer + AddPeer(ChatPeer), + /// Remove a peer + RemovePeer(ChatPeer), + /// Add an event message + Event(String), +} diff --git a/src/options.rs b/src/options.rs new file mode 100644 index 0000000..ef5605c --- /dev/null +++ b/src/options.rs @@ -0,0 +1,71 @@ +use clap::Parser; +use std::{net::IpAddr, path::PathBuf}; + +const LISTEN_ADDR: [&str; 1] = ["0.0.0.0"]; +const LOCAL_KEY_PATH: &str = "./local"; +const LOCAL_CERT_PATH: &str = "./cert.pem"; + +/// The rust peer command line options +#[derive(Debug, Parser)] +#[clap(name = "ghost-gossiper p2p messenger")] +pub struct Options { + /// WebRTC UDP port for the app. + #[clap(long, env, default_value = "0")] + pub webrtc_port: u16, + + /// QUIC UDP port for the app. + #[clap(long, env, default_value = "0")] + pub quic_port: u16, + + /// TCP port for the app. + #[clap(long, env, default_value = "0")] + pub tcp_port: u16, + + /// Address to listen on. + #[clap(long, env, action = clap::ArgAction::Append, value_delimiter = ',', default_values = LISTEN_ADDR)] + pub listen_addresses: Vec, + + /// If known, the external address of this node. Will be used to correctly advertise our external address across all transports. + #[clap(long, env, action = clap::ArgAction::Append, value_delimiter = ',')] + pub external_addresses: Vec, + + /// Nodes to connect to on startup. Can be specified several times. + #[clap(long, env, action = clap::ArgAction::Append, value_delimiter = ',')] + pub connect: Vec, + + /// If set, the path to the local certificate file. + #[clap(long, env, default_value = LOCAL_CERT_PATH)] + pub local_cert_path: PathBuf, + + /// If set, the path to the local key file. + #[clap(long, env, default_value = LOCAL_KEY_PATH)] + pub local_key_path: PathBuf, + + /// If set, the peer will make autonat client requests (default: true) + #[clap(long, env, default_value = "true")] + pub autonat_client: bool, + + /// If set, the peer will act as an autonat server + #[clap(long, env)] + pub autonat_server: bool, + + /// If set, the peer will try to upgrade connections using DCUtR (default: true) + #[clap(long, env, default_value = "true")] + pub dcutr: bool, + + /// If set, the peer will not initialize the TUI and will run headless. + #[clap(long, env)] + pub headless: bool, + + /// If set, the peer will use kademlia (default: true) + #[clap(long, env, default_value = "true")] + pub kademlia: bool, + + /// If set, the peer will support relay client connections (default: true) + #[clap(long, env, default_value = "true")] + pub relay_client: bool, + + /// If set, the peer will act as a relay server + #[clap(long, env)] + pub relay_server: bool, +} diff --git a/src/peer.rs b/src/peer.rs new file mode 100644 index 0000000..1b51bc7 --- /dev/null +++ b/src/peer.rs @@ -0,0 +1,1114 @@ +use crate::{ + decode_unknown_protobuf, ipaddr_to_multiaddr, is_private_ip, pretty_print_fields, + proto::Peer as DiscoveredPeer, split_peer_id, ChatPeer, Codec as FileExchangeCodec, Message, + Options, Request as FileRequest, +}; +use clap::Parser; +use futures::StreamExt; +use libp2p::{ + autonat::{ + v2::client::{ + Behaviour as AutonatClient, Config as AutonatClientConfig, Event as AutonatClientEvent, + }, + v2::server::{Behaviour as AutonatServer, Event as AutonatServerEvent}, + }, + connection_limits::{self, Behaviour as ConnectionLimits}, + dcutr::{Behaviour as Dcutr, Event as DcutrEvent}, + gossipsub::{ + self, Behaviour as Gossipsub, Event as GossipsubEvent, IdentTopic as GossipsubIdentTopic, + Message as GossipsubMessage, MessageId as GossipsubMessageId, TopicHash, + }, + identify::{Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent}, + identity::{self, PublicKey}, + kad::{ + store::MemoryStore, AddProviderOk, Behaviour as Kademlia, Config as KademliaConfig, + Event as KademliaEvent, GetClosestPeersOk, GetProvidersOk, QueryId, QueryResult, RecordKey, + }, + memory_connection_limits::Behaviour as MemoryConnectionLimits, + multiaddr::{Multiaddr, Protocol}, + noise::Config as NoiseConfig, + relay::{ + client::{Behaviour as RelayClient, Event as RelayClientEvent}, + Behaviour as RelayServer, Config as RelayServerConfig, Event as RelayServerEvent, + }, + request_response::{ + Behaviour as RequestResponse, Config as RequestResponseConfig, + Event as RequestResponseEvent, Message as RequestResponseMessage, ProtocolSupport, + }, + swarm::{behaviour::toggle::Toggle, NetworkBehaviour, Swarm, SwarmEvent}, + tcp::Config as TcpConfig, + tls::Config as TlsConfig, + yamux::Config as YamuxConfig, + PeerId, StreamProtocol, SwarmBuilder, +}; +use libp2p_webrtc as webrtc; +use libp2p_webrtc::tokio::Certificate; +use quick_protobuf::{BytesReader, MessageRead}; +use rand_core::OsRng; +use std::{ + collections::{hash_map::DefaultHasher, HashSet}, + fmt::{self, Write}, + hash::{Hash, Hasher}, + time::Duration, +}; +use tokio::sync::mpsc::{Receiver, Sender}; +use tokio_util::sync::CancellationToken; +use tracing::{debug, error, info, warn}; + +// Universal connectivity agent string +const UNIVERSAL_CONNECTIVITY_AGENT: &str = "universal-connectivity/0.1.0"; + +// Protocol Names +const IPFS_KADEMLIA_PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/kad/1.0.0"); +const IPFS_IDENTIFY_PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/ipfs/id/1.0.0"); +const FILE_EXCHANGE_PROTOCOL_NAME: StreamProtocol = + StreamProtocol::new("/universal-connectivity-file/1"); + +// Gossipsub Topics +const GOSSIPSUB_CHAT_TOPIC: &str = "universal-connectivity"; +const GOSSIPSUB_CHAT_FILE_TOPIC: &str = "universal-connectivity-file"; +const GOSSIPSUB_PEER_DISCOVERY: &str = "universal-connectivity-browser-peer-discovery"; + +// Kademlia bootstrap interval +const KADEMLIA_BOOTSTRAP_INTERVAL: u64 = 300; +const IPFS_BOOTSTRAP_NODES: [&str; 4] = [ + "/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", + "/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt", +]; + +/// The Peer Behaviour +#[derive(NetworkBehaviour)] +struct Behaviour { + autonat_client: Toggle, + autonat_server: Toggle, + connection_limits: ConnectionLimits, + dcutr: Toggle, + gossipsub: Gossipsub, + identify: Identify, + kademlia: Toggle>, + memory_connection_limits: MemoryConnectionLimits, + relay_client: Toggle, + relay_server: Toggle, + request_response: RequestResponse, +} + +/// The Peer state +pub struct Peer { + /// The addresses we're listening on + listen_addresses: HashSet, + /// The external addresses that others see, given on command line + external_addresses: HashSet, + /// The multiaddrs to dial, given on command line + to_dial: Vec, + /// The sender to the ui + to_ui: Sender, + /// The receiver from the ui + from_ui: Receiver, + /// The shutdown token + shutdown: CancellationToken, + /// The swarm itself + swarm: Swarm, + /// The query id for the kademlia bootstrap + bootstrap_query_id: Option, + /// The query id for providing the universal connectivity agent string + start_providing_query_id: Option, + /// The query id for getting the providers of the universal connectivity agent string + get_providers_query_id: Option, + /// The query id for getting the closest peers to the universal connectivity agent string + get_closest_peers_query_id: HashSet, +} + +impl Peer { + /// Create a new Peer instance by initializing the swarm and peer state + pub async fn new( + keypair: identity::Keypair, + tls_cert: Certificate, + to_ui: Sender, + from_ui: Receiver, + shutdown: CancellationToken, + ) -> anyhow::Result { + // parse the command line arguments + let opt = Options::parse(); + + let mut listen_addresses = HashSet::new(); + for addr in opt.listen_addresses.iter() { + // add the WebRTC address + listen_addresses.insert( + ipaddr_to_multiaddr(addr) + .with(Protocol::Udp(opt.webrtc_port)) + .with(Protocol::WebRTCDirect), + ); + // add the QUIC address + listen_addresses.insert( + ipaddr_to_multiaddr(addr) + .with(Protocol::Udp(opt.quic_port)) + .with(Protocol::QuicV1), + ); + // add the TCP address + listen_addresses.insert(ipaddr_to_multiaddr(addr).with(Protocol::Tcp(opt.tcp_port))); + } + + let mut external_addresses = HashSet::new(); + for addr in opt.external_addresses.iter() { + external_addresses.insert(ipaddr_to_multiaddr(addr)); + } + + // keep them as Strings because they can be PeerId's or Multiaddr's + let to_dial = opt.connect; + + // initialize the swarm + let swarm = { + let local_peer_id = PeerId::from(keypair.public()); + debug!("Local peer id: {local_peer_id}"); + + // Initialize the autonat client behaviour + let autonat_client = if opt.autonat_client { + let cfg = AutonatClientConfig::default(); + Some(AutonatClient::new(OsRng, cfg)) + } else { + None + } + .into(); + + // Initialize the autonat server behaviour + let autonat_server = if opt.autonat_server { + Some(AutonatServer::new(OsRng)) + } else { + None + } + .into(); + + // Create the ConnectionLimits behaviour + let connection_limits = { + let cfg = connection_limits::ConnectionLimits::default() + .with_max_pending_incoming(Some(100)) + .with_max_pending_outgoing(Some(100)) + .with_max_established_per_peer(Some(10)) + .with_max_established(Some(1000)); + ConnectionLimits::new(cfg) + }; + + // Create the Dcutr behaviour + let dcutr = if opt.dcutr { + Some(Dcutr::new(local_peer_id)) + } else { + None + } + .into(); + + // Create a gossipsub behaviour + let gossipsub = { + // This closure creates a unique message id for each message by hashing its contents + let message_id_fn = |message: &GossipsubMessage| { + let mut s = DefaultHasher::new(); + message.data.hash(&mut s); + GossipsubMessageId::from(s.finish().to_string()) + }; + + // Set a custom gossipsub configuration + let gossipsub_config = gossipsub::ConfigBuilder::default() + // This sets the kind of message validation. The default is Strict (enforce message signing) + .validation_mode(gossipsub::ValidationMode::Permissive) + // This ensures no two messages of the same content will be propagated. + .message_id_fn(message_id_fn) + .mesh_outbound_min(1) + .mesh_n_low(1) + .flood_publish(true) + .build() + .expect("Valid config"); + + // build a gossipsub network behaviour + Gossipsub::new( + gossipsub::MessageAuthenticity::Signed(keypair.clone()), + gossipsub_config, + ) + .expect("Correct configuration") + }; + + // Create an Identify behaviour + let identify = { + let cfg = IdentifyConfig::new( + IPFS_IDENTIFY_PROTOCOL_NAME.to_string(), // bug: https://github.com/libp2p/rust-libp2p/issues/5940 + keypair.public(), + ) + .with_agent_version(UNIVERSAL_CONNECTIVITY_AGENT.to_string()); + Identify::new(cfg) + }; + + // Create a Kademlia behaviour + let kademlia: Toggle> = if opt.kademlia { + let mut cfg = KademliaConfig::new(IPFS_KADEMLIA_PROTOCOL_NAME); + cfg.set_query_timeout(Duration::from_secs(60)); + cfg.set_periodic_bootstrap_interval(Some(Duration::from_secs( + KADEMLIA_BOOTSTRAP_INTERVAL, + ))); + let store = MemoryStore::new(local_peer_id); + Some(Kademlia::with_config(local_peer_id, store, cfg)) + } else { + None + } + .into(); + + // Create the MemoryConnectionLimits behaviour + let memory_connection_limits = MemoryConnectionLimits::with_max_percentage(0.9); + + // Create the RelayServer behaviour + let relay_server = if opt.relay_server { + let cfg = RelayServerConfig { + max_reservations: usize::MAX, + max_reservations_per_peer: 100, + reservation_rate_limiters: Vec::default(), + circuit_src_rate_limiters: Vec::default(), + max_circuits: usize::MAX, + max_circuits_per_peer: 100, + ..Default::default() + }; + Some(RelayServer::new(local_peer_id, cfg)) + } else { + None + } + .into(); + + // Create the RequestResponse behaviour + let request_response = { + let cfg = RequestResponseConfig::default(); + RequestResponse::new([(FILE_EXCHANGE_PROTOCOL_NAME, ProtocolSupport::Full)], cfg) + }; + + // Initialize the overall peer behaviour + let mut behaviour = Behaviour { + autonat_client, + autonat_server, + connection_limits, + dcutr, + gossipsub, + identify, + kademlia, + memory_connection_limits, + relay_client: None.into(), + relay_server, + request_response, + }; + + // Build the swarm + let sb = SwarmBuilder::with_existing_identity(keypair.clone()) + .with_tokio() + .with_tcp( + TcpConfig::new().nodelay(true), + (TlsConfig::new, NoiseConfig::new), // passes the keypair to the constructors + YamuxConfig::default, + )? + .with_quic() + .with_other_transport(|id_keys| { + Ok(webrtc::tokio::Transport::new( + id_keys.clone(), + tls_cert.clone(), + )) + })? + .with_dns()?; + + // if we are to be a relay client, add the relay client behaviour + if opt.relay_client { + sb.with_relay_client((TlsConfig::new, NoiseConfig::new), YamuxConfig::default)? + .with_behaviour(|_key, relay_client| { + behaviour.relay_client = Some(relay_client).into(); + behaviour + })? + .build() + } else { + sb.with_behaviour(|_key| behaviour)?.build() + } + }; + + Ok(Self { + listen_addresses, + external_addresses, + to_dial, + to_ui, + from_ui, + shutdown, + swarm, + bootstrap_query_id: None, + start_providing_query_id: None, + get_providers_query_id: None, + get_closest_peers_query_id: HashSet::new(), + }) + } + + /// Send a message to the UI + pub async fn msg(&mut self, msg: impl ToString) -> anyhow::Result<()> { + self.to_ui.send(Message::Event(msg.to_string())).await?; + Ok(()) + } + + /// Update our external address if needed + pub async fn update_external_address(&mut self, address: &Multiaddr) -> anyhow::Result { + if !is_private_ip(address) && self.external_addresses.insert(address.clone()) { + self.msg(format!("Adding external address: {address}")) + .await?; + self.swarm.add_external_address(address.clone()); + return Ok(true); + } + Ok(false) + } + + /// Run the Peer + pub async fn run(&mut self) -> anyhow::Result<()> { + // Listen on the given addresses + let addrs: Vec = self.listen_addresses.iter().cloned().collect(); + for addr in addrs.iter() { + if let Err(e) = self.swarm.listen_on(addr.clone()) { + self.msg(format!("Failed to listen on {addr}: {e}")).await?; + } + } + + // Set the external address if passed in + let addrs: Vec = self.external_addresses.drain().collect(); + for addr in addrs.iter() { + self.update_external_address(addr).await?; + } + + // Dial the given addresses...they can be PeerId's or Multiaddr's + for addr in self.to_dial.clone().iter() { + if let Ok(addr) = addr.parse::() { + // attempt to dial the address + if let Err(e) = self.swarm.dial(addr.clone()) { + self.msg(format!("Failed to dial {addr}: {e}")).await?; + } else { + self.msg(format!("Dialed {addr}")).await?; + } + + // add the address to the kademlia routing table if it is enabled + if let Some((multiaddr, peerid)) = split_peer_id(addr) { + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + kad.add_address(&peerid, multiaddr); + } + } + } else if let Ok(addr) = addr.parse::() { + // attempt to dial the address + if let Err(e) = self.swarm.dial(addr) { + self.msg(format!("Failed to dial {addr}: {e}")).await?; + } else { + self.msg(format!("Dialed {addr}")).await?; + } + } else { + self.msg(format!("Failed to parse {addr}")).await?; + } + } + + // initiate a bootstrap of kademlia if it is enabled + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + // parse the bootstrap multiaddrs + let bootstrappers: Vec = IPFS_BOOTSTRAP_NODES + .iter() + .filter_map(|s| s.parse().ok()) + .collect(); + for addr in bootstrappers.iter() { + if let Some((multiaddr, peerid)) = split_peer_id(addr.clone()) { + kad.add_address(&peerid, multiaddr); + } + } + + // start the bootstrap process + match kad.bootstrap() { + Ok(query_id) => { + self.bootstrap_query_id = Some(query_id); + self.msg("Bootstrapping Kademlia").await?; + } + Err(e) => { + self.msg(format!("Failed to bootstrap Kademlia: {e}")) + .await?; + self.msg(format!( + "Don't worry, it will try again in {KADEMLIA_BOOTSTRAP_INTERVAL} seconds" + )) + .await?; + } + } + } + + // Initialize the gossipsub topics, the hashes are the same as the topic names + let chat_topic = GossipsubIdentTopic::new(GOSSIPSUB_CHAT_TOPIC); + let file_topic = GossipsubIdentTopic::new(GOSSIPSUB_CHAT_FILE_TOPIC); + let peer_discovery = GossipsubIdentTopic::new(GOSSIPSUB_PEER_DISCOVERY); + + // Subscribe to the gossipsub topics + info!("Subscribing to topics"); + for topic in [ + chat_topic.clone(), + file_topic.clone(), + peer_discovery.clone(), + ] { + if let Err(e) = self.swarm.behaviour_mut().gossipsub.subscribe(&topic) { + debug!("Failed to subscribe to topic {topic}: {e}"); + } + } + + // Create our loop ticker + let mut tick = tokio::time::interval(Duration::from_millis(18)); + + // Run the main loop + loop { + // process messages from the UI + if let Ok(message) = self.from_ui.try_recv() { + match message { + Message::Chat { data, .. } => { + error!("chat received"); + match self + .swarm + .behaviour_mut() + .gossipsub + .publish(chat_topic.hash(), data) + { + Err(e) => debug!("Failed to publish chat message: {e}"), + _ => self.msg("Sent chat message from you".to_string()).await?, + } + } + Message::AllPeers { .. } => { + error!("all peers received"); + let peers = self + .swarm + .behaviour() + .gossipsub + .all_peers() + .filter(|(_, topics)| !topics.is_empty()) + .map(|(peer_id, topics)| { + (*peer_id, topics.iter().map(|t| t.to_string()).collect()) + }) + .collect(); + self.to_ui.send(Message::AllPeers { peers }).await?; + } + _ => { + debug!("Unhandled message: {:?}", message); + } + } + } + + tokio::select! { + _ = self.shutdown.cancelled() => { + info!("Unsubscribing from topics"); + // Subscribe to the gossipsub topics + for topic in &[chat_topic, file_topic, peer_discovery] { + if !self.swarm.behaviour_mut().gossipsub.unsubscribe(topic) { + debug!("Failed to unsubscribe from topic {topic}"); + } + } + + info!("Shutting down the peer"); + break; + } + + _ = tick.tick() => {} + + Some(event) = self.swarm.next() => match event { + + // When the swarm in initiates a dial + SwarmEvent::Dialing { peer_id, .. } => { + let peer_id = peer_id.map_or("Unknown".to_string(), |peer_id| peer_id.to_string()); + debug!("Dialing {peer_id}"); + } + + // When we have confirmed our external address + SwarmEvent::ExternalAddrConfirmed { address } => { + let p2p_address = address + .clone() + .with(Protocol::P2p(*self.swarm.local_peer_id())); + self.msg(format!("Confirmed external address: {p2p_address}")).await?; + } + + // When we successfully listen on an address + SwarmEvent::NewListenAddr { address, .. } => { + let p2p_address = address + .clone() + .with(Protocol::P2p(*self.swarm.local_peer_id())); + self.msg(format!("Listening on {p2p_address}")) + .await?; + } + + // When we successfully connect to a peer + SwarmEvent::ConnectionEstablished { peer_id, .. } => { + debug!("Connected to {peer_id}"); + } + + // When we fail to connect to a peer + SwarmEvent::OutgoingConnectionError { peer_id, error, .. } => { + warn!("Failed to dial {peer_id:?}: {error}"); + } + + // When we fail to accept a connection from a peer + SwarmEvent::IncomingConnectionError { error, .. } => { + warn!("{:#}", anyhow::Error::from(error)) + } + + // When a connection to a peer is closed + SwarmEvent::ConnectionClosed { peer_id, cause, .. } => { + warn!("Connection to {peer_id} closed: {cause:?}"); + self.to_ui.send(Message::RemovePeer(peer_id.into())).await?; + + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + kad.remove_peer(&peer_id); + info!("Removed {peer_id} from the routing table (if it was in there)."); + } + } + + // When we receive an autonat client event + SwarmEvent::Behaviour(BehaviourEvent::AutonatClient(AutonatClientEvent { tested_addr, server, result, .. })) => { + let result = result.map(|_| "Ok".to_string()).unwrap_or_else(|e| e.to_string()); + debug!("NAT test to {tested_addr} with {server}: {result}"); + } + // When we receive an autonat server event + SwarmEvent::Behaviour(BehaviourEvent::AutonatServer(AutonatServerEvent { tested_addr, client, result, .. })) => { + let result = result.map(|_| "Ok".to_string()).unwrap_or_else(|e| e.to_string()); + self.msg(format!("NAT tested {tested_addr} to {client}: {result}")).await?; + } + + // When we receive a dcutr event + SwarmEvent::Behaviour(BehaviourEvent::Dcutr(DcutrEvent { remote_peer_id, result })) => { + let result = result.map(|_| "Ok".to_string()).unwrap_or_else(|e| e.to_string()); + self.msg(format!("Dcutr connection to {remote_peer_id}: {result}")).await?; + } + + // When we receive a gossipsub event + SwarmEvent::Behaviour(BehaviourEvent::Gossipsub(event)) => match event { + GossipsubEvent::Message { .. } => { + let msg = UniversalConnectivityMessage::try_from(event)?; + self.msg(format!("{msg}")).await?; + match msg { + UniversalConnectivityMessage::Chat { from, data, ..} => { + self.to_ui.send(Message::Chat{from, data}).await?; + if let Some(peer) = from { + self.to_ui.send(Message::AddPeer(peer)).await?; + } + } + UniversalConnectivityMessage::File { from, data, .. } => { + let file_id = String::from_utf8(data)?; + if let Some(peer) = from { + self.swarm.behaviour_mut().request_response.send_request( + &peer.into(), + FileRequest { + file_id: file_id.clone(), + }, + ); + self.msg(format!("Sent file request to {peer} for {file_id}")).await?; + } + } + UniversalConnectivityMessage::PeerDiscovery { discovered_peer, discovered_addrs, .. } => { + let mut msg = discovered_peer + .map_or("\tDialing: Unknown".to_string(), |discovered_peer| { + format!("\tDialing: {} ({})", discovered_peer.id(), discovered_peer) + }); + // attempt to dial the discovered peer + for addr in &discovered_addrs { + if let Err(e) = self.swarm.dial(addr.clone()) { + write!(msg, "\n\t\tError {e}").unwrap(); + } else { + write!(msg, "\n\t\t{addr}").unwrap(); + } + } + self.msg(msg).await?; + if let Some(peer) = discovered_peer { + self.to_ui.send(Message::AddPeer(peer)).await?; + } + } + _ => {} + } + } + GossipsubEvent::Subscribed { peer_id, topic } => { + debug!("{peer_id} subscribed to {topic}"); + if topic.as_str() == GOSSIPSUB_CHAT_TOPIC { + self.to_ui.send(Message::AddPeer(peer_id.into())).await?; + } + } + GossipsubEvent::Unsubscribed { peer_id, topic } => { + debug!("{peer_id} unsubscribed from {topic}"); + if topic.as_str() == GOSSIPSUB_CHAT_TOPIC { + self.to_ui.send(Message::RemovePeer(peer_id.into())).await?; + } + } + GossipsubEvent::GossipsubNotSupported { peer_id } => { + warn!("{peer_id} does not support gossipsub"); + } + GossipsubEvent::SlowPeer { peer_id, .. } => { + warn!("{peer_id} is a slow peer"); + } + } + + // When we receive an identify event + SwarmEvent::Behaviour(BehaviourEvent::Identify(event)) => match event { + IdentifyEvent::Received { info, .. } => { + //self.update_external_address(&info.observed_addr).await?; + if info.agent_version == UNIVERSAL_CONNECTIVITY_AGENT { + let peer_id: PeerId = info.public_key.into(); + let agent = format!("{} version: {}", info.agent_version, info.protocol_version); + let protocols = info.protocols.iter().map(|p| format!("\n\t\t{p}") ).collect::>().join(""); + self.msg(format!("Identify {peer_id}:\n\tagent: {agent}\n\tprotocols: {protocols}")).await?; + for addr in info.listen_addrs.iter() { + if !is_private_ip(addr) { + if let Err(e) = self.swarm.dial(addr.clone()) { + self.msg(format!("Failed to dial {addr}: {e}")).await?; + } + } + } + } + } + IdentifyEvent::Sent { .. } => { + debug!("identify::Event::Sent"); + } + IdentifyEvent::Pushed { .. } => { + debug!("identify::Event::Pushed"); + } + IdentifyEvent::Error { peer_id, error, .. } => { + match error { + libp2p::swarm::StreamUpgradeError::Timeout => { + // When a browser tab closes, we don't get a swarm event + // maybe there's a way to get this with TransportEvent + // but for now remove the peer from routing table if there's an Identify timeout + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + kad.remove_peer(&peer_id); + info!("Removed {peer_id} from the routing table (if it was in there)."); + } + self.to_ui.send(Message::RemovePeer(peer_id.into())).await?; + } + _ => { + debug!("{error}"); + } + } + } + } + + // When we receive a kademlia event + SwarmEvent::Behaviour(BehaviourEvent::Kademlia(event)) => match event { + KademliaEvent::OutboundQueryProgressed { id, result, step, .. } => match result { + QueryResult::Bootstrap(result) => { + if let Some(query_id) = self.bootstrap_query_id { + if id == query_id { + match result { + Ok(bootstrap) => { + if step.last { + self.bootstrap_query_id = None; + self.msg("Kademlia bootstrapped".to_string()).await?; + + let mut msgs = Vec::new(); + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + let key = RecordKey::new(&UNIVERSAL_CONNECTIVITY_AGENT); + // start providing the universal connectivity agent string + if let Ok(qid) = kad.start_providing(key.clone()) { + msgs.push(format!("Kademlia providing: {}", hex::encode(key.clone()))); + self.start_providing_query_id = Some(qid); + } + } + for msg in msgs.iter() { + self.msg(msg).await?; + } + } else { + self.msg(format!("Kademlia bootstrapping peer {}, remaining: {}", bootstrap.peer, bootstrap.num_remaining)).await?; + } + } + Err(e) => { + self.msg(format!("Failed to bootstrap Kademlia: {e}")).await?; + self.bootstrap_query_id = None; + } + } + } + } + } + QueryResult::GetClosestPeers(result) => { + if self.get_closest_peers_query_id.contains(&id) { + match result { + Ok(GetClosestPeersOk { peers, .. }) => { + //if step.last { + self.get_closest_peers_query_id.remove(&id); + self.msg(format!("Kademlia {} potential universal connectivity peers:", peers.len())).await?; + for peer in peers.iter().cloned() { + self.msg(format!("\t{}:", peer.peer_id)).await?; + for addr in peer.addrs.iter().take(1) { + self.msg(format!("\t\t{addr}")).await?; + } + } + /* + } else { + self.msg(format!("Kademlia getting closest peers: {}", peers.len())).await?; + } + */ + } + Err(e) => { + self.get_closest_peers_query_id.remove(&id); + self.msg(format!("Failed to get closest peers: {e}")).await?; + } + } + } + } + QueryResult::GetProviders(result) => { + if let Some(query_id) = self.get_providers_query_id { + if id == query_id { + match result { + Ok(GetProvidersOk::FoundProviders { providers, .. }) => { + //if step.last { + self.get_providers_query_id = None; + let mut msgs = Vec::new(); + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + let peers: Vec = providers.iter().cloned().collect(); + msgs.push(format!("Kademlia {} found providers", peers.len())); + for peer in peers.iter().cloned() { + self.get_closest_peers_query_id.insert(kad.get_closest_peers(peer)); + } + } + for msg in msgs.iter() { + self.msg(msg).await?; + } + /* + } else { + self.get_providers_query_id = None; + self.msg(format!("Kademlia found getting providers: {}", providers.len())).await?; + } + */ + } + Ok(GetProvidersOk::FinishedWithNoAdditionalRecord { closest_peers }) => { + //if step.last { + self.get_providers_query_id = None; + let mut msgs = Vec::new(); + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + msgs.push(format!("Kademlia {} found providers", closest_peers.len())); + for peer in closest_peers.iter().cloned() { + self.get_closest_peers_query_id.insert(kad.get_closest_peers(peer)); + } + } + for msg in msgs.iter() { + self.msg(msg).await?; + } + /* + } else { + self.get_providers_query_id = None; + self.msg(format!("Kademlia finished getting providers: {}", closest_peers.len())).await?; + } + */ + } + Err(e) => { + self.get_providers_query_id = None; + self.msg(format!("Failed to get providers of universal connectivity agent string: {e}")).await?; + + } + } + } + } + } + QueryResult::GetRecord(result) => match result { + Ok(_record) => { + self.msg("Kademlia record retrieved".to_string()).await?; + } + Err(e) => { + self.msg(format!("Failed to retrieve Kademlia record: {e}")).await?; + } + } + QueryResult::StartProviding(result) => { + if let Some(query_id) = self.start_providing_query_id { + if id == query_id { + match result { + Ok(AddProviderOk { key }) => { + if step.last { + self.start_providing_query_id = None; + self.msg("Kademlia provider registered".to_string()).await?; + if let Some(ref mut kad) = self.swarm.behaviour_mut().kademlia.as_mut() { + // query for the providers of the universal connectivity agent string + self.get_providers_query_id = Some(kad.get_providers(key.clone())); + } + self.msg(format!("Kademlia getting providers for: {}", hex::encode(key.clone()))).await?; + } else { + self.msg(format!("Kademlia adding provider record: {}", step.count)).await?; + } + } + Err(e) => { + self.start_providing_query_id = None; + self.msg(format!("Failed to start providing Kademlia record: {e}")).await?; + } + } + } + } + } + _ => {} + } + ref _other => {} + } + + // When we receive a relay client event + SwarmEvent::Behaviour(BehaviourEvent::RelayClient(event)) => match event { + RelayClientEvent::ReservationReqAccepted { relay_peer_id, renewal, limit } => { + self.msg(format!("Relay reservation request accepted:\n\tfrom: {relay_peer_id}\n\trenewed: {renewal}\n\tlimit: {limit:?}")).await?; + } + RelayClientEvent::OutboundCircuitEstablished { relay_peer_id, .. } => { + self.msg(format!("Outbound relay circuit established:\n\tto: {relay_peer_id}")).await?; + } + RelayClientEvent::InboundCircuitEstablished { src_peer_id, .. } => { + self.msg(format!("Inbound relay circuit established:\n\tfrom: {src_peer_id}")).await?; + } + } + + // When we receive a relay server event + SwarmEvent::Behaviour(BehaviourEvent::RelayServer(event)) => match event { + RelayServerEvent::ReservationReqAccepted { src_peer_id, renewed } => { + self.msg(format!("Relay reservation request accepted:\n\tfrom: {src_peer_id}\n\trenewed: {renewed}")).await?; + } + RelayServerEvent::ReservationReqDenied { src_peer_id, .. } => { + self.msg(format!("Relay reservation request denied: {src_peer_id}")).await?; + } + RelayServerEvent::ReservationTimedOut { src_peer_id } => { + self.msg(format!("Relay reservation timed out: {src_peer_id}")).await?; + } + RelayServerEvent::CircuitReqDenied { src_peer_id, dst_peer_id, .. } => { + self.msg(format!("Relay circuit request denied:\n\tfrom: {src_peer_id}\n\tto: {dst_peer_id}")).await?; + } + RelayServerEvent::CircuitReqAccepted { src_peer_id, dst_peer_id } => { + self.msg(format!("Relay circuit request accepted:\n\tfrom: {src_peer_id}\n\tto: {dst_peer_id}")).await?; + } + RelayServerEvent::CircuitClosed { src_peer_id, dst_peer_id, error } => { + self.msg(format!("Relay circuit closed:\n\tfrom: {src_peer_id}\n\tto: {dst_peer_id}\n\terror: {}", error.map_or("None".to_string(), |e| e.to_string()))).await?; + } + _ => {} + } + + // When we receive a request_response event + SwarmEvent::Behaviour(BehaviourEvent::RequestResponse(event)) => match event { + RequestResponseEvent::Message { message, .. } => match message { + RequestResponseMessage::Request { request, .. } => { + //TODO: support ProtocolSupport::Full + debug!( + "umimplemented: request_response::Message::Request: {:?}", + request + ); + } + RequestResponseMessage::Response { response, .. } => { + info!( + "request_response::Message::Response: size:{}", + response.file_body.len() + ); + // TODO: store this file (in memory or disk) and provider it via Kademlia + } + } + RequestResponseEvent::OutboundFailure { + request_id, error, .. + } => { + error!( + "request_response::Event::OutboundFailure for request {:?}: {:?}", + request_id, error + ) + } + _ => {} + } + event => { + debug!("Other type of event: {:?}", event); + } + } + } + } + + Ok(()) + } +} + +enum UniversalConnectivityMessage { + Chat { + propagation_source: PeerId, + from: Option, + data: Vec, + seq_no: Option, + topic: TopicHash, + }, + File { + propagation_source: PeerId, + from: Option, + data: Vec, + seq_no: Option, + topic: TopicHash, + }, + PeerDiscovery { + propagation_source: PeerId, + from: Option, + discovered_peer: Option, + discovered_addrs: Vec, + seq_no: Option, + topic: TopicHash, + }, + Unknown { + propagation_source: PeerId, + from: Option, + data: Vec, + seq_no: Option, + topic: TopicHash, + }, +} + +impl TryFrom for UniversalConnectivityMessage { + type Error = anyhow::Error; + + fn try_from(event: GossipsubEvent) -> anyhow::Result { + if let GossipsubEvent::Message { + propagation_source, + message, + .. + } = event + { + let from = message.source.map(Into::into); + let data = message.data.clone(); + let seq_no = message.sequence_number; + let topic = message.topic.clone(); + + match topic.as_str() { + GOSSIPSUB_CHAT_TOPIC => Ok(Self::Chat { + propagation_source, + from, + data, + seq_no, + topic, + }), + GOSSIPSUB_CHAT_FILE_TOPIC => Ok(Self::File { + propagation_source, + from, + data, + seq_no, + topic, + }), + GOSSIPSUB_PEER_DISCOVERY => { + let mut reader = BytesReader::from_bytes(&data); + let peer = + DiscoveredPeer::from_reader(&mut reader, &data).map_err(|_| fmt::Error)?; + + let discovered_peer = { + if let Ok(pubkey) = PublicKey::try_decode_protobuf(&peer.publicKey) { + Some(PeerId::from(pubkey).into()) + } else { + None + } + }; + + // only accept valid Multiaddrs + let discovered_addrs = { + let mut m: Vec = Vec::new(); + for multiaddr in &peer.multiAddrs { + if let Ok(ma) = Multiaddr::try_from(multiaddr.to_vec()) { + m.push(ma); + } + } + m + }; + + Ok(Self::PeerDiscovery { + propagation_source, + from, + discovered_peer, + discovered_addrs, + seq_no, + topic, + }) + } + _ => Ok(Self::Unknown { + propagation_source, + from, + data, + seq_no, + topic, + }), + } + } else { + Err(anyhow::anyhow!("Invalid GossipsubEvent")) + } + } +} + +impl fmt::Display for UniversalConnectivityMessage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Chat { + propagation_source, + from, + data, + seq_no, + topic, + } => { + let propagation_source = { + let ps: ChatPeer = propagation_source.into(); + format!("{} ({})", ps.id(), ps) + }; + let chat_peer = from.as_ref().map_or("Unknown".to_string(), |from| { + format!("{} ({})", from.id(), from) + }); + let source = from.as_ref().map_or("Unknown".to_string(), |peer| { + format!("{} ({})", peer.id(), peer) + }); + let seq_no = seq_no.map_or("Unknown".to_string(), |seq_no| seq_no.to_string()); + let message = + String::from_utf8(data.to_vec()).unwrap_or("invalid UTF-8".to_string()); + write!(f, "Received chat message:\n\tp source: {propagation_source}\n\tsource: {source}\n\tseq no: {seq_no}\n\ttopic: {topic}\n\tfrom: {chat_peer}\n\tmsg: {message}") + } + Self::File { + propagation_source, + from, + data, + seq_no, + topic, + } => { + let propagation_source = { + let ps: ChatPeer = propagation_source.into(); + format!("{} ({})", ps.id(), ps) + }; + let chat_peer = from.as_ref().map_or("Unknown".to_string(), |from| { + format!("{} ({})", from.id(), from) + }); + let source = from.as_ref().map_or("Unknown".to_string(), |peer| { + format!("{} ({})", peer.id(), peer) + }); + let seq_no = seq_no.map_or("Unknown".to_string(), |seq_no| seq_no.to_string()); + let message = + String::from_utf8(data.to_vec()).unwrap_or("invalid UTF-8".to_string()); + write!(f, "Received file offer:\n\tp source: {propagation_source}\n\tsource: {source}\n\tseq no: {seq_no}\n\ttopic: {topic}\n\tfrom: {chat_peer}\n\tfile id: {message}") + } + Self::PeerDiscovery { + propagation_source, + from, + discovered_peer, + discovered_addrs, + seq_no, + topic, + } => { + let propagation_source = { + let ps: ChatPeer = propagation_source.into(); + format!("{} ({})", ps.id(), ps) + }; + let chat_peer = from.as_ref().map_or("Unknown".to_string(), |from| { + format!("{} ({})", from.id(), from) + }); + let source = from.as_ref().map_or("Unknown".to_string(), |peer| { + format!("{} ({})", peer.id(), peer) + }); + let seq_no = seq_no.map_or("Unknown".to_string(), |seq_no| seq_no.to_string()); + let discovered_peer = discovered_peer + .map_or("Unknown".to_string(), |discovered_peer| { + format!("{} ({})", discovered_peer.id(), discovered_peer) + }); + write!(f, "Received peer discovery:\n\tp source: {propagation_source}\n\tsource: {source}\n\tseq no: {seq_no}\n\ttopic: {topic}\n\tfrom: {chat_peer}\n\tpeer: {discovered_peer}\n\tmultiaddrs: {}", discovered_addrs.len()) + } + Self::Unknown { + propagation_source, + from, + data, + seq_no, + topic, + } => { + let propagation_source = { + let ps: ChatPeer = propagation_source.into(); + format!("{} ({})", ps.id(), ps) + }; + let chat_peer = from.as_ref().map_or("Unknown".to_string(), |from| { + format!("{} ({})", from.id(), from) + }); + let source = from.as_ref().map_or("Unknown".to_string(), |peer| { + format!("{} ({})", peer.id(), peer) + }); + let seq_no = seq_no.map_or("Unknown".to_string(), |seq_no| seq_no.to_string()); + let fields = decode_unknown_protobuf(data).map_err(|_| fmt::Error)?; + let data = pretty_print_fields(&fields); + write!(f, "Received unknown message:\n\tp source: {propagation_source}\n\tsource: {source}\n\tseq no: {seq_no}\n\ttopic: {topic}\n\tfrom: {chat_peer}\n\tdata: {data}") + } + } + } +} diff --git a/src/ui/headless.rs b/src/ui/headless.rs new file mode 100644 index 0000000..82b6164 --- /dev/null +++ b/src/ui/headless.rs @@ -0,0 +1,114 @@ +#![allow(dead_code)] +use crate::{log::Message as LogMessage, ChatPeer, Message, Ui}; +use async_trait::async_trait; +use libp2p::core::PeerId; +use signal_hook::{consts::SIGTERM, iterator::Signals}; +use std::{collections::HashSet, time::Duration}; +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tokio_util::sync::CancellationToken; + +/// A headless UI for the peer +pub struct Headless { + // my peer id + me: ChatPeer, + // we receive log messages from the log thread + from_log: Receiver, + // we send UI messages to the peer thread + to_peer: Sender, + // we receive UI messages from the peer thread + from_peer: Receiver, + // the shutdown token + shutdown: CancellationToken, + // the list of peers + peers: HashSet, +} + +impl Headless { + /// Create a new UI instance + pub fn build( + me: PeerId, + from_log: Receiver, + shutdown: CancellationToken, + ) -> (Box, Sender, Receiver) { + // create a new channels for sending/receiving messages + let (to_peer, from_ui) = mpsc::channel::(64); + let (to_ui, from_peer) = mpsc::channel::(64); + + // create a new TUI instance + let ui: Box = Box::new(Self { + me: me.into(), + from_log, + to_peer, + from_peer, + shutdown, + peers: HashSet::new(), + }); + + (ui, to_ui, from_ui) + } +} + +#[async_trait] +impl Ui for Headless { + /// Run the UI + async fn run(&mut self) -> anyhow::Result<()> { + // Register the SIGTERM signal + let mut signals = Signals::new([SIGTERM])?; + + println!("Headless UI started"); + println!("Press Ctrl+C to exit"); + println!("My peer id: {} ({})", self.me.id(), self.me); + + // Main loop + 'main: loop { + // Process log messages + if let Ok(log) = self.from_log.try_recv() { + //TODO: remove this after [PR 5966](https://github.com/libp2p/rust-libp2p/pull/5966) + if !log.message.starts_with("Can't send data channel") { + println!("{}", log.message); + } + } + + // Process peer messages + if let Ok(ui_message) = self.from_peer.try_recv() { + match ui_message { + Message::Chat { from, data } => { + let from = from.map_or("Unknown".to_string(), |peer| peer.to_string()); + let message = + String::from_utf8(data).unwrap_or("Invalid UTF-8".to_string()); + println!("{}: {}", from, message); + } + Message::AddPeer(peer) => { + if self.peers.insert(peer) { + println!( + "Adding peer:\n\tpeer id: {}\n\tname: {}", + peer.id(), + peer.name() + ); + } + } + Message::RemovePeer(peer) => { + if self.peers.remove(&peer) { + println!("Removing peer: {peer:?}"); + } + } + Message::Event(event) => { + println!("{}", event); + } + _ => {} + } + } + + // check if we have received the shutdown signal from the OS + if signals.pending().next() == Some(SIGTERM) { + println!("Received SIGTERM, shutting down"); + self.shutdown.cancel(); + break 'main; + } + + tokio::time::sleep(Duration::from_millis(18)).await; + } + + Ok(()) + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs new file mode 100644 index 0000000..e5023c5 --- /dev/null +++ b/src/ui/mod.rs @@ -0,0 +1,15 @@ +/// the async UI trait +/// the async UI trait +#[async_trait::async_trait] +pub trait Ui: Send { + /// Run the UI + async fn run(&mut self) -> anyhow::Result<()>; +} + +/// the TUI implementation +pub mod tui; +pub use tui::Tui; + +/// the headless implementation +pub mod headless; +pub use headless::Headless; diff --git a/src/ui/tui.rs b/src/ui/tui.rs new file mode 100644 index 0000000..b005528 --- /dev/null +++ b/src/ui/tui.rs @@ -0,0 +1,508 @@ +use crate::{log::Message as LogMessage, ChatPeer, Message, Ui}; +use async_trait::async_trait; +use crossterm::{ + event::{ + self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent, KeyModifiers, + MouseEvent, MouseEventKind, + }, + execute, + terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen}, +}; +use libp2p::core::PeerId; +use ratatui::{ + backend::CrosstermBackend, + layout::{Alignment, Constraint, Direction, Layout}, + prelude::{Buffer, Rect, Widget}, + style::{Color, Modifier, Style}, + text::{Line, Span}, + widgets::{Block, Borders, List, ListItem, Paragraph}, + Terminal, +}; +use std::{ + collections::{HashSet, VecDeque}, hash::{DefaultHasher, Hash, Hasher}, io, option::Option, time::Duration +}; +use tokio::sync::mpsc::{self, Receiver, Sender}; +use tokio_util::sync::CancellationToken; +use tracing::{error, info}; + +/// A simple UI for the peer +pub struct Tui { + // my peer id + me: ChatPeer, + // we receive log messages from the log thread + from_log: Receiver, + // we send UI messages to the peer thread + to_peer: Sender, + // we receive UI messages from the peer thread + from_peer: Receiver, + // the shutdown token + shutdown: CancellationToken, +} + +impl Tui { + /// Create a new UI instance + pub fn build( + me: PeerId, + from_log: Receiver, + shutdown: CancellationToken, + ) -> (Box, Sender, Receiver) { + // create a new channels for sending/receiving messages + let (to_peer, from_ui) = mpsc::channel::(64); + let (to_ui, from_peer) = mpsc::channel::(64); + + // create a new TUI instance + let ui: Box = Box::new(Self { + me: me.into(), + from_log, + to_peer, + from_peer, + shutdown, + }); + + (ui, to_ui, from_ui) + } +} + +#[async_trait] +impl Ui for Tui { + /// Run the UI + async fn run(&mut self) -> anyhow::Result<()> { + // the currently selected tab + let mut selected_tab = 0; + + // TUI setup + enable_raw_mode()?; + let mut stdout = io::stdout(); + execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?; + let backend = CrosstermBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + + // Log Widget + let mut log_widget = LinesWidget::new("Log", 200); + + // Chat Widget + let mut chat_widget = ChatWidget::new(&self.me); + + // Main loop + loop { + // Process log messages + if let Ok(log) = self.from_log.try_recv() { + //TODO: remove this after [PR 5966](https://github.com/libp2p/rust-libp2p/pull/5966) + if !log.message.starts_with("Can't send data channel") { + log_widget.add_line(log.message); + } + } + + // Process peer messages + if let Ok(ui_message) = self.from_peer.try_recv() { + match ui_message { + Message::Chat { from, data } => { + let message = + String::from_utf8(data).unwrap_or("Invalid UTF-8".to_string()); + chat_widget.add_chat(from, message); + } + Message::AllPeers { peers } => { + for (peer, topics) in peers { + let mut peer_str = format!("{peer}: "); + for topic in topics { + peer_str.push_str(&format!("\n\t{}, ", topic)); + } + info!("{peer_str}"); + } + } + Message::AddPeer(peer) => { + if chat_widget.peers.insert(peer) { + log_widget.add_line(format!( + "Adding peer:\n\tpeer id: {}\n\tname: {}", + peer.id(), + peer.name() + )); + } + } + Message::RemovePeer(peer) => { + if chat_widget.peers.remove(&peer) { + log_widget.add_line(format!("Removing peer: {peer:?}")); + } + } + Message::Event(event) => { + log_widget.add_line(event); + } + } + } + + // Draw the UI + terminal.draw(|f| match selected_tab { + 0 => f.render_widget(&mut chat_widget, f.area()), + 1 => f.render_widget(&mut log_widget, f.area()), + _ => {} + })?; + + // Handle input events + if event::poll(Duration::from_millis(18))? { + match event::read()? { + Event::Key(key) => match key { + // Handle ctrl+c + KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + } => { + info!("Received Ctrl+C, shutting down..."); + self.shutdown.cancel(); + break; + } + + // Handle ctrl+shift+p + KeyEvent { + code: KeyCode::Char('p'), + modifiers: KeyModifiers::CONTROL | KeyModifiers::SHIFT, + .. + } => { + error!("All peers sent"); + self.to_peer + .send(Message::AllPeers { peers: vec![] }) + .await?; + } + + // Handle all other key events + _ => match key.code { + KeyCode::Tab => { + selected_tab = (selected_tab + 1) % 2; + } + KeyCode::Char(c) if selected_tab == 0 => { + chat_widget.input.push(c); + } + KeyCode::Backspace if selected_tab == 0 => { + chat_widget.input.pop(); + } + KeyCode::Enter if selected_tab == 0 => { + let input_str = chat_widget.input.as_str(); + if input_str.len() > 0 { + match input_str { + "/quit" => { + info!("Received /quit command, shutting down..."); + self.shutdown.cancel(); + break; + } + _ => { + // send the chat message to the swarm to be gossiped + self.to_peer + .send(Message::Chat { + from: Some(self.me), + data: chat_widget.input.clone().into_bytes(), + }) + .await?; + // add our chat to the local chat widget + chat_widget + .add_chat(Some(self.me), chat_widget.input.clone()); + } + } + + // clear the input + chat_widget.input.clear(); + } + } + _ => {} + }, + }, + Event::Mouse(event) => match selected_tab { + 0 => { + let _ = chat_widget.mouse_event(event); + } + 1 => { + let _ = log_widget.mouse_event(event); + } + _ => {} + }, + _ => {} + } + } + } + + // Cleanup + disable_raw_mode()?; + execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture)?; + + Ok(()) + } +} + +// Function to wrap text into multiple lines based on a max width +fn wrap_text<'a>(line: Line<'a>, max_width: usize) -> Vec> { + let mut wrapped_lines = Vec::new(); + let mut words_with_styles: Vec<(String, Style)> = Vec::new(); + + for span in line.spans { + let style = span.style; + for word in span.content.split_inclusive(' ') { + words_with_styles.push((word.to_string(), style)); + } + } + + if words_with_styles.is_empty() { + return vec![Line::from("")]; + } + + let leading_whitespace = words_with_styles[0].0 + .chars() + .take_while(|c| c.is_whitespace()) + .collect::(); + + let mut current_line_spans: Vec> = Vec::new(); + let mut current_line_width = 0; + + for (word_raw, style) in words_with_styles { + let word = word_raw.replace('\t', " "); + let word_len = word.chars().count(); + + if current_line_width + word_len > max_width { + if !current_line_spans.is_empty() { + wrapped_lines.push(Line::from(current_line_spans.drain(..).collect::>())); + } + + if word_len > max_width { + let mut remaining = word.clone(); + while !remaining.is_empty() { + let split_point = std::cmp::min(remaining.len(), max_width); + let (chunk, rest) = remaining.split_at(split_point); + let formatted_chunk = format!("{}{}", leading_whitespace, chunk); + + wrapped_lines.push(Line::from(Span::styled(formatted_chunk, style))); + remaining = rest.to_string(); + } + current_line_width = 0; + } else { + let formatted_word = format!("{}{}", leading_whitespace, word.trim_start()); + current_line_width = formatted_word.chars().count(); + current_line_spans.push(Span::styled(formatted_word, style)); + } + } else { + let content = if current_line_spans.is_empty() && current_line_width == 0 { + format!("{}{}", leading_whitespace, word.trim_start()) + } else { + word.clone() + }; + + current_line_width += content.chars().count(); + current_line_spans.push(Span::styled(content, style)); + } + } + + if !current_line_spans.is_empty() { + wrapped_lines.push(Line::from(current_line_spans)); + } + + wrapped_lines +} + +// Lines Widget +struct LinesWidget<'a> { + title: String, + max: usize, + lines: VecDeque>, + scroll: usize, + area: Rect, +} + +impl<'a> LinesWidget<'a> { + // Create a new LogWidget instance + fn new(title: impl Into, max: usize) -> Self { + Self { + title: title.into(), + max, + lines: VecDeque::new(), + scroll: 0, + area: Rect::default(), + } + } + + // Handle a mouse event + fn mouse_event(&mut self, event: MouseEvent) -> bool { + // check if the event happened in our area + let x = event.column; + let y = event.row; + + if x >= self.area.x + && x < self.area.x + self.area.width + && y >= self.area.y + && y < self.area.y + self.area.height + { + match event.kind { + MouseEventKind::ScrollUp => { + self.scroll += 1; + } + MouseEventKind::ScrollDown => { + if self.scroll > 0 { + self.scroll -= 1; + } + } + _ => {} + } + true + } else { + false + } + } + + // Add a line to the widget + fn add_line(&mut self, line: impl Into>) { + self.lines.push_back(line.into()); + if self.lines.len() > self.max { + self.lines.drain(0..(self.lines.len() - self.max)); + } + } +} + +impl<'a> Widget for &mut LinesWidget<'a> { + fn render(self, area: Rect, buf: &mut Buffer) { + let block = Block::default() + .title(self.title.as_str()) + .title_alignment(Alignment::Right) + .borders(Borders::ALL) + .style(Style::default()); + + self.area = block.inner(area); + let inner_area = self.area; + let max_lines = inner_area.height as usize; + + let mut logs: Vec = self + .lines + .iter() + .flat_map(|l| { + let wrapped_lines = wrap_text(l.clone(), inner_area.width as usize - 2); + wrapped_lines + .into_iter() + .map(ListItem::new) + .collect::>() + }) + .collect(); + if logs.len() > max_lines { + if logs.len() > (max_lines + self.scroll) { + logs.drain(0..(logs.len() - max_lines - self.scroll)); + } else { + self.scroll = max_lines; + } + } + List::new(logs).block(block).render(area, buf); + } +} + +// Chat Widget +struct ChatWidget<'a> { + me: &'a ChatPeer, + peers: HashSet, + chat: LinesWidget<'a>, + input: String, +} + +impl<'a> ChatWidget<'a> { + // Create a new ChatWidget instance + fn new(me: &'a ChatPeer) -> Self { + let mut peers = HashSet::new(); + peers.insert(*me); + + ChatWidget { + me, + peers, + chat: LinesWidget::new("Chat", 100), + input: String::new(), + } + } + + // Handle a mouse event + fn mouse_event(&mut self, event: MouseEvent) -> bool { + self.chat.mouse_event(event) || self.chat.mouse_event(event) + } + + // Add a chat message to the widget + fn add_chat(&mut self, peer: Option, message: impl Into) { + use ratatui::prelude::Stylize; + + let peer = peer.map_or("Unknown".to_string(), |p| p.to_string()); + let time_str = chrono::Local::now().format("[%H:%M:%S]"); + + let mut hasher = DefaultHasher::new(); + peer.hash(&mut hasher); + let hash = hasher.finish(); + + let color_index = (hash % 216) as u8 + 16; + + self.chat.add_line( + Line::from(vec![ + time_str.to_string().into(), + " ".into(), + peer.fg(Color::Indexed(color_index)), + ": ".into(), + message.into().into() + ]) + ) + } +} + +impl Widget for &mut ChatWidget<'_> { + fn render(self, area: Rect, buf: &mut Buffer) { + let layout = Layout::default() + .direction(Direction::Vertical) + .constraints( + [ + Constraint::Length(3), + Constraint::Fill(1), + Constraint::Length(3), + ] + .as_ref(), + ) + .split(area); + + let topic_block = Block::default() + .title("Conversation") + .title_alignment(Alignment::Right) + .borders(Borders::ALL) + .style(Style::default()); + Paragraph::new("Topic: ghost-gossip-main") + .block(topic_block) + .render(layout[0], buf); + + // calculate the layout for the top row + let content_layout = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(100), Constraint::Length(24)].as_ref()) + .split(layout[1]); + + // render the chat messages + self.chat.render(content_layout[0], buf); + + // render the peers list + let peers_block = Block::default() + .title("Peers") + .title_alignment(Alignment::Right) + .borders(Borders::ALL) + .style(Style::default()); + let peers: Vec = self + .peers + .iter() + .map(|p| { + if p == self.me { + ListItem::new(Span::styled( + format!("{} (You)", p), + Style::default().add_modifier(Modifier::ITALIC), + )) + } else { + ListItem::new(Span::raw(p.to_string())) + } + }) + .collect(); + List::new(peers) + .block(peers_block) + .render(content_layout[1], buf); + + // render the chat input + let message_block = Block::default() + .title("Type a message") + .title_alignment(Alignment::Right) + .borders(Borders::ALL) + .style(Style::default()); + Paragraph::new(format!("{} > {}", self.me, self.input.clone())) + .block(message_block) + .render(layout[2], buf); + } +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..a60de72 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,189 @@ +use libp2p::{multiaddr::Protocol, Multiaddr, PeerId}; +use quick_protobuf::reader::BytesReader; +use std::{convert::TryFrom, fmt, net::IpAddr}; + +/// Define protobuf wire types since they are no longer in quick-protobuf +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum WireType { + /// Varint wire type + Varint = 0, + /// Fixed64 wire type + Fixed64 = 1, + /// Length-delimited wire type + LengthDelimited = 2, + /// Start group wire type + StartGroup = 3, + /// End group wire type + EndGroup = 4, + /// Fixed32 wire type + Fixed32 = 5, +} + +/// Error type for TryFrom conversion +#[derive(Debug)] +pub struct InvalidWireType(u32); + +impl fmt::Display for InvalidWireType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Invalid wire type value: {}", self.0) + } +} + +impl std::error::Error for InvalidWireType {} + +impl TryFrom for WireType { + type Error = InvalidWireType; + + fn try_from(tag: u32) -> Result { + // Extract wire type from the lower 3 bits + let wire_type_value = tag & 0x07; + + match wire_type_value { + 0 => Ok(WireType::Varint), + 1 => Ok(WireType::Fixed64), + 2 => Ok(WireType::LengthDelimited), + 3 => Ok(WireType::StartGroup), + 4 => Ok(WireType::EndGroup), + 5 => Ok(WireType::Fixed32), + invalid => Err(InvalidWireType(invalid)), + } + } +} + +/// Decode an unknown protobuf message into a list of fields +pub fn decode_unknown_protobuf(bytes: &[u8]) -> anyhow::Result> { + let mut reader = BytesReader::from_bytes(bytes); + let mut fields = Vec::new(); + + // Read the next tag + while let Ok(tag) = reader.next_tag(bytes) { + // Extract field number and wire type + let field_number = tag >> 3; + let wire_type = WireType::try_from(tag).map_err(|e| { + quick_protobuf::Error::Message(format!("Invalid wire type value: {}", e.0)) + })?; + + // Decode the value based on wire type + let value = match wire_type { + WireType::Varint => { + let varint = reader.read_varint64(bytes)?; + format!("int64: {}", varint) // Could also be int32, uint32, etc. + } + WireType::Fixed64 => { + let fixed64 = reader.read_fixed64(bytes)?; + format!("fixed64: {}", fixed64) // Could also be double + } + WireType::LengthDelimited => { + let len = reader.read_varint32(bytes)? as usize; + let data = reader.read_bytes(bytes)?; + // Try to interpret as string; if it fails, treat as raw bytes + match std::str::from_utf8(data) { + Ok(s) => format!("string: \"{}\"", s), + Err(_) => format!("bytes({}): {}", len, hex::encode(data)), + } + } + WireType::Fixed32 => { + let fixed32 = reader.read_fixed32(bytes)?; + format!("fixed32: {}", fixed32) // Could also be float + } + WireType::StartGroup | WireType::EndGroup => { + // Groups are deprecated and rare; skip for simplicity + return Err( + quick_protobuf::Error::Message("Groups not supported".to_string()).into(), + ); + } + }; + + fields.push(format!( + "Field {} ({:?}): {}", + field_number, wire_type, value + )); + } + + Ok(fields) +} + +/// Pretty print a list of fields +pub fn pretty_print_fields(fields: &[String]) -> String { + let mut output = String::new(); + output.push_str("Decoded Protobuf Message {\n"); + for field in fields { + output.push_str(" "); + output.push_str(field); + output.push('\n'); + } + output.push('}'); + output +} + +/// Split the PeerId from a Multiaddr +pub fn split_peer_id(multiaddr: Multiaddr) -> Option<(Multiaddr, PeerId)> { + let mut base_addr = Multiaddr::empty(); + let mut peer_id = None; + + // Iterate over the protocols in the Multiaddr + for protocol in multiaddr.into_iter() { + if let Protocol::P2p(id) = protocol { + peer_id = Some(id); + break; // Stop once we find the P2p component + } else { + base_addr.push(protocol); // Add non-P2p components to the base address + } + } + + peer_id.map(|id| (base_addr, id)) +} + +/// Extract the IP address from a Multiaddr +pub fn extract_ip_multiaddr(multiaddr: &Multiaddr) -> Option { + let mut result = Multiaddr::empty(); + + for component in multiaddr.into_iter() { + match component { + Protocol::Ip4(addr) => { + result.push(Protocol::Ip4(addr)); + return Some(result); + } + Protocol::Ip6(addr) => { + result.push(Protocol::Ip6(addr)); + return Some(result); + } + _ => continue, + } + } + + None +} + +/// Check if a Multiaddr contains a private IP address +pub fn is_private_ip(multiaddr: &Multiaddr) -> bool { + for component in multiaddr.into_iter() { + match component { + Protocol::Ip4(addr) => { + return addr.is_private() || // 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 + addr.is_loopback() || // 127.0.0.0/8 + addr.is_link_local() || // 169.254.0.0/16 + addr.is_unspecified(); // 0.0.0.0 + } + Protocol::Ip6(addr) => { + return addr.is_loopback() || // ::1 + addr.is_unspecified() || // :: + // Unique Local Address (fc00::/7 where 8th bit is 1) + (addr.segments()[0] & 0xfe00 == 0xfc00) || + // Link-Local unicast (fe80::/10) + (addr.segments()[0] & 0xffc0 == 0xfe80); + } + _ => continue, + } + } + false +} + +/// Convert an IP address to a Multiaddr +pub fn ipaddr_to_multiaddr(ip: &IpAddr) -> Multiaddr { + let multiaddr = match ip { + IpAddr::V4(ipv4) => Multiaddr::empty().with(Protocol::Ip4(*ipv4)), + IpAddr::V6(ipv6) => Multiaddr::empty().with(Protocol::Ip6(*ipv6)), + }; + multiaddr +}