diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1baef92 --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +# Generated by Cargo +# will have compiled files and executables +debug +target + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb +*.env +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/W + + +# Added by cargo +/target +/logs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..41cef35 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3910 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Omikron" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "ansi_term", + "base64 0.22.1", + "dashmap", + "dotenv", + "futures", + "hex", + "hkdf", + "livekit-api", + "livekit-protocol", + "log", + "once_cell", + "rand 0.8.5", + "rand_core 0.6.4", + "rustls", + "sha2", + "strum", + "strum_macros", + "thiserror 2.0.18", + "tokio", + "ttp-core", + "ttp-native", + "uuid", + "x448", +] + +[[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 = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[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 = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "asn1-rs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" +dependencies = [ + "asn1-rs-derive", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[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 = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[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-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-native-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9343dc5acf07e79ff82d0c37899f079db3534d99f189a1837c8e549c99405bec" +dependencies = [ + "futures-util", + "native-tls", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-tungstenite" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cca750b12e02c389c1694d35c16539f88b8bbaa5945934fdc1b41a776688589" +dependencies = [ + "async-native-tls", + "async-std", + "futures-io", + "futures-util", + "log", + "pin-project-lite", + "tungstenite 0.21.0", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" +dependencies = [ + "aws-lc-sys", + "untrusted 0.7.1", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" + +[[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 = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "num-traits", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cmake" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" +dependencies = [ + "cc", +] + +[[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.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +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 = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +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 0.2.9", + "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 = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" + +[[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 = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" +dependencies = [ + "asn1-rs", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +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 = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + +[[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 = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[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 = "ed448-goldilocks" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" +dependencies = [ + "fiat-crypto 0.1.20", + "hex", + "subtle", +] + +[[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 = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[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 5.4.1", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[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.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[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 = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[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 = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[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 = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[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 = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[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 = "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 = "httlib-huffman" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877" + +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.4.0", +] + +[[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 1.4.0", + "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.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http 1.4.0", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http 1.4.0", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.3", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[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 = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jsonwebtoken" +version = "10.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1" +dependencies = [ + "base64 0.22.1", + "ed25519-dalek", + "getrandom 0.2.17", + "hmac", + "js-sys", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde_json", + "sha2", + "signature", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "livekit-api" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020c7eaab7cec16b69a058659f9acff32067e0e2d4f81e80639f3ffa29464b09" +dependencies = [ + "async-tungstenite", + "base64 0.21.7", + "http 1.4.0", + "jsonwebtoken", + "livekit-protocol", + "log", + "os_info", + "parking_lot", + "pbjson-types", + "prost", + "rand 0.9.2", + "reqwest", + "scopeguard", + "serde", + "serde_json", + "sha2", + "thiserror 1.0.69", + "tokio-tungstenite", + "url", +] + +[[package]] +name = "livekit-protocol" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c21d56b4dc2598b1296ca891abb225bf1c05ea007d6f43bc4fd48c31be64bf59" +dependencies = [ + "futures-util", + "livekit-runtime", + "parking_lot", + "pbjson", + "pbjson-types", + "prost", + "serde", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "livekit-runtime" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "532e84c6cdc5fe774f2b5d9912597b5f3bea561927a48296d03e24549d21c3f6" +dependencies = [ + "tokio", + "tokio-stream", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +dependencies = [ + "value-bag", +] + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[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 = "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-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[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-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "octets" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8311fa8ab7a57759b4ff1f851a3048d9ef0effaa0130726426b742d26d8a88e7" + +[[package]] +name = "oid-registry" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_info" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224" +dependencies = [ + "android_system_properties", + "log", + "nix", + "objc2", + "objc2-foundation", + "objc2-ui-kit", + "serde", + "windows-sys 0.61.2", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pbjson" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +dependencies = [ + "heck 0.4.1", + "itertools 0.11.0", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64 0.22.1", + "serde_core", +] + +[[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 = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[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 = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +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 = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[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.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +dependencies = [ + "bytes", + "heck 0.5.0", + "itertools 0.12.1", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools 0.12.1", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2 0.6.3", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "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.3", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +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 = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[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.5", +] + +[[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.5", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rcgen" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b99e0098aa4082912d4c649628623db6aba77335e4f4569ff5083a6448b32e" +dependencies = [ + "aws-lc-rs", + "rustls-pki-types", + "time", + "x509-parser 0.18.1", + "yasna", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "http 1.4.0", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[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.17", + "libc", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[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 = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted 0.9.0", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[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 = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "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 = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "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.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn", +] + +[[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.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[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 = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2 0.6.3", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite 0.20.1", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http 1.4.0", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[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.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "ttp-core" +version = "0.1.0" +source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" +dependencies = [ + "base64 0.22.1", + "byteorder", + "rand 0.8.5", + "strum", + "strum_macros", +] + +[[package]] +name = "ttp-native" +version = "0.1.0" +source = "git+https://github.com/Tensamin/TTP.git#e246d1af6a42c71514d0ccc06fef94d71e4ad167" +dependencies = [ + "quinn", + "rustls", + "rustls-native-certs", + "thiserror 2.0.18", + "tokio", + "ttp-core", + "wtransport", +] + +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.4.0", + "httparse", + "log", + "native-tls", + "rand 0.8.5", + "sha1", + "thiserror 1.0.69", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[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 = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[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.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[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 = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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 = "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-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[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.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[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.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wtransport" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56b611f195638f3790e4e5a41e9d777643a6c324ae4ffd1f0f53f2738e7678c" +dependencies = [ + "bytes", + "pem", + "quinn", + "rcgen", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "sha2", + "socket2 0.5.10", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "url", + "wtransport-proto", + "x509-parser 0.17.0", +] + +[[package]] +name = "wtransport-proto" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1627c5b59450278e9771aab35275d72bfa2788128c197c5af1e4a820c8737ef4" +dependencies = [ + "httlib-huffman", + "octets", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "x448" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" +dependencies = [ + "ed448-goldilocks", + "hex", + "rand_core 0.5.1", +] + +[[package]] +name = "x509-parser" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" +dependencies = [ + "asn1-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "time", +] + +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs", + "aws-lc-rs", + "data-encoding", + "der-parser", + "lazy_static", + "nom", + "oid-registry", + "rusticata-macros", + "thiserror 2.0.18", + "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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3127b12 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "Omikron" +version = "0.1.0" +edition = "2024" + +[dependencies] +ttp-core = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-core" } +ttp-native = { git = "https://github.com/Tensamin/TTP.git", package = "ttp-native" } + +ansi_term = "*" +uuid = { version = "*", features = ["v4"] } + +base64 = "0.22.1" +dashmap = "*" +futures = "*" +hex = "*" +once_cell = "1.21.3" +rand = "0.8" +rand_core = { version = "0.6", features = ["getrandom", "std"] } +rustls = { version = "0.23.37", default-features = false, features = [ + "std", + "tls12", + "aws-lc-rs", + "prefer-post-quantum", +] } +aes-gcm = "0.10.3" +sha2 = "0.10.9" +tokio = { version = "*", features = ["full"] } +x448 = { version = "*" } +log = "0.4" +dotenv = "0.15.0" +hkdf = "0.12.4" +strum = "0.28.0" +strum_macros = "0.28.0" +livekit-api = { version = "0.4.14", features = ["native-tls"] } +livekit-protocol = "0.7.1" +thiserror = "2.0.18" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d218eff --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) [2025] [Methanium] +All rights reserved. + +This software is protected by copyright. Copying, editing, +distributing, publicly performing, or any other use of this software +or its components, in source or binary form, is strictly prohibited without the express +written permission of the copyright holder. + +FUTURE LICENSE ACCEPTANCE: +It is the copyright holder's intention to release this software in the future +under a license yet to be defined, which will, among other things, +allow private, non-commercial use. This statement does not constitute +a current license grant and does not alter the above +prohibition on use, copying, or modification. Until the formal +publication of such a future license, all rights remain +reserved. diff --git a/README.md b/README.md new file mode 100644 index 0000000..06c1cdb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ⚠️ Moved to [git.methanium.net](https://git.methanium.net/tensamin/omikron) ⚠️ diff --git a/src/anonymous_clients/anonymous_client_connection.rs b/src/anonymous_clients/anonymous_client_connection.rs new file mode 100644 index 0000000..8c57490 --- /dev/null +++ b/src/anonymous_clients/anonymous_client_connection.rs @@ -0,0 +1,519 @@ +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +use crate::anonymous_clients::anonymous_manager::{self, generate_username}; +use crate::calls::call_manager; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::rho::rho_manager; +use crate::util::logger::PrintType; +use crate::{log_cv_in, log_cv_out, log_out}; + +pub struct AnonymousClientConnection { + user_id: u64, + + pub sender: Arc, + pub receiver: Arc, + pub ping: Arc>, + pub interested_users: Arc>>, + is_open: Arc>, + pub user_name: Arc>, + pub display_name: Arc>, + pub avatar: Arc>, +} + +impl AnonymousClientConnection { + pub async fn from_general(general: Arc, user_id: u64) -> Arc { + let username: String = generate_username(); + Arc::new(Self { + user_id: user_id, + + ping: Arc::new(RwLock::new(0)), + interested_users: Arc::new(RwLock::new(Vec::new())), + is_open: Arc::new(RwLock::new(true)), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + user_name: Arc::new(RwLock::new(username.to_lowercase())), + display_name: Arc::new(RwLock::new(username)), + avatar: Arc::new(RwLock::new(String::new())), + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + while let Ok(cv) = self_clone.receiver.receive().await { + self_clone.clone().handle_message(cv).await; + } + self_clone.handle_close().await; + }); + } + + /// Get the user ID + pub fn get_user_id(&self) -> u64 { + self.user_id + } + + /// Get the user name + pub async fn get_user_name(&self) -> String { + self.user_name.read().await.clone() + } + + /// Get the display name + pub async fn get_display_name(&self) -> String { + self.display_name.read().await.clone() + } + + pub async fn set_display_name(&self, display: String) { + *self.display_name.write().await = display; + } + + /// Get the avatar + pub async fn get_avatar(&self) -> String { + self.avatar.read().await.clone() + } + + /// Send a CommunicationValue to the client + pub async fn send_message(self: Arc, cv: &CommunicationValue) { + if !*self.is_open.read().await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Attempted to send message to a closed connection." + ); + return; + } + if !cv.is_type(CommunicationType::pong) { + log_cv_out!(PrintType::Client, &cv); + } + if let Err(e) = self.sender.send(&cv).await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Send failed: {:?}", + e + ); + } + } + + /// Handle incoming message from client + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + tokio::spawn(async move { + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_cv_in!(PrintType::Client, &cv); + + if cv.is_type(CommunicationType::identification) { + let call_id = + Uuid::parse_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")) + .unwrap_or(Uuid::new_v4()); + + let call = if let Some(call) = call_manager::get_call(call_id).await { + if call.is_anonymous().await { + call + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + return; + } + } else { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_not_authenticated, + ) + .await; + return; + }; + + let mut invited = Vec::new(); + for call_invitee in call.members.read().await.clone() { + let call_invitee_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::get_user_data).add_data( + DataTypes::user_id, + DataValue::Number(call_invitee.user_id as i64), + ), + Some(Duration::from_secs(2)), + ) + .await + .unwrap(); + let mut json_invitee = Vec::new(); + let _ = json_invitee.push(( + DataTypes::user_id, + call_invitee_cv.get_data(DataTypes::user_id).clone(), + )); + let _ = json_invitee.push(( + DataTypes::username, + call_invitee_cv.get_data(DataTypes::username).clone(), + )); + let _ = json_invitee.push(( + DataTypes::display, + call_invitee_cv.get_data(DataTypes::display).clone(), + )); + let _ = json_invitee.push(( + DataTypes::avatar, + call_invitee_cv.get_data(DataTypes::avatar).clone(), + )); + + let _ = invited.push(DataValue::Container(json_invitee)); + } + + let token = call.create_anonymous_token(self.get_user_id()).await; + + let mut serialized = Vec::new(); + let _ = serialized.push((DataTypes::call_id, DataValue::Str(call_id.to_string()))); + let _ = + serialized.push((DataTypes::call_invited, DataValue::Array(invited.clone()))); + let _ = serialized.push((DataTypes::call_members, DataValue::Array(invited))); + let _ = serialized.push((DataTypes::call_token, DataValue::Str(token.unwrap()))); + self.clone() + .send_message( + &&CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::user_id, DataValue::Number(self.user_id as i64)) + .add_data( + DataTypes::username, + DataValue::Str(self.clone().get_user_name().await), + ) + .add_data( + DataTypes::display, + DataValue::Str(self.get_display_name().await), + ) + .add_data(DataTypes::avatar, DataValue::Str(self.get_avatar().await)) + .add_data(DataTypes::call_state, DataValue::Container(serialized)), + ) + .await; + } + + // Handle ping + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + // Handle client status changes + if cv.is_type(CommunicationType::client_changed) { + self.handle_client_changed(cv).await; + return; + } + + // Handle call invites + if cv.is_type(CommunicationType::call_invite) { + self.handle_call_invite(cv).await; + return; + } + + // Handle get call requests + if cv.is_type(CommunicationType::call_token) { + self.handle_get_call(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_disconnect_user) { + self.handle_call_disconnect_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_timeout_user) { + self.handle_call_timeout_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::change_user_data) { + if let Some(display_name) = cv.get_data(DataTypes::display).as_str() { + let _ = self.set_display_name(display_name.to_string()).await; + } + + return; + } + + if cv.is_type(CommunicationType::get_user_data) { + if let Some(anonymous) = { + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + anonymous_manager::get_anonymous_user(user_id as u64).await + } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { + anonymous_manager::get_anonymous_user_by_name(username.to_string()).await + } else { + None + } + } { + let response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data( + DataTypes::username, + DataValue::Str(anonymous.get_user_name().await), + ) + .add_data( + DataTypes::user_id, + DataValue::Number(anonymous.user_id as i64), + ) + .add_data( + DataTypes::display, + DataValue::Str(anonymous.get_display_name().await), + ) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())) + .add_data( + DataTypes::avatar, + DataValue::Str(anonymous.get_avatar().await), + ); + + self.send_message(&response).await; + + return; + } + } + + if cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::delete_user) + { + self.handle_omega_forward(cv).await; + return; + } + }); + } + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let client_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + client_for_closure.send_message(&response_cv).await; + } + }); + } + + /// Handle ping message + async fn handle_ping(self: Arc, cv: CommunicationValue) { + // Update our ping if provided + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + if let Ok(ping_val) = last_ping.to_string().parse::() { + let mut ping_guard = self.ping.write().await; + *ping_guard = ping_val; + } + } + + // Send pong response + let response = CommunicationValue::new(CommunicationType::pong).with_id(cv.get_id()); + + self.send_message(&response).await; + } + + /// Handle client status change + async fn handle_client_changed(self: Arc, _cv: CommunicationValue) { + /*let user_id = self.get_user_id().await; + if let Some(_status_str) = cv.get_data(DataTypes::user_state) { + let user_status = UserStatus::online; + }*/ + } + + /// Handle call invite + async fn handle_call_invite(self: Arc, cv: CommunicationValue) { + let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); + if receiver_id == 0 { + self.send_error_response(&cv.get_id(), CommunicationType::error_no_user_id) + .await; + return; + } + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response( + &cv.get_id(), + CommunicationType::error_invalid_call_id, + ) + .await; + return; + } + }, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error_no_call_id) + .await; + return; + } + }; + + let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; + if !invited { + self.send_error_response(&cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + + // Find target RhoConnection + let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { + Some(rho) => rho, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Get sender user ID + let sender_id = self.get_user_id(); + + // Create and send call distribution message + let forward = CommunicationValue::new(CommunicationType::call_invite) + .with_receiver(receiver_id as u64) + .with_sender(sender_id) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data( + DataTypes::receiver_id, + DataValue::Str(receiver_id.to_string()), + ) + .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); + + target_rho.message_to_client(forward).await; + + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle get call request + async fn handle_get_call(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id(); + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(&id_str.to_string()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + _ => { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + if let Some(token) = call_manager::get_call_token(user_id, call_id).await { + let response = CommunicationValue::new(CommunicationType::call_token) + .with_id(cv.get_id()) + .with_receiver(user_id) + .add_data(DataTypes::call_token, DataValue::Str(token.to_string())); + self.send_message(&response).await; + } else { + self.send_error_response(&cv.get_id(), CommunicationType::error) + .await; + return; + } + } + async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id()) + .await + .unwrap() + .has_admin() + { + call.get_caller(user_id as u64) + .await + .unwrap() + .set_timeout(untill) + .await; + } + } + } + async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id()) + .await + .unwrap() + .has_admin() + { + call.remove_caller(user_id as u64).await; + } + } + } + + /// Send error response + async fn send_error_response(self: Arc, message_id: &u32, error_type: CommunicationType) { + let error = CommunicationValue::new(error_type).with_id(*message_id); + self.send_message(&error).await; + } + /// Close the connection + pub async fn close(&self) { + let mut is_open_guard = self.is_open.write().await; + if !*is_open_guard { + return; + } + *is_open_guard = false; + + let _ = self.sender.close(); + } + + #[allow(dead_code)] + /// Set interested users list + pub async fn set_interested_users(self: Arc, interested_ids: Vec) { + let mut interested_guard = self.interested_users.write().await; + *interested_guard = interested_ids; + } + #[allow(dead_code)] + pub async fn get_interested_users(self: Arc) -> Vec { + let interested_guard = self.interested_users.read().await; + interested_guard.clone() + } + + #[allow(dead_code)] + /// Check if interested in a user and send notification + pub async fn are_you_interested(self: Arc, user_id: i64) { + let interested_guard = self.clone().get_interested_users().await; + if interested_guard.contains(&user_id) { + let notification = CommunicationValue::new(CommunicationType::client_changed) + .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(¬ification).await; + } + } + + /// Handle connection close + pub async fn handle_close(&self) { + + // TODO delete temp user + } +} + +// Implement Clone to make it easier to work with Arc +impl Clone for AnonymousClientConnection { + fn clone(&self) -> Self { + Self { + sender: Arc::clone(&self.sender), + receiver: Arc::clone(&self.receiver), + user_id: self.user_id, + ping: Arc::clone(&self.ping), + interested_users: Arc::clone(&self.interested_users), + is_open: Arc::clone(&self.is_open), + user_name: Arc::clone(&self.user_name), + display_name: Arc::clone(&self.display_name), + avatar: Arc::clone(&self.avatar), + } + } +} diff --git a/src/anonymous_clients/anonymous_manager.rs b/src/anonymous_clients/anonymous_manager.rs new file mode 100644 index 0000000..edfefde --- /dev/null +++ b/src/anonymous_clients/anonymous_manager.rs @@ -0,0 +1,53 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use rand::Rng; +use rand::seq::SliceRandom; +use std::sync::Arc; + +use crate::anonymous_clients::anonymous_client_connection::AnonymousClientConnection; + +static ANONYMOUS_USERS: Lazy>> = + Lazy::new(|| DashMap::new()); + +#[allow(dead_code)] +pub async fn add_anonymous_user(connection: Arc) { + ANONYMOUS_USERS.insert(connection.get_user_id(), connection); +} + +#[allow(dead_code)] +pub async fn remove_anonymous_user(user_id: u64) { + ANONYMOUS_USERS.remove(&user_id); +} + +pub async fn get_anonymous_user(user_id: u64) -> Option> { + ANONYMOUS_USERS.get(&user_id).map(|c| c.clone()) +} + +pub async fn get_anonymous_user_by_name( + username: String, +) -> Option> { + for user_conn in ANONYMOUS_USERS + .iter() + .map(|ref_multi| ref_multi.value().clone()) + { + if user_conn.get_user_name().await == username { + return Some(user_conn); + } + } + + return None; +} + +// TODO: implement check if taken +pub fn generate_username() -> String { + let adjectives = ["Swift", "Clever", "Brave", "Sneaky", "Fierce"]; + let nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Dragon"]; + + let mut rng = rand::thread_rng(); + let adj = adjectives.choose(&mut rng).unwrap(); + let noun = nouns.choose(&mut rng).unwrap(); + + let number: u16 = rng.gen_range(0..10000); + + format!("{}{}{}", adj, noun, number) +} diff --git a/src/anonymous_clients/mod.rs b/src/anonymous_clients/mod.rs new file mode 100644 index 0000000..3edd0b4 --- /dev/null +++ b/src/anonymous_clients/mod.rs @@ -0,0 +1,2 @@ +pub mod anonymous_client_connection; +pub mod anonymous_manager; diff --git a/src/calls/call_group.rs b/src/calls/call_group.rs new file mode 100644 index 0000000..91e85cf --- /dev/null +++ b/src/calls/call_group.rs @@ -0,0 +1,106 @@ +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; + +use std::{env, sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::{ + calls::{call_util, caller::Caller}, + omega::omega_connection::get_omega_connection, +}; + +pub struct CallGroup { + pub call_id: Uuid, + pub members: RwLock>>, + pub show: RwLock, + pub anonymous_joining: RwLock, + pub short_link: RwLock>, +} + +impl CallGroup { + pub fn new(call_id: Uuid, user: Arc) -> Self { + CallGroup { + call_id, + members: RwLock::new(vec![user]), + show: RwLock::new(true), + anonymous_joining: RwLock::new(false), + short_link: RwLock::new(None), + } + } + + pub async fn get_caller(&self, user_id: u64) -> Option> { + self.members + .read() + .await + .iter() + .find(|caller| caller.user_id == user_id) + .cloned() + } + + pub async fn is_anonymous(&self) -> bool { + *self.anonymous_joining.read().await + } + + pub async fn set_anonymous_joining(&self, enable: bool) { + *self.anonymous_joining.write().await = enable; + + let _ = call_util::set_room_metadata( + self.call_id, + format!("{{\"anonymous_joining\": \"{}\"}}", enable), + ) + .await; + + if self.short_link.read().await.is_none() { + let long_link = format!( + "https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}", + self.call_id, + env::var("ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0), + ); + let response_cv = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::shorten_link) + .add_data(DataTypes::link, DataValue::Str(long_link)), + Some(Duration::from_secs(20)), + ) + .await; + if let Ok(response) = response_cv { + *self.short_link.write().await = Some( + response + .get_data(DataTypes::link) + .as_str() + .unwrap() + .to_string(), + ); + log::info!( + "Shortened link for call {} is {}", + self.call_id, + self.short_link.read().await.as_ref().unwrap() + ); + } + } + } + + pub async fn create_anonymous_token(&self, user_id: u64) -> Option { + if self.is_anonymous().await { + if let Ok(token) = call_util::create_token(user_id, self.call_id, false) { + return Some(token); + } + } + None + } + + pub async fn remove_caller(&self, user_id: u64) { + let _ = call_util::remove_participant(self.call_id, user_id).await; + self.members + .write() + .await + .retain(|caller| caller.user_id != user_id); + } + + pub async fn get_short_link(self: Arc) -> Option { + self.short_link.read().await.clone() + } +} diff --git a/src/calls/call_manager.rs b/src/calls/call_manager.rs new file mode 100644 index 0000000..a17f347 --- /dev/null +++ b/src/calls/call_manager.rs @@ -0,0 +1,97 @@ +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::sync::Arc; +use uuid::Uuid; + +use crate::calls::{call_group::CallGroup, caller::Caller}; + +pub static CALL_GROUPS: Lazy>> = Lazy::new(|| DashMap::new()); +#[allow(dead_code)] +pub async fn get_call_invites(user_id: u64) -> Vec> { + let mut callers = Vec::new(); + for (_, cg) in CALL_GROUPS.clone().into_iter() { + let members = cg.members.read().await; + for member in members.iter() { + if member.user_id == user_id { + callers.push(member.clone()); + } + } + } + callers +} + +pub async fn get_call(call_id: Uuid) -> Option> { + if let Some(b) = CALL_GROUPS.get(&call_id) { + Some(b.clone()) + } else { + None + } +} + +pub async fn get_call_groups(user_id: u64) -> Vec> { + let mut call_groups = Vec::new(); + for (_, cg) in CALL_GROUPS.clone().into_iter() { + let is_member = { + let members = cg.members.read().await; + members.iter().any(|m| m.user_id == user_id) + }; + + if is_member { + call_groups.push(cg.clone()); + } + } + call_groups +} + +pub async fn get_call_token(user_id: u64, call_id: Uuid) -> Option { + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let mut members = cg.members.write().await; + + if let Some(member) = members.iter().find(|m| m.user_id == user_id) { + return Some(member.create_token()); + } + + let new_caller = Arc::new(Caller::new(user_id, call_id, false)); + let token = new_caller.create_token(); + + members.push(new_caller); + + return Some(token); + } + + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let cg_clone = cg.clone(); + + let mut members = cg_clone.members.write().await; + if let Some(member) = members.iter().find(|m| m.user_id == user_id) { + return Some(member.create_token()); + } + let new_caller = Arc::new(Caller::new(user_id, call_id, false)); + let token = new_caller.create_token(); + members.push(new_caller); + return Some(token); + } + + let caller = Arc::new(Caller::new(user_id, call_id, true)); + let call_group = CallGroup::new(call_id, caller.clone()); + + CALL_GROUPS.insert(call_id, Arc::new(call_group)); + + Some(caller.create_token()) +} + +pub async fn add_invite(call_id: Uuid, inviter_id: u64, invitee_id: u64) -> bool { + if let Some(cg) = CALL_GROUPS.get(&call_id) { + let mut members = cg.members.write().await; + + let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id); + + if is_inviter_member { + if !members.iter().any(|m| m.user_id == invitee_id) { + members.push(Arc::new(Caller::new(invitee_id, call_id, false))); + } + return true; + } + } + false +} diff --git a/src/calls/call_util.rs b/src/calls/call_util.rs new file mode 100644 index 0000000..57df005 --- /dev/null +++ b/src/calls/call_util.rs @@ -0,0 +1,151 @@ +use livekit_api::{ + access_token::{self}, + services::room::RoomClient, +}; +use livekit_protocol::Room; +use std::env; +use std::str::FromStr; +use std::time::Duration; +use uuid::Uuid; + +use crate::{calls::call_manager::CALL_GROUPS, log, log_err, util::logger::PrintType}; + +pub fn get_livekit() -> Result<(String, String, String), ()> { + let hostname = match env::var("LIVEKI_HOSTNAME") { + Ok(secret) => secret, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKI_HOSTNAME not set!"); + return Err(()); + } + }; + let api_key = match env::var("LIVEKIT_API_KEY") { + Ok(key) => key, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKIT_API_KEY not set!"); + return Err(()); + } + }; + let api_secret = match env::var("LIVEKIT_API_SECRET") { + Ok(secret) => secret, + Err(_) => { + log_err!(0, PrintType::General, "LIVEKIT_API_SECRET not set!"); + return Err(()); + } + }; + Ok((hostname, api_key, api_secret)) +} + +pub fn create_token(user_id: u64, call_id: Uuid, has_admin: bool) -> Result { + let (_, api_key, api_secret) = get_livekit()?; + + let token = access_token::AccessToken::with_api_key(&api_key, &api_secret) + .with_identity(&user_id.to_string()) + .with_grants(access_token::VideoGrants { + room_join: true, + room_admin: has_admin, + room: call_id.to_string(), + ..Default::default() + }) + .with_metadata(&format!("{{\"isAdmin\":{}}}", has_admin)) + .to_jwt(); + if let Ok(token) = token { + Ok(token) + } else { + Err(()) + } +} +#[allow(dead_code)] +pub async fn get_room(call_id: Uuid) -> Result<(RoomClient, Room), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + let rooms = room_service.list_rooms(Vec::new()).await; + if let Ok(rooms) = rooms { + for room in rooms { + if room.name == call_id.to_string() { + return Ok((room_service, room)); + } + } + } + } + return Err(()); +} + +pub async fn remove_participant(call_id: Uuid, user_id: u64) -> Result<(), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + if let Ok(_) = room_service + .remove_participant(&call_id.to_string(), &user_id.to_string()) + .await + { + return Ok(()); + } + } + return Err(()); +} + +#[allow(dead_code)] +pub async fn get_room_metadata(call_id: Uuid) -> Result { + if let Ok((_, room)) = get_room(call_id).await { + Ok(room.metadata) + } else { + Err(()) + } +} + +pub async fn set_room_metadata(call_id: Uuid, metadata: String) -> Result<(), ()> { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + if let Ok(_) = room_service + .update_room_metadata(&call_id.to_string(), &metadata) + .await + { + return Ok(()); + } + } + return Err(()); +} + +pub fn garbage_collect_calls() { + tokio::spawn(async move { + loop { + if let Ok((hostname, api_key, api_secret)) = get_livekit() { + let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret); + clean_calls(room_service).await; + } + tokio::time::sleep(Duration::from_secs(2)).await; + } + }); +} +pub async fn clean_calls(room_service: RoomClient) { + let rooms = room_service.list_rooms(Vec::new()).await.unwrap(); + let mut call_ids: Vec = Vec::new(); + let mut no_users: Vec = Vec::new(); + for room in rooms { + if let Ok(id) = Uuid::from_str(&room.name) { + if room.num_participants == 0 { + no_users.push(id); + } + call_ids.push(id); + } + } + let size_pre = CALL_GROUPS.len(); + for (id, _) in CALL_GROUPS.clone().into_iter() { + if !call_ids.contains(&id) { + CALL_GROUPS.remove(&id); + } + } + for (_, cg) in CALL_GROUPS.clone().into_iter() { + *cg.show.write().await = !no_users.contains(&cg.call_id); + } + + let size_post = CALL_GROUPS.len(); + if size_pre - size_post != 0 { + log!( + 0, + PrintType::Call, + "Cleaned {} calls, {} remaining", + size_pre - size_post, + size_post + ); + } +} diff --git a/src/calls/caller.rs b/src/calls/caller.rs new file mode 100644 index 0000000..74e0c3b --- /dev/null +++ b/src/calls/caller.rs @@ -0,0 +1,49 @@ +use std::time::{SystemTime, UNIX_EPOCH}; + +use tokio::sync::RwLock; +use uuid::Uuid; + +use crate::calls::call_util; + +pub struct Caller { + pub user_id: u64, + pub call_id: Uuid, + pub has_admin: bool, + pub timeout: RwLock, +} + +impl Caller { + pub fn new(user_id: u64, call_id: Uuid, has_admin: bool) -> Self { + Caller { + user_id, + call_id, + has_admin, + timeout: RwLock::new(0), + } + } + #[allow(dead_code)] + pub fn set_admin(&mut self, has_admin: bool) { + self.has_admin = has_admin; + } + pub fn has_admin(&self) -> bool { + self.has_admin + } + #[allow(dead_code)] + pub async fn is_timeouted(&self) -> bool { + *self.timeout.read().await + > SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64 + } + pub async fn set_timeout(&self, timeout: i64) { + *self.timeout.write().await = timeout; + } + pub fn create_token(&self) -> String { + if let Ok(token) = call_util::create_token(self.user_id, self.call_id, self.has_admin()) { + token + } else { + String::new() + } + } +} diff --git a/src/calls/mod.rs b/src/calls/mod.rs new file mode 100644 index 0000000..8413354 --- /dev/null +++ b/src/calls/mod.rs @@ -0,0 +1,4 @@ +pub mod call_group; +pub mod call_manager; +pub mod call_util; +pub mod caller; diff --git a/src/data/mod.rs b/src/data/mod.rs new file mode 100644 index 0000000..22d12a3 --- /dev/null +++ b/src/data/mod.rs @@ -0,0 +1 @@ +pub mod user; diff --git a/src/data/user.rs b/src/data/user.rs new file mode 100644 index 0000000..a175e6d --- /dev/null +++ b/src/data/user.rs @@ -0,0 +1,30 @@ +use strum::IntoEnumIterator; +use strum_macros::EnumIter; + +#[derive(Debug, Clone, PartialEq, EnumIter, Eq)] +#[allow(unused, non_camel_case_types)] +pub enum UserStatus { + user_offline, + user_online, + user_dnd, + user_idle, + user_wc, + user_borked, + iota_offline, + iota_online, + iota_borked, +} +#[allow(unused)] +impl UserStatus { + pub fn to_string(&self) -> String { + format!("{:?}", self) + } + pub fn from_str(s: &str) -> Option { + for sel in UserStatus::iter() { + if &sel.to_string() == s { + return Some(sel); + } + } + None + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fa511ea --- /dev/null +++ b/src/main.rs @@ -0,0 +1,51 @@ +mod anonymous_clients; +mod calls; +mod data; +mod omega; +mod rho; +mod util; + +use std::env; + +use dotenv::dotenv; +use once_cell::sync::Lazy; +use rustls::crypto::aws_lc_rs::default_provider; + +use crate::{ + calls::call_util::garbage_collect_calls, + omega::omega_connection::get_omega_connection, + rho::server::start, + util::{ + crypto_helper::{load_public_key, load_secret_key}, + logger::startup, + }, +}; + +static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); +pub fn get_private_key() -> x448::Secret { + load_secret_key(&*PRIVATE_KEY).unwrap() +} +static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); +pub fn get_public_key() -> x448::PublicKey { + load_public_key(&*PUBLIC_KEY).unwrap() +} + +#[tokio::main] +async fn main() { + if let Err(_) = default_provider().install_default() { + println!("Error loading Provider"); + return; + } + dotenv().ok(); + startup(); + + get_omega_connection(); + tokio::spawn(async move { + if let Err(e) = start(959).await { + log_err!(0, util::logger::PrintType::General, "{}", e); + } + }); + garbage_collect_calls(); + + tokio::signal::ctrl_c().await.unwrap(); +} diff --git a/src/omega/mod.rs b/src/omega/mod.rs new file mode 100644 index 0000000..cbe77d9 --- /dev/null +++ b/src/omega/mod.rs @@ -0,0 +1 @@ +pub mod omega_connection; diff --git a/src/omega/omega_connection.rs b/src/omega/omega_connection.rs new file mode 100644 index 0000000..ab84eb2 --- /dev/null +++ b/src/omega/omega_connection.rs @@ -0,0 +1,741 @@ +use crate::{ + data::user::UserStatus, + get_private_key, log, log_cv_in, log_cv_out, log_err, log_in, + rho::rho_manager::{self, RHO_CONNECTIONS, connection_count}, + util::{ + crypto_helper::{decrypt_b64, secret_key_to_base64}, + file_util::load_file_vec, + logger::PrintType, + }, +}; +use dashmap::DashMap; +use once_cell::sync::Lazy; +use std::{collections::HashMap, env, sync::Arc, time::Duration}; +use tokio::{ + sync::{Mutex, RwLock, mpsc, watch}, + task::JoinHandle, + time::{Instant, sleep}, +}; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +// ============================================================================ +// Configuration +// ============================================================================ + +const OMEGA_HOST_DEFAULT: &str = "methanium.net"; +const OMEGA_PORT_DEFAULT: u16 = 9187; +const RECONNECT_DELAY: Duration = Duration::from_secs(5); +const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); +const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); +const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); +const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); +const TASK_MAX_AGE: Duration = Duration::from_secs(60); + +// ============================================================================ +// Waiting Task System +// ============================================================================ + +pub struct WaitingTask { + pub task: Box, CommunicationValue) -> bool + Send + Sync>, + pub inserted_at: Instant, +} + +pub static WAITING_TASKS: Lazy> = Lazy::new(DashMap::new); + +pub fn start_task_cleanup_loop() { + tokio::spawn(async { + loop { + sleep(TASK_CLEANUP_INTERVAL).await; + WAITING_TASKS.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE); + } + }); +} + +// ============================================================================ +// Connection State +// ============================================================================ + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ConnectionState { + Disconnected, + Connecting, + Connected { identified: bool }, +} + +#[allow(unused_variables)] +impl ConnectionState { + pub fn is_connected(&self) -> bool { + match self { + ConnectionState::Connected { identified } => true, + _ => false, + } + } + + #[allow(dead_code)] + pub fn is_identified(&self) -> bool { + match self { + ConnectionState::Connected { identified: true } => true, + _ => false, + } + } +} + +// ============================================================================ +// Omega Connection (Client-side with auto-reconnect) +// ============================================================================ +#[allow(dead_code)] +pub struct OmegaConnection { + state: Arc>, + sender: Arc>>>, + connection_loop_handle: Arc>>>, + host: String, + port: u16, + server_cert: Vec, + last_ping: Arc>, + heartbeat_handle: Arc>>>, + message_send_times: Arc>>, + pub connection_id: Uuid, + shutdown_tx: Arc>>>, + // Track if we should reconnect on close + reconnect_on_close: Arc>, +} + +impl OmegaConnection { + pub fn new() -> Self { + Self::with_host(OMEGA_HOST_DEFAULT, OMEGA_PORT_DEFAULT) + } + + pub fn with_host(host: &str, port: u16) -> Self { + // Load server certificate from default location + let server_cert = + load_file_vec("certs", "cert.pem").expect("Failed to load server certificate"); + + Self::with_host_and_cert(host, port, server_cert) + } + + // New constructor that accepts certificate directly + pub fn with_host_and_cert(host: &str, port: u16, server_cert: Vec) -> Self { + let (shutdown_tx, _) = watch::channel(false); + + OmegaConnection { + state: Arc::new(RwLock::new(ConnectionState::Disconnected)), + sender: Arc::new(RwLock::new(None)), + connection_loop_handle: Arc::new(Mutex::new(None)), + host: host.to_string(), + port, + server_cert, + last_ping: Arc::new(Mutex::new(-1)), + heartbeat_handle: Arc::new(Mutex::new(None)), + message_send_times: Arc::new(Mutex::new(HashMap::new())), + connection_id: Uuid::new_v4(), + shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), + reconnect_on_close: Arc::new(RwLock::new(true)), + } + } + + // ------------------------------------------------------------------------- + // Connection Management + // ------------------------------------------------------------------------- + + pub async fn start(self: Arc) { + // Cancel any existing connection loop + if let Some(handle) = self.connection_loop_handle.lock().await.take() { + handle.abort(); + } + + // Set reconnect flag + *self.reconnect_on_close.write().await = true; + + let self_clone = self.clone(); + let handle = tokio::spawn(async move { + self_clone.connection_loop().await; + }); + + *self.connection_loop_handle.lock().await = Some(handle); + } + + #[allow(dead_code)] + pub async fn stop(&self) { + // Disable reconnection + *self.reconnect_on_close.write().await = false; + + if let Some(tx) = self.shutdown_tx.lock().await.take() { + let _ = tx.send(true); + } + + if let Some(handle) = self.connection_loop_handle.lock().await.take() { + handle.abort(); + } + + if let Some(handle) = self.heartbeat_handle.lock().await.take() { + handle.abort(); + } + + // Close sender if connected + if let Some(sender) = self.sender.read().await.as_ref() { + sender.close(); + } + + *self.state.write().await = ConnectionState::Disconnected; + *self.sender.write().await = None; + } + + async fn connection_loop(self: Arc) { + let mut reconnect_delay = RECONNECT_DELAY; + let shutdown_rx = self.shutdown_tx.lock().await.as_ref().unwrap().subscribe(); + let mut shutdown_rx = shutdown_rx; + + loop { + if *shutdown_rx.borrow() { + log_in!(0, PrintType::Omega, "Connection loop shutting down"); + break; + } + + // Check if reconnection is enabled + if !*self.reconnect_on_close.read().await { + log_in!(0, PrintType::Omega, "Reconnection disabled, exiting loop"); + break; + } + + match self.clone().connect_once().await { + Ok(()) => { + // Connection closed gracefully, check if we should reconnect + if *self.reconnect_on_close.read().await { + log_err!( + 0, + PrintType::Omega, + "Connection lost, reconnecting in {:?}...", + reconnect_delay + ); + } else { + log_in!(0, PrintType::Omega, "Connection closed, not reconnecting"); + break; + } + } + Err(e) => { + log_err!( + 0, + PrintType::Omega, + "Connection failed: {}, retrying in {:?}...", + e, + reconnect_delay + ); + } + } + + tokio::select! { + _ = sleep(reconnect_delay) => {} + _ = shutdown_rx.changed() => { + if *shutdown_rx.borrow() { + break; + } + } + } + + reconnect_delay = std::cmp::min(reconnect_delay * 2, MAX_RECONNECT_DELAY); + } + } + + async fn connect_once(self: Arc) -> Result<(), String> { + *self.state.write().await = ConnectionState::Connecting; + + let addr_str = format!("https://{}:{}", self.host, self.port); + + let (sender, mut receiver) = ttp_native::client::connect(&addr_str, None) + .await + .map_err(|e| format!("Connection failed: {}", e))?; + + log_in!( + 0, + PrintType::Omega, + "QUIC connection established to {}", + addr_str + ); + + // Store sender + let sender_arc = Arc::new(sender); + *self.sender.write().await = Some(sender_arc.clone()); + *self.state.write().await = ConnectionState::Connected { identified: false }; + + // Get handle for close monitoring + let sender_handle = sender_arc.handle().clone(); + + // Start read loop + let read_self = self.clone(); + let read_handle = tokio::spawn(async move { + read_self.read_loop(&mut receiver, sender_handle).await; + }); + + // Send identification + self.send_identification().await; + + // Start heartbeat + let heartbeat_self = self.clone(); + let heartbeat_handle = tokio::spawn(async move { + heartbeat_self.heartbeat_loop().await; + }); + *self.heartbeat_handle.lock().await = Some(heartbeat_handle); + + // Wait for read loop to complete (connection closed) + let result = read_handle.await; + + // Cleanup + *self.sender.write().await = None; + *self.state.write().await = ConnectionState::Disconnected; + + if let Some(handle) = self.heartbeat_handle.lock().await.take() { + handle.abort(); + } + + match result { + Ok(()) => { + // Check if we should reconnect + if *self.reconnect_on_close.read().await { + Err("Connection closed, will reconnect".to_string()) + } else { + Ok(()) + } + } + Err(e) => Err(format!("Read loop error: {}", e)), + } + } + + // ------------------------------------------------------------------------- + // Identification Handshake + // ------------------------------------------------------------------------- + + async fn send_identification(&self) { + let id = rand_u32(); + + let omikron_id = env::var("ID") + .unwrap_or("0".to_string()) + .parse::() + .unwrap_or(0); + + let identify_msg = CommunicationValue::new(CommunicationType::identification) + .with_id(id) + .add_data(DataTypes::omikron_id, DataValue::Number(omikron_id)); + + WAITING_TASKS.insert( + id, + WaitingTask { + task: Box::new(|selfc, cv| { + if cv.is_type(CommunicationType::error_not_found) { + log_err!( + 0, + PrintType::Omega, + "Identification failed: Omikron ID not found" + ); + return false; + } + if !cv.is_type(CommunicationType::challenge) { + return false; + } + + tokio::spawn(async move { + if let Err(e) = selfc.handle_challenge(cv).await { + log_err!(0, PrintType::Omega, "Challenge handling failed: {}", e); + } + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(&identify_msg).await; + } + + async fn handle_challenge(&self, cv: CommunicationValue) -> Result<(), String> { + let challenge = cv + .get_data(DataTypes::challenge) + .as_str() + .ok_or("Challenge not found")?; + + let server_pub_key = cv + .get_data(DataTypes::public_key) + .as_str() + .ok_or("Public key not found")?; + + let decrypted_challenge = decrypt_b64( + &secret_key_to_base64(&get_private_key()), + server_pub_key, + challenge, + ) + .map_err(|e| format!("Decryption failed: {:?}", e))?; + + let response_msg = CommunicationValue::new(CommunicationType::challenge_response) + .with_id(cv.get_id()) + .add_data(DataTypes::challenge, DataValue::Str(decrypted_challenge)); + + let response_id = response_msg.get_id(); + + WAITING_TASKS.insert( + response_id, + WaitingTask { + task: Box::new(|selfc, final_cv| { + if !final_cv.is_type(CommunicationType::identification_response) { + log_err!(0, PrintType::Omega, "Expected identification_response"); + return false; + } + + let accepted = final_cv + .get_data(DataTypes::accepted) + .as_bool() + .unwrap_or(false); + + if !accepted { + log_err!(0, PrintType::Omega, "Omega did not accept identification"); + return false; + } + + tokio::spawn(async move { + let mut state = selfc.state.write().await; + if let ConnectionState::Connected { identified: _ } = *state { + *state = ConnectionState::Connected { identified: true }; + } + drop(state); + + selfc.sync_client_iota_status().await; + }); + + log!(0, PrintType::Omega, "Successfully identified with Omega"); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(&response_msg).await; + Ok(()) + } + + async fn sync_client_iota_status(self: Arc) { + let mut connected_iota_ids: Vec = Vec::new(); + let mut connected_user_ids: Vec = Vec::new(); + + let rho_connections_reader = RHO_CONNECTIONS.read().await; + + for iota_id in rho_connections_reader.keys() { + connected_iota_ids.push(DataValue::Number(*iota_id)); + } + + for rho in rho_connections_reader.values() { + for client_conn in rho.get_client_connections().await { + connected_user_ids.push(DataValue::Number(client_conn.get_user_id().await as i64)); + } + } + + drop(rho_connections_reader); + + let sync_msg = CommunicationValue::new(CommunicationType::sync_client_iota_status) + .add_data(DataTypes::iota_ids, DataValue::Array(connected_iota_ids)) + .add_data(DataTypes::user_ids, DataValue::Array(connected_user_ids)) + .add_data( + DataTypes::rho_connections, + DataValue::Number(connection_count().await as i64), + ); + + self.send_message(&sync_msg).await; + } + + // ------------------------------------------------------------------------- + // Read Loop & Heartbeat + // ------------------------------------------------------------------------- + + async fn read_loop( + self: Arc, + receiver: &mut Receiver, + sender_handle: Arc, + ) { + // Monitor both receiver and sender handle for close + let mut close_rx = sender_handle.subscribe_close(); + + loop { + tokio::select! { + result = receiver.receive() => { + match result { + Ok(cv) => { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_in!(PrintType::Omega, &cv); + } + + if cv.is_type(CommunicationType::pong) || cv.is_type(CommunicationType::ping) { + self.handle_pong(&cv).await; + continue; + } + + let msg_id = cv.get_id(); + if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) { + if (task.task)(self.clone(), cv.clone()) { + continue; + } + } + + if cv.is_type(CommunicationType::iota_user_data) { + if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { + let mut user_ids: Vec = Vec::new(); + for value in users { + if let DataValue::Number(user_id) = value { + user_ids.push(*user_id as u64); + } + } + let connections = crate::rho::rho_manager::RHO_CONNECTIONS.read().await; + if let Some(iota_id) = cv.get_data(DataTypes::iota_id).as_number() { + if let Some(rho) = connections.get(&iota_id) { + rho.get_iota_connection().set_user_ids(user_ids).await; + } + } else { + for rho in connections.values() { + rho.get_iota_connection().set_user_ids(user_ids.clone()).await; + } + } + } + } + } + Err(e) => { + log_err!(0, PrintType::Omega, "Receive error: {}", e); + break; + } + } + } + _ = close_rx.changed() => { + // Connection was closed by either side + if let Some(reason) = close_rx.borrow().clone() { + log_err!(0, PrintType::Omega, "Connection closed: {:?}", reason); + } else { + log_in!(0, PrintType::Omega, "Connection closed cleanly"); + } + break; + } + } + } + } + + async fn heartbeat_loop(self: Arc) { + loop { + sleep(HEARTBEAT_INTERVAL).await; + + // Check if still connected + if !self.state.read().await.is_connected() { + break; + } + + // Check if sender is closed + if let Some(sender) = self.sender.read().await.as_ref() { + if sender.is_closed() { + log_err!(0, PrintType::Omega, "Sender closed, stopping heartbeat"); + break; + } + } else { + break; + } + + self.send_ping().await; + } + } + + async fn send_ping(&self) { + let ping = CommunicationValue::new(CommunicationType::ping).add_data( + DataTypes::send_time, + DataValue::Number( + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64, + ), + ); + self.send_message(&ping).await; + } + + async fn handle_pong(&self, cv: &CommunicationValue) { + let timestamp = cv + .get_data(DataTypes::send_time) + .as_number() + .unwrap_or_else(|| { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() as i64 + }); + + *self.last_ping.lock().await = timestamp; + } + + // ------------------------------------------------------------------------- + // Public API + // ------------------------------------------------------------------------- + + pub async fn send_message(&self, cv: &CommunicationValue) { + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Omega, &cv); + } + + let sender_guard = self.sender.read().await; + if let Some(sender) = sender_guard.as_ref() { + // Check if closed before sending + if sender.is_closed() { + log_err!(0, PrintType::Omega, "Cannot send: connection closed"); + drop(sender_guard); + // Trigger reconnection by closing the connection state + if let Some(sender) = self.sender.write().await.take() { + sender.close(); + } + return; + } + + let sender_clone = Arc::clone(sender); + drop(sender_guard); + + if let Err(e) = sender_clone.send(cv).await { + log_err!(0, PrintType::Omega, "Send failed: {}", e); + } + } else { + log_err!(0, PrintType::Omega, "Cannot send: not connected"); + } + } + + pub async fn await_connection(&self, timeout_duration: Option) -> Result<(), String> { + if self.state.read().await.is_connected() { + return Ok(()); + } + + let timeout = timeout_duration.unwrap_or(CONNECTION_TIMEOUT); + let start = Instant::now(); + + loop { + if self.state.read().await.is_connected() { + return Ok(()); + } + + if start.elapsed() >= timeout { + return Err(format!( + "Connection not established within {} seconds", + timeout.as_secs() + )); + } + + sleep(Duration::from_millis(100)).await; + } + } + + pub async fn await_response( + &self, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + self.await_connection(timeout_duration).await?; + + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + WAITING_TASKS.insert( + msg_id, + WaitingTask { + task: Box::new(move |_, response_cv| { + let inner_tx = tx.clone(); + tokio::spawn(async move { + let _ = inner_tx.send(response_cv).await; + }); + true + }), + inserted_at: Instant::now(), + }, + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(_) => Err("Channel closed".to_string()), + Err(_) => { + WAITING_TASKS.remove(&msg_id); + Err("Request timed out".to_string()) + } + } + } + + #[allow(dead_code)] + pub async fn is_connected(&self) -> bool { + self.state.read().await.is_connected() + } + #[allow(dead_code)] + pub async fn is_identified(&self) -> bool { + self.state.read().await.is_identified() + } + + #[allow(dead_code)] + pub async fn close_iota(iota_id: i64) { + let cv = CommunicationValue::new(CommunicationType::iota_disconnected) + .add_data(DataTypes::iota_id, DataValue::Number(iota_id)); + OMEGA_CONNECTION.send_message(&cv).await; + } + + pub async fn client_changed(_iota_id: i64, user_id: i64, state: UserStatus) { + let msg_type = match state { + UserStatus::iota_offline => CommunicationType::user_disconnected, + UserStatus::user_offline => CommunicationType::user_disconnected, + _ => CommunicationType::user_connected, + }; + + let cv = CommunicationValue::new(msg_type) + .add_data(DataTypes::user_id, DataValue::Number(user_id)); + OMEGA_CONNECTION.send_message(&cv).await; + } + + pub async fn user_states(user_id: i64, user_ids: Vec) { + let user_ids = user_ids.iter().map(|v| DataValue::Number(*v)).collect(); + + let cv = CommunicationValue::new(CommunicationType::get_states) + .add_data(DataTypes::user_ids, DataValue::Array(user_ids)); + let msg_id = cv.get_id(); + + WAITING_TASKS.insert( + msg_id, + WaitingTask { + task: Box::new( + move |_: Arc, response: CommunicationValue| { + tokio::spawn(async move { + let rho = rho_manager::get_rho_con_for_user(user_id).await; + if let Some(rho) = rho { + for client in rho.get_client_connections_for_user(user_id).await { + client.send_message(&response).await; + } + } + }); + true + }, + ), + inserted_at: Instant::now(), + }, + ); + + OMEGA_CONNECTION.send_message(&cv).await; + } +} + +// ============================================================================ +// Global Instance +// ============================================================================ + +static OMEGA_CONNECTION: Lazy> = Lazy::new(|| { + let conn = Arc::new(OmegaConnection::new()); + + // Start the connection manager immediately + let conn_clone = conn.clone(); + tokio::spawn(async move { + conn_clone.start().await; + }); + + start_task_cleanup_loop(); + + conn +}); + +pub fn get_omega_connection() -> Arc { + OMEGA_CONNECTION.clone() +} diff --git a/src/omega/ping_pong_task.rs b/src/omega/ping_pong_task.rs new file mode 100644 index 0000000..c7fc0b9 --- /dev/null +++ b/src/omega/ping_pong_task.rs @@ -0,0 +1,41 @@ +use std::time::Duration; +use tokio::time::Instant; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue, rand_u32}; + +use crate::omega::omega_connection::OmegaConnection; + +const PING_TIMEOUT: Duration = Duration::from_secs(30); + +impl OmegaConnection { + pub async fn send_ping(&self) { + let id = rand_u32(); + let send_time = Instant::now(); + + let mut message_send_times = self.message_send_times.lock().await; + message_send_times.retain(|_uuid, time| time.elapsed() < PING_TIMEOUT); + message_send_times.insert(id as i64, send_time); + + self.send_ping_message(id).await; + } + + pub async fn send_ping_message(&self, id: u32) { + let ping_message = CommunicationValue::new(CommunicationType::ping) + .with_id(id) + .add_data( + DataTypes::last_ping, + DataValue::Number(self.last_ping.lock().await.unwrap()), + ); + + self.send_message(&ping_message).await; + } + + /// Handles incoming pong and calculates latency + pub async fn handle_pong(&self, cv: &CommunicationValue, _log: bool) { + let id = cv.get_id(); + let mut message_send_times = self.message_send_times.lock().await; + if let Some(send_time) = message_send_times.remove(&(id as i64)) { + let ping = Instant::now().duration_since(send_time).as_millis() as i64; + *self.last_ping.lock().await = ping; + } + } +} diff --git a/src/rho/client_connection.rs b/src/rho/client_connection.rs new file mode 100644 index 0000000..ebaabf2 --- /dev/null +++ b/src/rho/client_connection.rs @@ -0,0 +1,583 @@ +use crate::anonymous_clients::anonymous_manager; +use crate::calls::{call_manager, call_util}; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::rho::{rho_connection::RhoConnection, rho_manager}; +use crate::util::logger::PrintType; +use crate::{data::user::UserStatus, omega::omega_connection::OmegaConnection}; +use crate::{log_cv_in, log_cv_out, log_err, log_in, log_out}; +use std::str::FromStr; +use std::sync::Arc; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; +use uuid::Uuid; + +pub struct ClientConnection { + pub user_id: u64, + + pub sender: Arc, + pub receiver: Arc, + pub ping: Arc>, + pub_key: Arc>>>, + pub rho_connection: Arc>>>, + pub interested_users: Arc>>, + is_open: Arc>, +} + +impl ClientConnection { + pub async fn from_general(general: Arc, user_id: u64) -> Arc { + Arc::new(Self { + ping: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: general.rho_connection.clone(), + interested_users: Arc::new(RwLock::new(Vec::new())), + is_open: Arc::new(RwLock::new(true)), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + user_id: user_id, + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + while let Ok(cv) = self_clone.receiver.receive().await { + self_clone.clone().handle_message(cv).await; + } + self_clone.handle_close().await; + }); + + let self_clone2 = self.clone(); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_millis(50)).await; + if self_clone2.get_rho_connection().await.is_none() { + self_clone2 + .send_error_response(0, CommunicationType::error) + .await; + } + }); + } + + /// Get the user ID + pub async fn get_user_id(&self) -> u64 { + self.user_id + } + + /// Get current ping + pub async fn get_ping(&self) -> i64 { + *self.ping.read().await + } + + /// Get RhoConnection if available + pub async fn get_rho_connection(&self) -> Option> { + self.rho_connection.read().await.clone() + } + + /// Send a CommunicationValue to the client + pub async fn send_message(self: Arc, cv: &CommunicationValue) { + if !*self.is_open.read().await { + log_out!( + self.user_id as i64, + PrintType::Client, + "Attempted to send message to a closed connection." + ); + return; + } + if !cv.is_type(CommunicationType::pong) && !cv.is_type(CommunicationType::ping) { + log_cv_out!(PrintType::Client, &cv); + } + let _ = self.sender.send(&cv).await; + } + + /// Handle incoming message from client + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + tokio::spawn(async move { + if cv.is_type(CommunicationType::ping) { + self.handle_ping(cv).await; + return; + } + log_cv_in!(PrintType::Client, cv); + + // Handle client status changes + if cv.is_type(CommunicationType::client_changed) { + self.handle_client_changed(cv).await; + return; + } + + // Handle call invites + if cv.is_type(CommunicationType::call_invite) { + self.handle_call_invite(cv).await; + return; + } + + // Handle get call requests + if cv.is_type(CommunicationType::call_token) { + self.handle_get_call(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_disconnect_user) { + self.handle_call_disconnect_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_timeout_user) { + self.handle_call_timeout_user(cv).await; + return; + } + + if cv.is_type(CommunicationType::call_set_anonymous_joining) { + self.handle_call_set_anonymous_joining(cv).await; + return; + } + if cv.is_type(CommunicationType::get_user_data) { + if let Some(anonymous) = { + if let Some(user_id) = cv.get_data(DataTypes::user_id).as_number() { + anonymous_manager::get_anonymous_user(user_id as u64).await + } else if let Some(username) = cv.get_data(DataTypes::username).as_str() { + anonymous_manager::get_anonymous_user_by_name(username.to_string()).await + } else { + None + } + } { + let response = CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.get_id()) + .add_data( + DataTypes::username, + DataValue::Str(anonymous.get_user_name().await), + ) + .add_data( + DataTypes::user_id, + DataValue::Number(anonymous.get_user_id() as i64), + ) + .add_data( + DataTypes::display, + DataValue::Str(anonymous.get_display_name().await), + ) + .add_data( + DataTypes::avatar, + DataValue::Str(anonymous.get_avatar().await), + ) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(&response).await; + + return; + } + } + + if cv.is_type(CommunicationType::change_user_data) + || cv.is_type(CommunicationType::read_notification) + || cv.is_type(CommunicationType::get_notifications) + || cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::delete_user) + { + let sender = self.get_user_id().await; + self.handle_omega_forward(cv.with_sender(sender as u64)) + .await; + return; + } + // Forward other messages to Iota + self.forward_to_iota(cv).await; + }); + } + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let client_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + client_for_closure.send_message(&response_cv).await; + } + }); + } + + /// Handle ping message + async fn handle_ping(self: Arc, cv: CommunicationValue) { + // Update our ping if provided + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + let current = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + let mut ping_guard = self.ping.write().await; + *ping_guard = current as i64 - last_ping; + } + + // Get Iota ping from RhoConnection + let iota_ping = if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.get_iota_connection().get_ping().await + } else { + -1 + }; + + // Send pong response + let response = CommunicationValue::new(CommunicationType::pong) + .with_id(cv.get_id()) + .add_data(DataTypes::ping_iota, DataValue::Number(iota_ping)); + + self.send_message(&response).await; + } + + /// Handle client status change + async fn handle_client_changed(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id().await; + if let DataValue::Str(_status_str) = cv.get_data(DataTypes::user_state) { + let user_status = UserStatus::user_online; + if let Some(rho_conn) = self.get_rho_connection().await { + OmegaConnection::client_changed( + rho_conn.get_iota_id().await as i64, + user_id as i64, + user_status, + ) + .await; + } + } + } + + /// Handle call invite + async fn handle_call_invite(self: Arc, cv: CommunicationValue) { + let receiver_id: i64 = cv.get_data(DataTypes::receiver_id).as_number().unwrap_or(0); + if receiver_id == 0 { + self.send_error_response(cv.get_id(), CommunicationType::error_no_user_id) + .await; + return; + } + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + }, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error_no_call_id) + .await; + return; + } + }; + + let invited = call_manager::add_invite(call_id, self.user_id, receiver_id as u64).await; + if !invited { + self.send_error_response(cv.get_id(), CommunicationType::error_invalid_call_id) + .await; + return; + } + + // Find target RhoConnection + let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await { + Some(rho) => rho, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + // Get sender user ID + let sender_id = self.get_user_id().await; + + // Create and send call distribution message + let forward = CommunicationValue::new(CommunicationType::call_invite) + .with_receiver(receiver_id as u64) + .with_sender(sender_id as u64) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data( + DataTypes::receiver_id, + DataValue::Str(receiver_id.to_string()), + ) + .add_data(DataTypes::sender_id, DataValue::Str(sender_id.to_string())); + + target_rho.message_to_client(forward).await; + + let response = CommunicationValue::new(CommunicationType::success).with_id(cv.get_id()); + self.send_message(&response).await; + } + + /// Handle get call request + async fn handle_get_call(self: Arc, cv: CommunicationValue) { + let user_id = self.get_user_id().await; + + let call_id = match cv.get_data(DataTypes::call_id) { + DataValue::Str(id_str) => match Uuid::parse_str(id_str.as_str()) { + Ok(id) => id, + Err(_) => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }, + _ => { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + }; + + if let Some(token) = call_manager::get_call_token(user_id, call_id).await { + let response = CommunicationValue::new(CommunicationType::call_token) + .with_id(cv.get_id()) + .with_receiver(user_id as u64) + .add_data(DataTypes::call_token, DataValue::Str(token)); + self.send_message(&response).await; + } else { + self.send_error_response(cv.get_id(), CommunicationType::error) + .await; + return; + } + } + async fn handle_call_timeout_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + let untill = cv.get_data(DataTypes::untill).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + let _ = call_util::remove_participant(call_id, user_id as u64).await; + call.get_caller(user_id as u64) + .await + .unwrap() + .set_timeout(untill) + .await; + } + } + } + async fn handle_call_disconnect_user(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let user_id = cv.get_data(DataTypes::user_id).as_number().unwrap_or(0); + + let call = call_manager::get_call(call_id).await; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + call.remove_caller(user_id as u64).await; + } + } + } + async fn handle_call_set_anonymous_joining(self: Arc, cv: CommunicationValue) { + let call_id = + Uuid::from_str(cv.get_data(DataTypes::call_id).as_str().unwrap_or("")).unwrap(); + let enable = cv.get_data(DataTypes::enabled).as_bool().unwrap_or(true); + + let call = call_manager::get_call(call_id).await; + + let mut short_link = None; + if let Some(call) = call { + if call + .get_caller(self.get_user_id().await) + .await + .unwrap() + .has_admin() + { + call.set_anonymous_joining(enable).await; + } + short_link = call.get_short_link().await; + } + let mut response_cv = + CommunicationValue::new(CommunicationType::call_set_anonymous_joining) + .with_id(cv.get_id()) + .add_data(DataTypes::call_id, DataValue::Str(call_id.to_string())) + .add_data(DataTypes::enabled, DataValue::Bool(enable)); + if let Some(short_link) = short_link { + response_cv = response_cv.add_data(DataTypes::link, DataValue::Str(short_link)); + } + self.send_message(&response_cv).await; + } + + /// Forward message to Iota + async fn forward_to_iota(self: Arc, cv: CommunicationValue) { + let sender_user_id = self.get_user_id().await; + let msg_id = cv.get_id(); + let msg_type = cv.get_type(); + + log_in!( + sender_user_id as i64, + PrintType::Client, + "Forwarding client->iota: sender={} type={:?} id={} receiver={}", + sender_user_id, + msg_type, + msg_id, + cv.get_receiver() + ); + + if cv.is_type(CommunicationType::add_conversation) + && cv + .get_data(DataTypes::chat_partner_id) + .as_number() + .is_none() + { + let chat_partner_name = cv + .get_data(DataTypes::chat_partner_name) + .as_str() + .unwrap_or("") + .to_string(); + + if anonymous_manager::get_anonymous_user_by_name(chat_partner_name.to_string()) + .await + .is_some() + { + self.send_error_response(cv.get_id(), CommunicationType::error_anonymous) + .await; + return; + } + + let load_uuid_response = get_omega_connection() + .await_response( + &CommunicationValue::new(CommunicationType::get_user_data) + .with_id(cv.clone().get_id()) + .add_data( + DataTypes::username, + DataValue::Str(chat_partner_name.clone()), + ), + Some(Duration::from_secs(20)), + ) + .await; + let chat_partner_id = { + if let Ok(load_uuid_response) = load_uuid_response { + load_uuid_response.get_data(DataTypes::user_id).clone() + } else { + DataValue::Null + } + }; + + if let Some(rho_conn) = self.get_rho_connection().await { + let iota_id = rho_conn.get_iota_id().await; + log_in!( + sender_user_id as i64, + PrintType::Client, + "Resolved rho for add_conversation: sender={} -> iota_id={} id={}", + sender_user_id, + iota_id, + msg_id + ); + + let updated_cv = cv + .with_sender(sender_user_id as u64) + .add_data(DataTypes::chat_partner_id, chat_partner_id); + rho_conn.message_to_iota(updated_cv).await; + } else { + log_err!( + sender_user_id as i64, + PrintType::Client, + "No rho/iota mapping found for add_conversation sender={} type={:?} id={}", + sender_user_id, + msg_type, + msg_id + ); + } + return; + } + + if let Some(rho_conn) = self.get_rho_connection().await { + let iota_id = rho_conn.get_iota_id().await; + log_in!( + sender_user_id as i64, + PrintType::Client, + "Resolved rho for forward: sender={} -> iota_id={} type={:?} id={}", + sender_user_id, + iota_id, + msg_type, + msg_id + ); + + let updated_cv = cv.with_sender(sender_user_id as u64); + rho_conn.message_to_iota(updated_cv).await; + } else { + log_err!( + sender_user_id as i64, + PrintType::Client, + "No rho/iota mapping found for sender={} type={:?} id={}", + sender_user_id, + msg_type, + msg_id + ); + self.send_error_response(msg_id, CommunicationType::error) + .await; + } + } + + /// Send error response + async fn send_error_response(self: Arc, message_id: u32, error_type: CommunicationType) { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send_message(&error).await; + } + + /// Close the connection + pub async fn close(&self) { + let mut is_open_guard = self.is_open.write().await; + if !*is_open_guard { + return; + } + *is_open_guard = false; + + let _ = self.sender.close(); + } + + /// Set interested users list + pub async fn set_interested_users(self: Arc, interested_ids: Vec) { + let mut interested_guard = self.interested_users.write().await; + *interested_guard = interested_ids; + } + #[allow(dead_code)] + pub async fn get_interested_users(self: Arc) -> Vec { + let interested_guard = self.interested_users.read().await; + interested_guard.clone() + } + + /// Check if interested in a user and send notification + #[allow(dead_code)] + pub async fn are_you_interested(self: Arc, user_id: i64) { + let interested_guard = self.clone().get_interested_users().await; + if interested_guard.contains(&user_id) { + let notification = CommunicationValue::new(CommunicationType::client_changed) + .add_data(DataTypes::user_id, DataValue::Str(user_id.to_string())) + .add_data(DataTypes::user_state, DataValue::Str("online".to_string())); + + self.send_message(¬ification).await; + } + } + + /// Handle connection close + pub async fn handle_close(&self) { + let user_id = self.get_user_id().await; + if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id as i64).await { + rho_conn + .close_client_connection(Arc::new(self.clone())) + .await; + } + } +} + +// Implement Clone to make it easier to work with Arc +impl Clone for ClientConnection { + fn clone(&self) -> Self { + Self { + sender: Arc::clone(&self.sender), + receiver: Arc::clone(&self.receiver), + user_id: self.user_id, + ping: Arc::clone(&self.ping), + pub_key: Arc::clone(&self.pub_key), + rho_connection: Arc::clone(&self.rho_connection), + interested_users: Arc::clone(&self.interested_users), + is_open: Arc::clone(&self.is_open), + } + } +} diff --git a/src/rho/connection.rs b/src/rho/connection.rs new file mode 100755 index 0000000..ca746f0 --- /dev/null +++ b/src/rho/connection.rs @@ -0,0 +1,358 @@ +use rand::{Rng, distributions::Alphanumeric}; +use std::{sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; +use ttp_native::{Receiver, Sender}; + +use crate::{ + anonymous_clients::anonymous_client_connection::AnonymousClientConnection, + get_private_key, get_public_key, log_cv_in, log_cv_out, log_err, log_in, log_out, + omega::omega_connection::get_omega_connection, + rho::{ + client_connection::ClientConnection, iota_connection::IotaConnection, + rho_connection::RhoConnection, rho_manager, + }, + util::{ + crypto_helper::{load_public_key, public_key_to_base64}, + crypto_util::{DataFormat, SecurePayload}, + logger::PrintType, + }, +}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[allow(dead_code)] +pub enum ConnectionKind { + Client, + Iota, + AnonymousClient, + Phi, +} + +pub struct GeneralConnection { + pub sender: Arc, + pub receiver: Arc, + + identified: Arc>, + challenged: Arc>, + challenge: Arc>, + + connection_kind: Arc>>, + pub rho_connection: Arc>>>, + id: Arc>, + + pub_key: Arc>>>, +} +impl GeneralConnection { + pub fn new(sender: Sender, receiver: Receiver) -> Arc { + Arc::new(Self { + sender: Arc::new(sender), + receiver: Arc::new(receiver), + identified: Arc::new(RwLock::new(false)), + challenged: Arc::new(RwLock::new(false)), + challenge: Arc::new(RwLock::new(String::new())), + connection_kind: Arc::new(RwLock::new(None)), + rho_connection: Arc::new(RwLock::new(None)), + id: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + }) + } +} +impl GeneralConnection { + pub async fn handle(self: Arc) { + log_in!(0, PrintType::General, "General connection handler started"); + + loop { + let cv = match self.receiver.receive().await { + Ok(v) => v, + Err(_) => { + break; + } + }; + + log_cv_in!(cv); + + if !*self.identified.read().await { + self.handle_identification(cv).await; + continue; + } + + if !*self.challenged.read().await { + self.handle_challenge_response(cv).await; + } + + if *self.challenged.read().await { + let self_clone = self.clone(); + tokio::spawn(async move { + self_clone.migrate().await; + }); + break; + } + } + + log_out!(0, PrintType::General, "General connection handler stopped"); + } + async fn handle_identification(self: &Arc, cv: CommunicationValue) { + if !cv.is_type(CommunicationType::identification) { + return; + } + + if let DataValue::Number(iota_id) = cv.get_data(DataTypes::iota_id) { + *self.id.write().await = *iota_id as u64; + *self.connection_kind.write().await = Some(ConnectionKind::Iota); + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .add_data(DataTypes::iota_id, DataValue::Number(*iota_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + let response_cv = match response_cv { + Ok(r) => r, + Err(_) => { + return; + } + }; + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .as_str() + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + *self.identified.write().await = true; + + let encrypted_challenge = + SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(public_key_to_base64(&get_public_key())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); + + log_cv_out!(response); + let _ = self.sender.send(&response).await; + } else if let DataValue::Number(user_id) = cv.get_data(DataTypes::user_id) { + *self.id.write().await = *user_id as u64; + *self.connection_kind.write().await = Some(ConnectionKind::Client); + + let get_pub_key_msg = CommunicationValue::new(CommunicationType::get_user_data) + .add_data(DataTypes::user_id, DataValue::Number(*user_id)); + + let response_cv = get_omega_connection() + .await_response(&get_pub_key_msg, Some(Duration::from_secs(20))) + .await; + + let response_cv = match response_cv { + Ok(r) => r, + Err(_) => { + return; + } + }; + + let base64_pub = response_cv + .get_data(DataTypes::public_key) + .as_str() + .unwrap_or(""); + + let pub_key = match load_public_key(base64_pub) { + Some(pk) => pk, + None => { + return; + } + }; + + *self.pub_key.write().await = Some(pub_key.as_bytes().to_vec()); + + let challenge: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect(); + + *self.challenge.write().await = challenge.clone(); + *self.identified.write().await = true; + + let encrypted_challenge = + SecurePayload::new(challenge.as_bytes(), DataFormat::Raw, get_private_key()) + .unwrap() + .encrypt_x448(pub_key) + .unwrap() + .export(DataFormat::Base64); + + let response = CommunicationValue::new(CommunicationType::challenge) + .with_id(cv.get_id()) + .add_data( + DataTypes::public_key, + DataValue::Str(public_key_to_base64(&get_public_key())), + ) + .add_data(DataTypes::challenge, DataValue::Str(encrypted_challenge)); + + log_cv_out!(response); + let _ = self.sender.send(&response).await; + } + } + async fn handle_challenge_response(self: &Arc, cv: CommunicationValue) { + let id = *self.id.read().await as i64; + + if !cv.is_type(CommunicationType::challenge_response) { + return; + } + + if let DataValue::Str(response) = cv.get_data(DataTypes::challenge) { + let expected = self.challenge.read().await.clone(); + + if *response == expected { + *self.challenged.write().await = true; + + let response = CommunicationValue::new(CommunicationType::identification_response) + .with_id(cv.get_id()) + .add_data(DataTypes::accepted, DataValue::Bool(true)); + + log_cv_out!(response); + if let Err(_) = self.sender.send(&response).await { + return; + } + } else { + log_err!( + id, + PrintType::Iota, + "Challenge response mismatch expected={} actual={}", + expected, + response + ); + } + } else { + log_err!( + id, + PrintType::Iota, + "Challenge response missing challenge payload" + ); + } + } + + async fn migrate(self: &Arc) -> bool { + let kind = match *self.connection_kind.read().await { + Some(kind) => kind, + None => { + return false; + } + }; + let id = *self.id.read().await; + + match kind { + ConnectionKind::Client => { + let notify = CommunicationValue::new(CommunicationType::user_connected) + .add_data(DataTypes::user_id, DataValue::Number(id as i64)); + get_omega_connection().send_message(¬ify).await; + + let user_id = id as i64; + + let client = ClientConnection::from_general(self.clone(), id).await; + + let mut rho = rho_manager::get_rho_con_for_user(user_id).await; + + if rho.is_none() { + let get_user_msg = CommunicationValue::new(CommunicationType::get_user_data) + .add_data(DataTypes::user_id, DataValue::Number(user_id)); + + if let Ok(user_data_cv) = get_omega_connection() + .await_response(&get_user_msg, Some(Duration::from_secs(20))) + .await + { + if let DataValue::Number(iota_id) = + user_data_cv.get_data(DataTypes::iota_id) + { + if let Some(bound_rho) = + rho_manager::bind_user_to_iota(user_id, *iota_id).await + { + bound_rho.bind_user_id(user_id).await; + rho = Some(bound_rho); + } + } + } + } + + *self.rho_connection.write().await = rho.clone(); + + if let Some(rho_conn) = rho { + rho_conn.bind_user_id(user_id).await; + rho_conn.add_client_connection(client.clone()).await; + } else { + log_err!( + user_id, + PrintType::Client, + "No RhoConnection found for user {}, client not attached to iota", + id + ); + } + + client.start(); + } + ConnectionKind::Iota => { + let notify = CommunicationValue::new(CommunicationType::iota_connected) + .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); + get_omega_connection().send_message(¬ify).await; + + let iota = IotaConnection::from_general(self.clone(), id).await; + + let rho = Arc::new(RhoConnection::new(iota.clone(), Vec::new()).await); + + iota.set_rho_connection(rho.clone()).await; + + rho_manager::add_rho(rho).await; + + let get_iota_msg = CommunicationValue::new(CommunicationType::get_iota_data) + .add_data(DataTypes::iota_id, DataValue::Number(id as i64)); + + if let Ok(iota_data_cv) = get_omega_connection() + .await_response(&get_iota_msg, Some(Duration::from_secs(20))) + .await + { + if let DataValue::Array(users) = iota_data_cv.get_data(DataTypes::user_ids) { + let mut user_ids: Vec = Vec::new(); + for value in users { + if let DataValue::Number(user_id) = value { + user_ids.push(*user_id as u64); + } + } + iota.set_user_ids(user_ids).await; + } + } + + iota.clone().start(); + } + ConnectionKind::AnonymousClient => { + let client = AnonymousClientConnection::from_general(self.clone(), id).await; + client.start(); + } + ConnectionKind::Phi => { + let phi = ClientConnection::from_general(self.clone(), id).await; + phi.start(); + } + } + true + } +} diff --git a/src/rho/iota_connection.rs b/src/rho/iota_connection.rs new file mode 100755 index 0000000..41336ea --- /dev/null +++ b/src/rho/iota_connection.rs @@ -0,0 +1,461 @@ +use crate::calls::call_group::CallGroup; +use crate::calls::call_manager; +use crate::log_cv_in; +use crate::log_cv_out; +use crate::log_err; +use crate::log_in; +use crate::omega::omega_connection::get_omega_connection; +use crate::rho::connection::GeneralConnection; +use crate::util::logger::PrintType; +use dashmap::DashMap; +use std::collections::BTreeMap; +use std::{collections::HashMap, sync::Arc, time::Duration}; +use tokio::sync::RwLock; +use tokio::sync::mpsc; +use ttp_core::CommunicationType; +use ttp_core::CommunicationValue; +use ttp_core::DataTypes; +use ttp_core::DataValue; +use ttp_native::Receiver; +use ttp_native::Sender; +use x448::PublicKey; + +use super::{rho_connection::RhoConnection, rho_manager}; +use crate::omega::omega_connection::OmegaConnection; + +#[allow(dead_code)] +pub struct IotaConnection { + pub iota_id: u64, + pub sender: Arc, + pub receiver: Arc, + pub user_ids: Arc>>, + pub ping: Arc>, + pub_key: Arc>>>, + pub waiting_tasks: + DashMap, CommunicationValue) -> bool + Send + Sync>>, + pub rho_connection: Arc>>>, +} + +impl IotaConnection { + pub async fn from_general(general: Arc, iota_id: u64) -> Arc { + Arc::new(Self { + ping: Arc::new(RwLock::new(0)), + pub_key: Arc::new(RwLock::new(None)), + rho_connection: general.rho_connection.clone(), + user_ids: Arc::new(RwLock::new(Vec::new())), + sender: general.sender.clone(), + receiver: general.receiver.clone(), + iota_id: iota_id, + waiting_tasks: DashMap::new(), + }) + } + pub fn start(self: Arc) { + let self_clone = self.clone(); + tokio::spawn(async move { + loop { + match self_clone.receiver.receive().await { + Ok(cv) => { + self_clone.clone().handle_message(cv).await; + } + Err(_) => { + break; + } + } + } + self_clone.handle_close().await; + }); + } + + /// Get the Iota ID + pub async fn get_iota_id(&self) -> u64 { + self.iota_id + } + + #[allow(dead_code)] + pub async fn get_public_key(&self) -> Option { + if let Some(public_key) = self.pub_key.read().await.clone() { + PublicKey::from_bytes(&public_key) + } else { + None + } + } + + /// Get the user IDs + pub async fn get_user_ids(&self) -> Vec { + self.user_ids.read().await.clone() + } + + /// Replace all users linked to this iota and synchronize the attached rho mapping. + pub async fn set_user_ids(&self, user_ids: Vec) { + { + let mut guard = self.user_ids.write().await; + *guard = user_ids.clone(); + } + + if let Some(rho_conn) = self.get_rho_connection().await { + let user_ids_i64: Vec = user_ids.into_iter().map(|u| u as i64).collect(); + rho_conn.set_user_ids(user_ids_i64).await; + } + } + + pub async fn add_user_id(&self, user_id: u64) { + let mut should_sync = false; + { + let mut guard = self.user_ids.write().await; + if !guard.contains(&user_id) { + guard.push(user_id); + should_sync = true; + } + } + + if should_sync { + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.add_user_id(user_id as i64).await; + } + } + } + + /// Get current ping + pub async fn get_ping(&self) -> i64 { + *self.ping.read().await + } + + /// Set the RhoConnection reference + pub async fn set_rho_connection(&self, rho_connection: Arc) { + let mut rho_ref = self.rho_connection.write().await; + *rho_ref = Some(rho_connection); + } + + /// Get RhoConnection if available + pub async fn get_rho_connection(&self) -> Option> { + let rho_ref = self.rho_connection.read().await; + if let Some(weak_ref) = rho_ref.as_ref() { + Some(weak_ref.clone()) + } else { + None + } + } + + /// Send a CommunicationValue to the Iota + pub async fn send_message(&self, cv: &CommunicationValue) { + if !cv.is_type(CommunicationType::pong) { + log_cv_out!(PrintType::Iota, cv); + } + if let Err(e) = self.sender.send(&cv).await { + log_err!( + self.iota_id as i64, + PrintType::Iota, + "Failed to send message: {:?}", + e + ); + } + } + + /// Handle incoming message from Iota + pub async fn handle_message(self: Arc, cv: CommunicationValue) { + let msg_id = cv.get_id(); + if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { + if (task)(self.clone(), cv.clone()) { + return; + } + } + + // Handle ping + if cv.is_type(CommunicationType::ping) || cv.is_type(CommunicationType::pong) { + self.handle_ping(cv).await; + return; + } + + log_cv_in!(PrintType::Iota, cv); + + // Handle GET_CHATS + if cv.is_type(CommunicationType::get_chats) { + self.handle_get_chats(cv).await; + return; + } + + // Handle forwarding to other Iotas or clients + let receiver_id = cv.get_receiver(); + if (receiver_id != 0 && !self.get_user_ids().await.contains(&(receiver_id as u64))) + || cv.is_type(CommunicationType::message_other_iota) + || cv.is_type(CommunicationType::send_chat) + { + self.handle_forward_message(cv).await; + return; + } + + if cv.is_type(CommunicationType::change_iota_data) + || cv.is_type(CommunicationType::push_notification) + || cv.is_type(CommunicationType::get_user_data) + || cv.is_type(CommunicationType::get_iota_data) + || cv.is_type(CommunicationType::get_register) + || cv.is_type(CommunicationType::complete_register_user) + || cv.is_type(CommunicationType::delete_iota) + { + let sender = self.get_iota_id().await; + self.handle_omega_forward(cv.with_sender(sender as u64)) + .await; + return; + } + self.forward_to_client(cv).await; + } + + #[allow(dead_code)] + async fn send_error_response(&self, message_id: u32, error_type: CommunicationType) { + let error = CommunicationValue::new(error_type).with_id(message_id); + self.send_message(&error).await; + } + + #[allow(dead_code)] + async fn close(&self) { + let _ = self.sender.close(); + } + + async fn handle_omega_forward(self: Arc, cv: CommunicationValue) { + let iota_for_closure = self.clone(); + tokio::spawn(async move { + let response_cv = get_omega_connection() + .await_response(&cv.with_sender(self.iota_id), Some(Duration::from_secs(20))) + .await; + if let Ok(response_cv) = response_cv { + iota_for_closure.send_message(&response_cv).await; + } + }); + } + /// Handle ping message + async fn handle_ping(&self, cv: CommunicationValue) { + if let DataValue::Number(last_ping) = cv.get_data(DataTypes::last_ping) { + if let Ok(ping_val) = last_ping.to_string().parse::() { + let mut ping_guard = self.ping.write().await; + *ping_guard = ping_val; + } + } + + let client_pings = if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.get_client_pings().await + } else { + HashMap::new() + }; + + let pings: Vec<(DataTypes, DataValue)> = client_pings + .into_iter() + .map(|(k, v)| (DataTypes::parse(k), DataValue::Number(v))) + .collect(); + let response = CommunicationValue::new(CommunicationType::pong) + .with_id(cv.get_id()) + .add_data(DataTypes::ping_clients, DataValue::Container(pings)); + + self.send_message(&response).await; + } + + /// Handle message forwarding to other Iotas + async fn handle_forward_message(&self, cv: CommunicationValue) { + let receiver_id = cv.get_receiver(); + let sender_id = cv.get_sender(); + let my_user_ids = self.get_user_ids().await; + + log_in!( + self.iota_id as i64, + PrintType::Iota, + "Authority check: sender_id={} receiver_id={} iota_user_ids={:?} msg_type={:?} msg_id={}", + sender_id, + receiver_id, + my_user_ids, + cv.get_type(), + cv.get_id() + ); + + if my_user_ids.contains(&(sender_id as u64)) { + if let Some(target_rho) = rho_manager::get_rho_con_for_user(receiver_id as i64).await { + target_rho.message_to_iota(cv).await; + } else { + let error = CommunicationValue::new(CommunicationType::error_no_iota) + .with_id(cv.get_id()) + .with_sender(cv.get_sender()); + self.send_message(&error).await; + } + } else { + log_err!( + self.iota_id as i64, + PrintType::Iota, + "Rejected client->iota forward: sender_id={} is not authorized for this iota. Known users={:?}", + sender_id, + my_user_ids + ); + + self.send_message( + &CommunicationValue::new(CommunicationType::error_invalid_user_id).add_data( + DataTypes::error_type, + DataValue::Str( + "You are sending to another User without authority.".to_string(), + ), + ), + ) + .await; + } + } + + /// Handle GET_CHATS message + async fn handle_get_chats(&self, cv: CommunicationValue) { + let receiver_id = cv.get_receiver(); + let mut interested_ids: Vec = Vec::new(); + + // ============================ + // Load Calls + // ============================ + let calls: Vec> = call_manager::get_call_groups(receiver_id).await; + + let mut invites: HashMap> = HashMap::new(); + let empty = calls.is_empty(); + + for call in calls { + for inviter in call.members.read().await.iter() { + let call_self = call.get_caller(receiver_id).await.unwrap(); + + let inviter_id = inviter.user_id; + let timeout = *call_self.timeout.read().await; + let admin = call_self.has_admin(); + + // Build call container + let mut call_map: BTreeMap = BTreeMap::new(); + + call_map.insert(DataTypes::call_id, DataValue::Str(call.call_id.to_string())); + + if timeout > 0 { + call_map.insert(DataTypes::timeout, DataValue::Number(timeout as i64)); + } + + if admin { + call_map.insert(DataTypes::has_admin, DataValue::Bool(true)); + } + + let call_container = DataValue::container_from_map(&call_map); + + invites + .entry(inviter_id as i64) + .or_insert_with(Vec::new) + .push(call_container); + } + } + + // ============================ + // Enrich Contacts + // ============================ + let enriched_contacts = if empty { + match cv.get_data(DataTypes::user_ids) { + DataValue::Array(arr) => DataValue::Array(arr.clone()), + _ => DataValue::Array(vec![]), + } + } else { + let mut enriched: Vec = Vec::new(); + + if let DataValue::Array(users) = cv.get_data(DataTypes::user_ids) { + for user_val in users { + if let DataValue::Container(entries) = user_val { + let mut user_map: BTreeMap = + entries.iter().cloned().collect(); + + // extract user_id + if let Some(DataValue::Number(user_id)) = user_map.get(&DataTypes::user_id) + { + interested_ids.push(*user_id); + + // attach calls if exists + if let Some(call_list) = invites.get(user_id) { + user_map + .insert(DataTypes::calls, DataValue::Array(call_list.clone())); + } + } + + enriched.push(DataValue::container_from_map(&user_map)); + } + } + } + + DataValue::Array(enriched) + }; + + // ============================ + // Notify Omega + // ============================ + OmegaConnection::user_states(receiver_id as i64, interested_ids.clone()).await; + + // ============================ + // Notify Rho + // ============================ + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn + .set_interested(receiver_id as i64, interested_ids) + .await; + } + + // ============================ + // Forward to client + // ============================ + self.forward_to_client(cv.add_data(DataTypes::user_ids, enriched_contacts)) + .await; + } + + /// Forward message to client + async fn forward_to_client(&self, cv: CommunicationValue) { + if let Some(rho_conn) = self.get_rho_connection().await { + let updated_cv = cv.with_sender(self.get_iota_id().await); + rho_conn.message_to_client(updated_cv).await; + } else { + } + } + + pub async fn handle_close(&self) { + if let Some(rho_conn) = self.get_rho_connection().await { + rho_conn.close_iota_connection().await; + } + } + + #[allow(dead_code)] + pub async fn await_response( + self: Arc, + cv: &CommunicationValue, + timeout_duration: Option, + ) -> Result { + let (tx, mut rx) = mpsc::channel(1); + let msg_id = cv.get_id(); + + let task_tx = tx.clone(); + self.waiting_tasks.insert( + msg_id, + Box::new(move |_, response_cv| { + let inner_tx = task_tx.clone(); + tokio::spawn(async move { + let _ = inner_tx.send(response_cv).await; + }); + true + }), + ); + + self.send_message(cv).await; + + let timeout = timeout_duration.unwrap_or(Duration::from_secs(10)); + + match tokio::time::timeout(timeout, rx.recv()).await { + Ok(Some(response_cv)) => Ok(response_cv), + Ok(_) => Err("Failed to receive response, channel was closed.".to_string()), + Err(_) => { + self.waiting_tasks.remove(&msg_id); + Err(format!( + "Request timed out after {} seconds.", + timeout.as_secs() + )) + } + } + } +} + +impl std::fmt::Debug for IotaConnection { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IotaConnection") + .field("iota_id", &"[async]") + .field("identified", &"[async]") + .field("ping", &"[async]") + .finish() + } +} diff --git a/src/rho/mod.rs b/src/rho/mod.rs new file mode 100644 index 0000000..ac498f3 --- /dev/null +++ b/src/rho/mod.rs @@ -0,0 +1,6 @@ +pub mod client_connection; +pub mod connection; +pub mod iota_connection; +pub mod rho_connection; +pub mod rho_manager; +pub mod server; diff --git a/src/rho/rho_connection.rs b/src/rho/rho_connection.rs new file mode 100644 index 0000000..6a740ff --- /dev/null +++ b/src/rho/rho_connection.rs @@ -0,0 +1,199 @@ +use super::{client_connection::ClientConnection, iota_connection::IotaConnection, rho_manager}; + +use crate::data::user::UserStatus; +use crate::omega::omega_connection::OmegaConnection; +use std::collections::HashMap; +use std::sync::Arc; +use tokio::sync::RwLock; +use ttp_core::{CommunicationType, CommunicationValue, DataTypes, DataValue}; + +pub struct RhoConnection { + iota_connection: Arc, + user_ids: Arc>>, + client_connections: Arc>>>, +} + +impl RhoConnection { + /// Create a new RhoConnection + pub async fn new(iota_connection: Arc, user_ids: Vec) -> Self { + let rho_connection = Self { + iota_connection, + user_ids: Arc::new(RwLock::new(user_ids.clone())), + client_connections: Arc::new(RwLock::new(Vec::new())), + }; + + rho_connection + } + + pub async fn get_iota_id(&self) -> u64 { + self.iota_connection.iota_id + } + + pub async fn get_user_ids(&self) -> Vec { + self.user_ids.read().await.clone() + } + + pub async fn set_user_ids(&self, user_ids: Vec) { + let mut guard = self.user_ids.write().await; + *guard = user_ids; + } + + pub async fn add_user_id(&self, user_id: i64) { + let mut guard = self.user_ids.write().await; + if !guard.contains(&user_id) { + guard.push(user_id); + } + } + + pub async fn bind_user_id(&self, user_id: i64) { + self.add_user_id(user_id).await; + self.iota_connection.add_user_id(user_id as u64).await; + } + + pub fn get_iota_connection(&self) -> &Arc { + &self.iota_connection + } + + pub async fn get_client_connections(&self) -> Vec> { + let connections = self.client_connections.read().await; + connections.clone() + } + + /// Get client connections for a specific user + pub async fn get_client_connections_for_user( + &self, + user_id: i64, + ) -> Vec> { + let connections = self.client_connections.read().await; + let mut collections = Vec::new(); + for con in connections.iter() { + if con.get_user_id().await == user_id as u64 { + collections.push(con.clone()); + } + } + collections + } + + /// Add a client connection + #[allow(dead_code)] + pub async fn add_client_connection(&self, connection: Arc) { + let notification = CommunicationValue::new(CommunicationType::client_connected).add_data( + DataTypes::user_id, + DataValue::Number(connection.get_user_id().await as i64), + ); + + self.iota_connection.send_message(¬ification).await; + + { + let mut connections = self.client_connections.write().await; + connections.push(Arc::clone(&connection)); + } + + OmegaConnection::client_changed( + self.get_iota_id().await as i64, + connection.get_user_id().await as i64, + UserStatus::user_online, + ) + .await; + } + + /// Remove a client connection + pub async fn close_client_connection(&self, connection: Arc) { + let target_user_id = connection.get_user_id().await; + { + let mut connections = self.client_connections.write().await; + connections.retain(|con| { + futures::executor::block_on(async { con.get_user_id().await != target_user_id }) + }); + } + + // Notify OmegaConnection + OmegaConnection::client_changed( + self.get_iota_id().await as i64, + connection.get_user_id().await as i64, + UserStatus::user_offline, + ) + .await; + } + + /// Close the Iota connection and all associated client connections + pub async fn close_iota_connection(&self) { + // Close all client connections + let connections = self.get_client_connections().await; + for connection in connections { + connection.close().await; + } + + // Remove from manager + rho_manager::remove_rho(self.get_iota_id().await as i64).await; + + // Notify OmegaConnection + OmegaConnection::close_iota(self.get_iota_id().await as i64).await; + } + + /// Send message from Iota to specific client + pub async fn message_to_client(&self, cv: CommunicationValue) { + let connections = self.client_connections.read().await; + let receiver_id = cv.get_receiver(); + for connection in connections.iter() { + if connection.get_user_id().await == receiver_id { + connection.clone().send_message(&cv).await; + } + } + } + + /// Send message to Iota + pub async fn message_to_iota(&self, cv: CommunicationValue) { + self.iota_connection.send_message(&cv).await; + } + + /// Set interested users for a specific client + pub async fn set_interested(&self, user_id: i64, interested_ids: Vec) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + let conn_user_id = connection.get_user_id().await; + if conn_user_id == user_id as u64 { + connection + .clone() + .set_interested_users(interested_ids.clone()) + .await; + break; + } + } + } + + /// Check if clients are interested in a user + #[allow(dead_code)] + pub async fn are_they_interested(&self, user_id: i64) { + let connections = self.client_connections.read().await; + for connection in connections.iter() { + connection.clone().are_you_interested(user_id).await; + } + } + + /// Get ping information for all clients + pub async fn get_client_pings(&self) -> HashMap { + let connections = self.client_connections.read().await; + let mut pings = HashMap::new(); + + for connection in connections.iter() { + let user_id = connection.get_user_id().await; + pings.insert(user_id.to_string(), connection.get_ping().await); + } + + pings + } + + /// Check if this RhoConnection contains a specific user ID + #[allow(dead_code)] + pub async fn contains_user(&self, user_id: &i64) -> bool { + self.user_ids.read().await.contains(user_id) + } + + /// Get count of active client connections + #[allow(dead_code)] + pub async fn client_count(&self) -> usize { + let connections = self.client_connections.read().await; + connections.len() + } +} diff --git a/src/rho/rho_manager.rs b/src/rho/rho_manager.rs new file mode 100644 index 0000000..e823a04 --- /dev/null +++ b/src/rho/rho_manager.rs @@ -0,0 +1,83 @@ +use super::rho_connection::RhoConnection; +use crate::log_in; +use crate::util::logger::PrintType; +use std::{ + collections::HashMap, + sync::{Arc, LazyLock}, +}; +use tokio::sync::RwLock; + +pub static RHO_CONNECTIONS: LazyLock>>>> = + LazyLock::new(|| Arc::new(RwLock::new(HashMap::new()))); + +pub async fn get_rho_con_for_user(user_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + for rho_connection in connections.values() { + let rho_user_ids = rho_connection.get_user_ids().await; + log_in!( + user_id, + PrintType::Client, + "Comparing user IDs: {:?}", + rho_user_ids + ); + if rho_user_ids.contains(&user_id) { + return Some(Arc::clone(rho_connection)); + } + } + None +} + +#[allow(dead_code)] +pub async fn contains_iota(iota_id: i64) -> bool { + let connections = RHO_CONNECTIONS.read().await; + connections.contains_key(&iota_id) +} + +/// Bind a user ID to an already tracked iota/rho connection. +pub async fn bind_user_to_iota(user_id: i64, iota_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + if let Some(rho_connection) = connections.get(&iota_id) { + let rho = Arc::clone(rho_connection); + drop(connections); + + rho.add_user_id(user_id).await; + + log_in!( + user_id, + PrintType::Client, + "Bound user {} to iota {}", + user_id, + iota_id + ); + + Some(rho) + } else { + None + } +} + +/// Remove a RhoConnection by Iota ID +pub async fn remove_rho(iota_id: i64) -> Option> { + let mut connections = RHO_CONNECTIONS.write().await; + connections.remove(&iota_id) +} + +/// Add a RhoConnection to the manager +pub async fn add_rho(rho_connection: Arc) { + let mut connections = RHO_CONNECTIONS.write().await; + let iota_id = rho_connection.get_iota_id().await; + connections.insert(iota_id as i64, rho_connection); +} + +/// Get a RhoConnection by Iota ID directly +#[allow(dead_code)] +pub async fn get_rho_by_iota(iota_id: i64) -> Option> { + let connections = RHO_CONNECTIONS.read().await; + connections.get(&iota_id).map(Arc::clone) +} + +/// Get the count of active connections +pub async fn connection_count() -> usize { + let connections = RHO_CONNECTIONS.read().await; + connections.len() +} diff --git a/src/rho/server.rs b/src/rho/server.rs new file mode 100644 index 0000000..9e3946d --- /dev/null +++ b/src/rho/server.rs @@ -0,0 +1,25 @@ +use crate::{ + log, + rho::connection::GeneralConnection, + util::{file_util::load_file_vec, logger::PrintType}, +}; +use ttp_native::Host; + +pub async fn start(port: u16) -> Result<(), Box> { + let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); + + let key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile"); + + let mut host: Host = ttp_native::host(port, cert_pem, key_pem).await?; + log!(0, PrintType::General, "Server listening on port {}", port); + + while let Some((sender, receiver)) = host.next().await { + tokio::spawn(async move { + let conn = GeneralConnection::new(sender, receiver); + conn.handle().await; + }); + } + log!(0, PrintType::General, "Server stopped"); + + Ok(()) +} diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs new file mode 100755 index 0000000..cb3ae6a --- /dev/null +++ b/src/util/crypto_helper.rs @@ -0,0 +1,149 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, OsRng}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD}; +use rand_core::RngCore; +use sha2::{Digest, Sha256}; +use x448::{PublicKey, Secret, SharedSecret}; + +/// Errors for crypto operations +#[allow(dead_code)] +#[derive(Debug)] +pub enum CryptoError { + Base64Decode(base64::DecodeError), + InvalidKey, + AgreementError, + EncryptionError(aes_gcm::Error), + DecryptionError(aes_gcm::Error), +} + +impl From for CryptoError { + fn from(err: base64::DecodeError) -> Self { + CryptoError::Base64Decode(err) + } +} + +#[allow(dead_code)] +pub struct KeyPair { + pub secret: Secret, + pub public: PublicKey, +} + +#[allow(dead_code)] +pub fn generate_keypair() -> KeyPair { + let mut buf = [0u8; 56]; + let mut rng = OsRng; + rng.fill_bytes(&mut buf); + let secret = Secret::from_bytes(&buf).unwrap(); + let public = PublicKey::from(&secret); + KeyPair { secret, public } +} + +pub fn public_key_to_base64(pubkey: &PublicKey) -> String { + STANDARD.encode(pubkey.as_bytes().as_ref()) +} + +pub fn secret_key_to_base64(secret: &Secret) -> String { + STANDARD.encode(secret.as_bytes().as_ref()) +} + +pub fn load_public_key(base64_pub: &str) -> Option { + let bytes = STANDARD.decode(base64_pub).unwrap(); + PublicKey::from_bytes(&bytes) +} + +pub fn load_secret_key(base64_secret: &str) -> Option { + let bytes = STANDARD.decode(base64_secret).unwrap(); + Secret::from_bytes(&bytes) +} + +fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(shared.as_bytes()); + let result = hasher.finalize(); + let mut key = [0u8; 32]; + key.copy_from_slice(&result[..32]); + key +} + +#[allow(dead_code)] +pub fn encrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + plaintext: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + encrypt(secret, peer_pub, plaintext) +} +pub fn encrypt( + secret: Secret, + peer_pub: PublicKey, + plaintext: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + let mut nonce_bytes = [0u8; 12]; + OsRng.fill_bytes(&mut nonce_bytes); + let nonce = Nonce::from_slice(&nonce_bytes); + let ciphertext = cipher + .encrypt(nonce, plaintext.as_bytes()) + .map_err(CryptoError::EncryptionError)?; + // prefix nonce to ciphertext + let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); + out.extend_from_slice(&nonce_bytes); + out.extend_from_slice(&ciphertext); + Ok(STANDARD.encode(&out)) +} + +pub fn decrypt_b64( + base64_secret: &str, + base64_peer_pub: &str, + encrypted_base64: &str, +) -> Result { + let secret = load_secret_key(base64_secret).unwrap(); + let peer_pub = load_public_key(base64_peer_pub).unwrap(); + decrypt(secret, peer_pub, encrypted_base64) +} +pub fn decrypt( + secret: Secret, + peer_pub: PublicKey, + encrypted_base64: &str, +) -> Result { + let shared = secret + .to_diffie_hellman(&peer_pub) + .ok_or(CryptoError::AgreementError)?; + let key_bytes = derive_aes_key(&shared); + let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); + + let encrypted = STANDARD.decode(encrypted_base64)?; + if encrypted.len() < 12 { + return Err(CryptoError::DecryptionError(aes_gcm::Error)); + } + let nonce_bytes = &encrypted[..12]; + let ciphertext = &encrypted[12..]; + let nonce = Nonce::from_slice(nonce_bytes); + let plaintext_bytes = cipher + .decrypt(nonce, ciphertext) + .map_err(CryptoError::DecryptionError)?; + let plaintext = String::from_utf8(plaintext_bytes) + .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; + Ok(plaintext) +} + +#[allow(dead_code)] +pub fn hash_it(input: &str) -> Vec { + let mut hasher = Sha256::new(); + hasher.update(input.as_bytes()); + hasher.finalize().to_vec() +} + +#[allow(dead_code)] +pub fn hex_hash(input: &str) -> String { + let digest = hash_it(input); + digest.iter().map(|b| format!("{:02x}", b)).collect() +} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs new file mode 100644 index 0000000..bb784e8 --- /dev/null +++ b/src/util/crypto_util.rs @@ -0,0 +1,178 @@ +use aes_gcm::{ + Aes256Gcm, Nonce, + aead::{Aead, KeyInit, Payload}, +}; +use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; +use hkdf::Hkdf; +type HkdfSha256 = sha2::Sha256; +use sha2::{Digest, Sha256 as HashSha256}; +use x448::{PublicKey, Secret}; + +#[derive(Debug)] +#[allow(dead_code)] +pub enum SecurePayloadError { + InvalidBase64, + InvalidHex, + EncryptionError, + DecryptionError, + InvalidKeyLength, +} + +#[derive(Clone, Copy, Debug)] +#[allow(dead_code)] +pub enum DataFormat { + Raw, + Base64, + Hex, +} + +pub struct SecurePayload { + inner_data: Vec, + private_key: Secret, +} + +impl Clone for SecurePayload { + fn clone(&self) -> Self { + Self { + inner_data: self.inner_data.clone(), + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + } + } +} + +#[allow(dead_code)] +impl SecurePayload { + pub fn new>( + data: T, + format: DataFormat, + private_key: S, + ) -> Result + where + S: Into, + { + let raw_data = match format { + DataFormat::Raw => data.as_ref().to_vec(), + DataFormat::Base64 => BASE64_STD + .decode(data.as_ref()) + .map_err(|_| SecurePayloadError::InvalidBase64)?, + DataFormat::Hex => { + hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? + } + }; + + Ok(Self { + inner_data: raw_data, + private_key: private_key.into(), + }) + } + + pub fn get_public_key(&self) -> [u8; 56] { + *PublicKey::from(&self.private_key).as_bytes() + } + + pub fn export(&self, format: DataFormat) -> String { + match format.into() { + DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), + DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), + DataFormat::Hex => hex::encode(&self.inner_data), + } + } + + pub fn get_bytes(&self) -> &[u8] { + &self.inner_data + } + + pub fn get_hash(&self, format: DataFormat) -> String { + let mut hasher = HashSha256::new(); + hasher.update(&self.inner_data); + let result = hasher.finalize(); + + match format { + DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), + DataFormat::Base64 => BASE64_STD.encode(result), + DataFormat::Hex => hex::encode(result), + } + } + + pub fn encrypt_x448(&self, public_key: S) -> Result + where + S: Into, + { + let peer_pub = public_key.into(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; + + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let ciphertext = cipher + .encrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::EncryptionError)?; + + Ok(SecurePayload { + inner_data: ciphertext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } + + pub fn decrypt_to_format( + &self, + peer_public_key_bytes: &[u8; 56], + output_format: DataFormat, + ) -> Result { + let decrypted_instance = + self.decrypt_x448(PublicKey::from_bytes(peer_public_key_bytes).unwrap())?; + Ok(decrypted_instance.export(output_format)) + } + + pub fn decrypt_x448( + &self, + peer_public_key_bytes: S, + ) -> Result + where + S: Into, + { + let peer_pub = peer_public_key_bytes.into(); + let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); + + let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); + let mut okm = [0u8; 44]; + hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + let key = &okm[..32]; + let nonce_bytes = &okm[32..]; + + let cipher = Aes256Gcm::new(key.into()); + let nonce = Nonce::from_slice(nonce_bytes); + + let plaintext = cipher + .decrypt( + nonce, + Payload { + msg: &self.inner_data, + aad: &[], + }, + ) + .map_err(|_| SecurePayloadError::DecryptionError)?; + + Ok(SecurePayload { + inner_data: plaintext, + private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), + }) + } +} diff --git a/src/util/file_util.rs b/src/util/file_util.rs new file mode 100644 index 0000000..cd214b1 --- /dev/null +++ b/src/util/file_util.rs @@ -0,0 +1,185 @@ +use std::fs::{self, File}; +use std::io::{self, BufReader, Read}; +use std::path::{Path, PathBuf}; + +use crate::log; +use crate::util::logger::PrintType; + +#[allow(dead_code)] +pub fn delete_directory(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + delete_dir_recursive(&dir) +} + +#[allow(dead_code)] +fn delete_dir_recursive(directory: &Path) -> bool { + if !directory.exists() { + return false; + } + if let Err(e) = fs::remove_dir_all(directory) { + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't delete directory {}: {}", + directory.display(), + e, + ); + return false; + } + true +} + +#[allow(dead_code)] +pub fn delete_user_directory(user_id: i64) { + let user_dir = Path::new(&get_directory()) + .join("users") + .join(user_id.to_string()); + let _ = delete_dir_recursive(&user_dir); +} + +#[allow(dead_code)] +pub fn load_file_buf(path: &str, name: &str) -> io::Result> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + // Ensure the directory exists, create if necessary + if !dir.exists() { + if let Err(_) = fs::create_dir_all(&dir) { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "Directory creation failed", + )); + } + } + + // Create the file if it doesn't exist + if !file_path.exists() { + return Err(io::Error::new( + io::ErrorKind::NotFound, + "File creation failed", + )); + } + + // Open the file and return a BufReader for efficient reading + let file = File::open(&file_path)?; + Ok(BufReader::new(file)) +} +#[allow(dead_code)] +pub fn has_file(path: &str, name: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + return false; + } + + if !file_path.exists() { + return false; + } + + true +} +#[allow(dead_code)] +pub fn has_dir(path: &str) -> bool { + let dir = Path::new(&get_directory()).join(path); + + if !dir.exists() { + return false; + } + + true +} + +#[allow(dead_code)] +pub fn load_file(path: &str, name: &str) -> String { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + if let Err(e) = fs::create_dir_all(&dir) { + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create directories: {}", + e + ); + return String::new(); + } + return String::new(); + } + + if !file_path.exists() { + if let Err(e) = File::create(&file_path) { + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create file: {}", + e + ); + } + return String::new(); + } + + let mut content = String::new(); + if let Ok(mut f) = File::open(&file_path) { + let _ = f.read_to_string(&mut content); + } + content +} + +pub fn load_file_vec(path: &str, name: &str) -> Result, std::io::Error> { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + std::fs::read(file_path) +} + +#[allow(dead_code)] +pub fn save_file(path: &str, name: &str, value: &str) { + let dir = Path::new(&get_directory()).join(path); + let file_path = dir.join(name); + + if !dir.exists() { + if let Err(e) = fs::create_dir_all(&dir) { + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't create directories: {}", + e + ); + return; + } + } + + if let Err(e) = fs::write(&file_path, value) { + log!( + 0, + PrintType::General, + "[IMPORTANT] Couldn't write file {}: {}", + file_path.display(), + e + ); + } +} + +#[allow(dead_code)] +pub fn get_children(path: &str) -> Vec { + let dir = Path::new(&get_directory()).join(path); + let mut children = Vec::new(); + if let Ok(entries) = fs::read_dir(&dir) { + for entry in entries { + if let Ok(entry) = entry { + children.push(entry.file_name().to_string_lossy().to_string()); + } + } + } + children +} + +pub fn get_directory() -> String { + let exe = std::env::current_exe().unwrap_or_else(|_| PathBuf::from(".")); + exe.parent() + .unwrap_or(Path::new(".")) + .to_string_lossy() + .to_string() +} diff --git a/src/util/logger.rs b/src/util/logger.rs new file mode 100644 index 0000000..9e9a085 --- /dev/null +++ b/src/util/logger.rs @@ -0,0 +1,280 @@ +use std::{ + collections::BTreeMap, + fs::{self, OpenOptions}, + io::Write, + path::Path, + sync::{OnceLock, mpsc}, + thread, + time::{SystemTime, UNIX_EPOCH}, +}; + +use ansi_term::Color; +use ttp_core::{CommunicationValue, DataTypes, DataValue}; + +static LOGGER: OnceLock> = OnceLock::new(); + +#[allow(dead_code)] +#[derive(Clone, Copy)] +pub enum PrintType { + Call, + Client, + Iota, + Omikron, + Omega, + General, +} + +struct LogMessage { + timestamp_ms: u128, + sender: Option, + prefix: &'static str, + kind: PrintType, + is_error: bool, + message: String, +} + +pub fn startup() { + let (tx, rx) = mpsc::channel::(); + LOGGER.set(tx).expect("Logger already initialized"); + + thread::spawn(move || { + let log_dir = Path::new("logs"); + fs::create_dir_all(log_dir).expect("Failed to create log directory"); + + let start_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs(); + + let path = log_dir.join(format!("log_{}.txt", start_ts)); + let mut file = OpenOptions::new() + .create(true) + .append(true) + .open(path) + .expect("Failed to open log file"); + + for msg in rx { + let ts = fixed_box(&msg.timestamp_ms.to_string(), 13); + let sender = match msg.sender { + Some(id) => fixed_box(&id.to_string(), 19), + _ => fixed_box("", 19), + }; + + let line = format!("{} {} {} {}", ts, sender, msg.prefix, msg.message); + + println!("{}", colorize(msg.kind, msg.is_error).paint(&line)); + + let _ = writeln!(file, "{}", line); + } + }); +} + +fn colorize(kind: PrintType, is_error: bool) -> Color { + if is_error { + return Color::Red; + } + + match kind { + PrintType::Call => Color::Purple, + PrintType::Client => Color::Green, + PrintType::Iota => Color::Yellow, + PrintType::Omikron => Color::Blue, + PrintType::Omega => Color::Cyan, + PrintType::General => Color::White, + } +} + +fn fixed_box(content: &str, width: usize) -> String { + let s: String = content.chars().take(width).collect(); + let len = s.chars().count(); + if len < width { + format!("[{}{}]", " ".repeat(width - len), s) + } else { + s + } +} + +pub fn log_internal( + sender: i64, + kind: PrintType, + prefix: &'static str, + is_error: bool, + message: String, +) { + let sender = if sender == 0 { None } else { Some(sender) }; + + if let Some(tx) = LOGGER.get() { + let _ = tx.send(LogMessage { + timestamp_ms: SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(), + sender, + prefix, + kind, + is_error, + message, + }); + } +} +#[macro_export] +macro_rules! log { + ($sender: expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, "", false, format!($($arg)*)) + }; +} +#[macro_export] +macro_rules! log_in { + ($sender: expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, ">", false, format!($($arg)*)) + }; +} +#[macro_export] +macro_rules! log_out { + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, "<", false, format!($($arg)*)) + }; +} +#[macro_export] +macro_rules! log_err { + ($sender:expr, $kind:expr, $($arg:tt)*) => { + $crate::util::logger::log_internal($sender, $kind, ">>", true, format!($($arg)*)) + }; +} + +// ******** COMMUNICATION VALUES ******** +pub fn log_cv_internal( + prefix: &'static str, + cv: &CommunicationValue, + print_type: Option, +) { + let formatted = format_cv(cv); + + log_internal( + cv.get_sender() as i64, + print_type.unwrap_or(PrintType::General), + prefix, + false, + formatted, + ); +} + +pub fn format_cv(cv: &CommunicationValue) -> String { + let mut parts = Vec::new(); + + let sender = cv.get_sender(); + let receiver = cv.get_receiver(); + + if sender > 0 && receiver > 0 { + parts.push(format!("{} > {}", sender, receiver)); + } else if sender > 0 { + parts.push(format!("{}", sender)); + } else if receiver > 0 { + parts.push(format!("> {}", receiver)); + } + + let comm_type = cv.get_type().to_string(); + parts.push(format!("{}", comm_type)); + + let data: &BTreeMap = cv.get_data_container(); + + let formated_data = + format_data_container(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); + + parts.push(format!("{}", formated_data)); + + parts.join(": ") +} + +fn format_data_container(data: Vec<(DataTypes, DataValue)>) -> String { + let parts: Vec = data + .into_iter() + .map(|(key, value)| { + let key_str = key.to_string(); + + match value { + DataValue::Str(s) => format!("{}=\"{}\"", key_str, s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{}={{ {} }}", key_str, inner_formatted) + } + + DataValue::Array(arr) => { + let arr_formatted = format_array(arr); + format!("{}=[{}]", key_str, arr_formatted) + } + + DataValue::Bool(b) => format!("{}={}", key_str, b), + + DataValue::BoolTrue => format!("{}=true", key_str), + DataValue::BoolFalse => format!("{}=false", key_str), + + DataValue::Number(num) => format!("{}={}", key_str, num), + + _ => "".to_string(), + } + }) + .collect(); + + parts.join(", ") +} + +fn format_array(arr: Vec) -> String { + let parts: Vec = arr + .into_iter() + .map(|value| match value { + DataValue::Str(s) => format!("\"{}\"", s), + + DataValue::Container(inner) => { + let inner_formatted = format_data_container(inner); + format!("{{ {} }}", inner_formatted) + } + + DataValue::Array(inner_arr) => { + let formatted = format_array(inner_arr); + format!("[{}]", formatted) + } + + DataValue::Bool(b) => b.to_string(), + + DataValue::BoolTrue => "true".to_string(), + DataValue::BoolFalse => "false".to_string(), + + DataValue::Number(num) => num.to_string(), + + _ => String::new(), + }) + .collect(); + + parts.join(", ") +} + +#[macro_export] +macro_rules! log_cv { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("", &$cv, None) + }; +} + +#[macro_export] +macro_rules! log_cv_in { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("> ", &$cv, None) + }; +} +#[macro_export] +macro_rules! log_cv_out { + ($kind:expr, $cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, Some($kind)) + }; + ($cv:expr) => { + $crate::util::logger::log_cv_internal("< ", &$cv, None) + }; +} diff --git a/src/util/mod.rs b/src/util/mod.rs new file mode 100644 index 0000000..1897223 --- /dev/null +++ b/src/util/mod.rs @@ -0,0 +1,4 @@ +pub mod crypto_helper; +pub mod crypto_util; +pub mod file_util; +pub mod logger;