Compare commits

..
27 changed files with 1400 additions and 1730 deletions

471
Cargo.lock generated
View file

@ -84,6 +84,131 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[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]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.91" version = "0.1.91"
@ -92,7 +217,25 @@ checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.2", "syn 3.0.1",
]
[[package]]
name = "async-tungstenite"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef0f7efedeac57d9b26170f72965ecfd31473ca52ca7a64e925b0b6f5f079886"
dependencies = [
"async-native-tls",
"async-std",
"atomic-waker",
"futures-core",
"futures-io",
"futures-task",
"futures-util",
"log",
"pin-project-lite",
"tungstenite 0.26.2",
] ]
[[package]] [[package]]
@ -191,6 +334,19 @@ dependencies = [
"objc2", "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]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.20.3" version = "3.20.3"
@ -319,6 +475,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "concurrent-queue"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "const-oid" name = "const-oid"
version = "0.9.6" version = "0.9.6"
@ -619,6 +784,33 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "fastbloom" name = "fastbloom"
version = "0.17.0" version = "0.17.0"
@ -661,6 +853,21 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[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]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.2" version = "1.2.2"
@ -724,6 +931,19 @@ version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
[[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]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.33" version = "0.3.33"
@ -813,6 +1033,18 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[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]] [[package]]
name = "h3" name = "h3"
version = "0.0.8" version = "0.0.8"
@ -893,6 +1125,12 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]] [[package]]
name = "hkdf" name = "hkdf"
version = "0.13.0" version = "0.13.0"
@ -977,9 +1215,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "1.11.0" version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498"
dependencies = [ dependencies = [
"atomic-waker", "atomic-waker",
"bytes", "bytes",
@ -996,18 +1234,18 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hyper-rustls" name = "hyper-tls"
version = "0.27.9" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [ dependencies = [
"http", "bytes",
"http-body-util",
"hyper", "hyper",
"hyper-util", "hyper-util",
"rustls", "native-tls",
"rustls-native-certs",
"tokio", "tokio",
"tokio-rustls", "tokio-native-tls",
"tower-service", "tower-service",
] ]
@ -1304,6 +1542,12 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "json"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
[[package]] [[package]]
name = "jsonwebtoken" name = "jsonwebtoken"
version = "10.4.0" version = "10.4.0"
@ -1338,6 +1582,15 @@ dependencies = [
"cpufeatures 0.3.0", "cpufeatures 0.3.0",
] ]
[[package]]
name = "kv-log-macro"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f"
dependencies = [
"log",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -1380,6 +1633,7 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e827d3444235ddccf360fc1d4737034e86ee2ea2c0a696f148ecc08e7249bfd3" checksum = "e827d3444235ddccf360fc1d4737034e86ee2ea2c0a696f148ecc08e7249bfd3"
dependencies = [ dependencies = [
"async-tungstenite",
"base64 0.21.7", "base64 0.21.7",
"device-info", "device-info",
"hmac 0.12.1", "hmac 0.12.1",
@ -1395,7 +1649,6 @@ dependencies = [
"prost", "prost",
"rand 0.9.5", "rand 0.9.5",
"reqwest", "reqwest",
"rustls-native-certs",
"scopeguard", "scopeguard",
"serde", "serde",
"serde_json", "serde_json",
@ -1403,7 +1656,6 @@ dependencies = [
"signature 2.2.0", "signature 2.2.0",
"thiserror 2.0.19", "thiserror 2.0.19",
"tokio", "tokio",
"tokio-rustls",
"tokio-tungstenite", "tokio-tungstenite",
"url", "url",
] ]
@ -1453,6 +1705,9 @@ name = "log"
version = "0.4.33" version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
dependencies = [
"value-bag",
]
[[package]] [[package]]
name = "lru-cache" name = "lru-cache"
@ -1703,6 +1958,23 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" 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]] [[package]]
name = "nix" name = "nix"
version = "0.31.3" version = "0.31.3"
@ -1941,14 +2213,17 @@ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"dashmap", "dashmap",
"dotenv", "dotenv",
"futures",
"json",
"livekit-api", "livekit-api",
"livekit-protocol", "livekit-protocol",
"log", "log",
"mtp", "mtp",
"mtp-transport", "mtp-transport",
"once_cell", "once_cell",
"rand 0.10.2", "rand 0.8.7",
"rustls", "rustls",
"serde",
"serde_json", "serde_json",
"strum", "strum",
"strum_macros", "strum_macros",
@ -1970,12 +2245,49 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
version = "0.10.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"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 2.0.119",
]
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
version = "0.9.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "os_info" name = "os_info"
version = "3.15.0" version = "3.15.0"
@ -1992,6 +2304,12 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "parking"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.5" version = "0.12.5"
@ -2093,6 +2411,23 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" 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]] [[package]]
name = "pkcs8" name = "pkcs8"
version = "0.10.2" version = "0.10.2"
@ -2119,6 +2454,20 @@ version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[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]] [[package]]
name = "poly1305" name = "poly1305"
version = "0.8.0" version = "0.8.0"
@ -2464,22 +2813,20 @@ dependencies = [
"http-body", "http-body",
"http-body-util", "http-body-util",
"hyper", "hyper",
"hyper-rustls", "hyper-tls",
"hyper-util", "hyper-util",
"js-sys", "js-sys",
"log", "log",
"native-tls",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"quinn",
"rustls",
"rustls-native-certs",
"rustls-pki-types", "rustls-pki-types",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper", "sync_wrapper",
"tokio", "tokio",
"tokio-rustls", "tokio-native-tls",
"tower", "tower",
"tower-http", "tower-http",
"tower-service", "tower-service",
@ -2715,14 +3062,14 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.2", "syn 3.0.1",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.151" version = "1.0.150"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -2958,9 +3305,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.2" version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" checksum = "5edbec4ed188954a10c12c038215f8ce7606b2d5c973cd8dc43e8795065c5f2f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -3037,14 +3384,14 @@ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.2", "syn 3.0.1",
] ]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.54" version = "0.3.53"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" checksum = "18dfaaeddcb932337b5e7866ee7d0ce9b76d2fd092997146f187ec09b4558a50"
dependencies = [ dependencies = [
"deranged", "deranged",
"num-conv", "num-conv",
@ -3062,9 +3409,9 @@ checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109"
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.32" version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" checksum = "c431b87111666e491a90baa837f914fb45cd5dc3c268591b0220ff5057f2085f"
dependencies = [ dependencies = [
"num-conv", "num-conv",
"time-core", "time-core",
@ -3097,9 +3444,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.53.1" version = "1.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" checksum = "d988bcd52dbe076d3d46903332f58c912b87a2c49b1428419a5845154762ffee"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -3124,12 +3471,12 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tokio-rustls" name = "tokio-native-tls"
version = "0.26.4" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [ dependencies = [
"rustls", "native-tls",
"tokio", "tokio",
] ]
@ -3152,12 +3499,10 @@ checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"log", "log",
"rustls", "native-tls",
"rustls-native-certs",
"rustls-pki-types",
"tokio", "tokio",
"tokio-rustls", "tokio-native-tls",
"tungstenite", "tungstenite 0.29.0",
] ]
[[package]] [[package]]
@ -3302,6 +3647,25 @@ version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "tungstenite"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
"bytes",
"data-encoding",
"http",
"httparse",
"log",
"native-tls",
"rand 0.9.5",
"sha1",
"thiserror 2.0.19",
"url",
"utf-8",
]
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.29.0" version = "0.29.0"
@ -3313,9 +3677,8 @@ dependencies = [
"http", "http",
"httparse", "httparse",
"log", "log",
"native-tls",
"rand 0.9.5", "rand 0.9.5",
"rustls",
"rustls-pki-types",
"sha1", "sha1",
"thiserror 2.0.19", "thiserror 2.0.19",
"url", "url",
@ -3388,6 +3751,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "utf-8"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "utf8_iter" name = "utf8_iter"
version = "1.0.4" version = "1.0.4"
@ -3405,6 +3774,18 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "value-bag"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd4ec1eb1d240636e354a30110a1dfcb37047169a4d9bd6d9d3469df574b5c4"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.5" version = "0.9.5"
@ -3872,18 +4253,18 @@ dependencies = [
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.8.55" version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.8.55" version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -12,25 +12,28 @@ mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [
] } ] }
mtp-transport = { git = "https://git.methanium.net/Methanium/mtp.git" } mtp-transport = { git = "https://git.methanium.net/Methanium/mtp.git" }
ansi_term = "0.12.1" ansi_term = "*"
uuid = { version = "1.24.0", features = ["v4"] } uuid = { version = "*", features = ["v4"] }
base64 = "0.22.1" base64 = "0.22.1"
dashmap = "6.2.1" dashmap = "*"
futures = "*"
once_cell = "1.21.4" once_cell = "1.21.4"
rand = "0.10.2" rand = "0.8"
rustls = { version = "0.23.42", default-features = false, features = [ rustls = { version = "0.23.42", default-features = false, features = [
"std", "std",
"tls12", "tls12",
"aws-lc-rs", "aws-lc-rs",
"prefer-post-quantum", "prefer-post-quantum",
] } ] }
tokio = { version = "1.53.0", features = ["full"] } tokio = { version = "*", features = ["full"] }
log = "0.4" log = "0.4"
dotenv = "0.15.0" dotenv = "0.15.0"
strum = "0.28.0" strum = "0.28.0"
strum_macros = "0.28.0" strum_macros = "0.28.0"
livekit-api = { version = "0.5.6", features = ["rustls-tls-native-roots"] } livekit-api = { version = "0.5.6", features = ["native-tls"] }
livekit-protocol = "0.7.10" livekit-protocol = "0.7.10"
thiserror = "2.0.19" thiserror = "2.0.19"
trust-dns-resolver = "0.23.2" trust-dns-resolver = "0.23.2"
serde_json = "1.0.151" serde = "1.0.229"
serde_json = "1.0.150"
json = "0.12.4"

View file

@ -6,16 +6,15 @@ use tokio::sync::RwLock;
use uuid::Uuid; use uuid::Uuid;
use crate::anonymous_clients::anonymous_manager::{self, generate_username}; use crate::anonymous_clients::anonymous_manager::{self, generate_username};
use crate::app_state::AppState; use crate::calls::{call_group::call_invite_secret_from_cv, call_manager};
use crate::calls::call_group::call_invite_secret_from_cv;
use crate::data::user::UserStatus; use crate::data::user::UserStatus;
use crate::omega::omega_connection::{OmegaConnection, get_omega_connection};
use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender}; use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender};
use crate::util::data_type_id; use crate::rho::rho_manager;
use crate::util::logger::PrintType; use crate::util::logger::PrintType;
use crate::{log_cv_in, log_cv_out, log_out}; use crate::{log_cv_in, log_cv_out, log_out};
pub struct AnonymousClientConnection { pub struct AnonymousClientConnection {
pub state: Arc<AppState>,
user_id: u64, user_id: u64,
pub sender: Arc<MtpSender>, pub sender: Arc<MtpSender>,
@ -26,14 +25,12 @@ pub struct AnonymousClientConnection {
pub user_name: Arc<RwLock<String>>, pub user_name: Arc<RwLock<String>>,
pub display_name: Arc<RwLock<String>>, pub display_name: Arc<RwLock<String>>,
pub avatar: Arc<RwLock<String>>, pub avatar: Arc<RwLock<String>>,
message_slots: Arc<tokio::sync::Semaphore>,
} }
impl AnonymousClientConnection { impl AnonymousClientConnection {
pub async fn from_general(general: Arc<GeneralConnection>, user_id: u64) -> Arc<Self> { pub async fn from_general(general: Arc<GeneralConnection>, user_id: u64) -> Arc<Self> {
let username: String = generate_username(); let username: String = generate_username();
Arc::new(Self { Arc::new(Self {
state: general.state.clone(),
user_id: user_id, user_id: user_id,
ping: Arc::new(RwLock::new(0)), ping: Arc::new(RwLock::new(0)),
@ -44,7 +41,6 @@ impl AnonymousClientConnection {
user_name: Arc::new(RwLock::new(username.to_lowercase())), user_name: Arc::new(RwLock::new(username.to_lowercase())),
display_name: Arc::new(RwLock::new(username)), display_name: Arc::new(RwLock::new(username)),
avatar: Arc::new(RwLock::new(String::new())), avatar: Arc::new(RwLock::new(String::new())),
message_slots: Arc::new(tokio::sync::Semaphore::new(32)),
}) })
} }
pub fn start(self: Arc<Self>) { pub fn start(self: Arc<Self>) {
@ -106,11 +102,7 @@ impl AnonymousClientConnection {
/// Handle incoming message from client /// Handle incoming message from client
pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) { pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) {
let Ok(permit) = self.message_slots.clone().acquire_owned().await else {
return;
};
tokio::spawn(async move { tokio::spawn(async move {
let _permit = permit;
if cv.is_type(CommunicationType::Ping) { if cv.is_type(CommunicationType::Ping) {
self.handle_ping(cv).await; self.handle_ping(cv).await;
return; return;
@ -121,7 +113,7 @@ impl AnonymousClientConnection {
let call_id = Uuid::parse_str(cv.get_data(DataType::CallId).as_str().unwrap_or("")) let call_id = Uuid::parse_str(cv.get_data(DataType::CallId).as_str().unwrap_or(""))
.unwrap_or(Uuid::new_v4()); .unwrap_or(Uuid::new_v4());
let call = if let Some(call) = self.state.call_manager.get_call(call_id).await { let call = if let Some(call) = call_manager::get_call(call_id).await {
if call.is_anonymous().await { if call.is_anonymous().await {
call call
} else { } else {
@ -143,10 +135,7 @@ impl AnonymousClientConnection {
let mut invited = Vec::new(); let mut invited = Vec::new();
for call_invitee in call.members.read().await.clone() { for call_invitee in call.members.read().await.clone() {
let Ok(call_invitee_cv) = self let call_invitee_cv = get_omega_connection()
.state
.omega
.clone()
.await_response( .await_response(
&CommunicationValue::new(CommunicationType::GetUserData) &CommunicationValue::new(CommunicationType::GetUserData)
.add_typed_default( .add_typed_default(
@ -156,9 +145,7 @@ impl AnonymousClientConnection {
Some(Duration::from_secs(2)), Some(Duration::from_secs(2)),
) )
.await .await
else { .unwrap();
continue;
};
let mut json_invitee = Vec::new(); let mut json_invitee = Vec::new();
let _ = json_invitee.push(( let _ = json_invitee.push((
DataType::UserId, DataType::UserId,
@ -180,21 +167,18 @@ impl AnonymousClientConnection {
let _ = invited.push(DataValue::Container( let _ = invited.push(DataValue::Container(
json_invitee json_invitee
.iter() .iter()
.map(|(k, v)| (data_type_id(*k, &TypeMap::latest()), v.clone())) .map(|(k, v)| (k.to_id(&TypeMap::latest()), v.clone()))
.collect(), .collect(),
)); ));
} }
let token = match call.create_anonymous_token(self.get_user_id()).await { let token = call.create_anonymous_token(self.get_user_id()).await;
Ok(Some(token)) => token,
Ok(None) | Err(_) => return,
};
let mut serialized = Vec::new(); let mut serialized = Vec::new();
let _ = serialized.push((DataType::CallId, DataValue::Str(call_id.to_string()))); let _ = serialized.push((DataType::CallId, DataValue::Str(call_id.to_string())));
let _ = serialized.push((DataType::CallInvited, DataValue::Array(invited.clone()))); let _ = serialized.push((DataType::CallInvited, DataValue::Array(invited.clone())));
let _ = serialized.push((DataType::CallMembers, DataValue::Array(invited))); let _ = serialized.push((DataType::CallMembers, DataValue::Array(invited)));
let _ = serialized.push((DataType::CallToken, DataValue::Str(token))); let _ = serialized.push((DataType::CallToken, DataValue::Str(token.unwrap())));
self.clone() self.clone()
.send_message( .send_message(
&&CommunicationValue::new(CommunicationType::IdentificationResponse) &&CommunicationValue::new(CommunicationType::IdentificationResponse)
@ -220,9 +204,7 @@ impl AnonymousClientConnection {
DataValue::Container( DataValue::Container(
serialized serialized
.iter() .iter()
.map(|(k, v)| { .map(|(k, v)| (k.to_id(&TypeMap::latest()), v.clone()))
(data_type_id(*k, &TypeMap::latest()), v.clone())
})
.collect(), .collect(),
), ),
), ),
@ -322,10 +304,7 @@ impl AnonymousClientConnection {
async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) { async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) {
let client_for_closure = self.clone(); let client_for_closure = self.clone();
tokio::spawn(async move { tokio::spawn(async move {
let response_cv = self let response_cv = get_omega_connection()
.state
.omega
.clone()
.await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20)))
.await; .await;
if let Ok(response_cv) = response_cv { if let Ok(response_cv) = response_cv {
@ -354,9 +333,7 @@ impl AnonymousClientConnection {
async fn handle_client_changed(self: Arc<Self>, cv: CommunicationValue) { async fn handle_client_changed(self: Arc<Self>, cv: CommunicationValue) {
if let DataValue::Str(status_str) = cv.get_data(DataType::UserState) { if let DataValue::Str(status_str) = cv.get_data(DataType::UserState) {
let user_status = UserStatus::from_str(&status_str).unwrap_or(UserStatus::user_online); let user_status = UserStatus::from_str(&status_str).unwrap_or(UserStatus::user_online);
self.state OmegaConnection::client_changed(self.user_id as i64, self.user_id as i64, user_status)
.omega
.client_changed(self.user_id as i64, self.user_id as i64, user_status)
.await; .await;
} }
} }
@ -394,10 +371,8 @@ impl AnonymousClientConnection {
return; return;
} }
}; };
let invited = self let invited =
.state call_manager::add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
.call_manager
.add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
.await; .await;
if !invited { if !invited {
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId) self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
@ -405,18 +380,14 @@ impl AnonymousClientConnection {
return; return;
} }
if !self if !call_manager::should_forward_invite(self.user_id, receiver_id as u64) {
.state
.call_manager
.should_forward_invite(self.user_id, receiver_id as u64)
{
let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id());
self.send_message(&response).await; self.send_message(&response).await;
return; return;
} }
// Find target RhoConnection // Find target RhoConnection
let target_rho = match self.state.rho.get_for_user(receiver_id).await { let target_rho = match rho_manager::get_rho_con_for_user(receiver_id).await {
Some(rho) => rho, Some(rho) => rho,
_ => { _ => {
// Get sender user ID // Get sender user ID
@ -435,7 +406,7 @@ impl AnonymousClientConnection {
DataValue::Str("call_invite".to_string()), DataValue::Str("call_invite".to_string()),
); );
let omega_conn = self.state.omega.clone(); let omega_conn = get_omega_connection();
// Send fire-and-forget, don't await to avoid blocking // Send fire-and-forget, don't await to avoid blocking
tokio::spawn(async move { tokio::spawn(async move {
let _ = omega_conn.send_message(&push_cv).await; let _ = omega_conn.send_message(&push_cv).await;
@ -496,39 +467,22 @@ impl AnonymousClientConnection {
} }
}; };
match self if let Some(token) = call_manager::get_call_token(user_id, call_id).await {
.state
.call_manager
.get_call_token(user_id, call_id)
.await
{
Ok(token) => {
let response = CommunicationValue::new(CommunicationType::CallToken) let response = CommunicationValue::new(CommunicationType::CallToken)
.with_id(cv.get_id()) .with_id(cv.get_id())
.with_receiver(user_id) .with_receiver(user_id)
.add_typed_default(DataType::CallToken, DataValue::Str(token)); .add_typed_default(DataType::CallToken, DataValue::Str(token.to_string()));
self.send_message(&response).await; self.send_message(&response).await;
} } else {
Err(error) => {
log::warn!(
"Unable to create anonymous call token for {}: {}",
call_id,
error
);
let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId) let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId)
.with_id(cv.get_id()) .with_id(cv.get_id())
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string())); .add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
self.send_message(&error_cv).await; self.send_message(&error_cv).await;
} return;
} }
} }
async fn handle_call_timeout_user(self: Arc<Self>, cv: CommunicationValue) { async fn handle_call_timeout_user(self: Arc<Self>, cv: CommunicationValue) {
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or("")) let call_id = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or("")).unwrap();
else {
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
.await;
return;
};
let user_id = cv let user_id = cv
.get_data(DataType::UserId) .get_data(DataType::UserId)
.as_signed_number() .as_signed_number()
@ -538,48 +492,41 @@ impl AnonymousClientConnection {
.as_signed_number() .as_signed_number()
.unwrap_or(0); .unwrap_or(0);
let Some(call) = self.state.call_manager.get_call(call_id).await else { let call = call_manager::get_call(call_id).await;
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNotFound) 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.try_into().unwrap())
.await; .await;
return;
};
let Some(caller) = call.get_caller(self.get_user_id()).await else {
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidUserId)
.await;
return;
};
if caller.has_admin() {
if let Some(target) = call.get_caller(user_id as u64).await {
target.set_timeout(untill as i64).await;
} }
} }
} }
async fn handle_call_disconnect_user(self: Arc<Self>, cv: CommunicationValue) { async fn handle_call_disconnect_user(self: Arc<Self>, cv: CommunicationValue) {
let Ok(call_id) = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or("")) let call_id = Uuid::from_str(cv.get_data(DataType::CallId).as_str().unwrap_or("")).unwrap();
else {
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidCallId)
.await;
return;
};
let user_id = cv let user_id = cv
.get_data(DataType::UserId) .get_data(DataType::UserId)
.as_signed_number() .as_signed_number()
.unwrap_or(0); .unwrap_or(0);
let Some(call) = self.state.call_manager.get_call(call_id).await else { let call = call_manager::get_call(call_id).await;
self.send_error_response(&cv.get_id(), CommunicationType::ErrorNotFound) if let Some(call) = call {
.await; if call
return; .get_caller(self.get_user_id())
}; .await
let Some(caller) = call.get_caller(self.get_user_id()).await else { .unwrap()
self.send_error_response(&cv.get_id(), CommunicationType::ErrorInvalidUserId) .has_admin()
.await; {
return;
};
if caller.has_admin() {
call.remove_caller(user_id as u64).await; call.remove_caller(user_id as u64).await;
} }
} }
}
/// Send error response /// Send error response
async fn send_error_response(self: Arc<Self>, message_id: &u32, error_type: CommunicationType) { async fn send_error_response(self: Arc<Self>, message_id: &u32, error_type: CommunicationType) {
@ -639,7 +586,6 @@ impl AnonymousClientConnection {
impl Clone for AnonymousClientConnection { impl Clone for AnonymousClientConnection {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
state: self.state.clone(),
sender: Arc::clone(&self.sender), sender: Arc::clone(&self.sender),
receiver: Arc::clone(&self.receiver), receiver: Arc::clone(&self.receiver),
user_id: self.user_id, user_id: self.user_id,
@ -649,7 +595,6 @@ impl Clone for AnonymousClientConnection {
user_name: Arc::clone(&self.user_name), user_name: Arc::clone(&self.user_name),
display_name: Arc::clone(&self.display_name), display_name: Arc::clone(&self.display_name),
avatar: Arc::clone(&self.avatar), avatar: Arc::clone(&self.avatar),
message_slots: Arc::clone(&self.message_slots),
} }
} }
} }

View file

@ -1,6 +1,7 @@
use dashmap::DashMap; use dashmap::DashMap;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rand::prelude::{IndexedRandom, RngExt}; use rand::Rng;
use rand::seq::SliceRandom;
use std::sync::Arc; use std::sync::Arc;
use crate::anonymous_clients::anonymous_client_connection::AnonymousClientConnection; use crate::anonymous_clients::anonymous_client_connection::AnonymousClientConnection;
@ -44,11 +45,11 @@ pub fn generate_username() -> String {
let adjectives = ["Swift", "Clever", "Brave", "Sneaky", "Fierce"]; let adjectives = ["Swift", "Clever", "Brave", "Sneaky", "Fierce"];
let nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Dragon"]; let nouns = ["Tiger", "Eagle", "Shark", "Wolf", "Dragon"];
let mut rng = rand::rng(); let mut rng = rand::thread_rng();
let adj = adjectives.choose(&mut rng).unwrap(); let adj = adjectives.choose(&mut rng).unwrap();
let noun = nouns.choose(&mut rng).unwrap(); let noun = nouns.choose(&mut rng).unwrap();
let number: u16 = rng.random_range(0..10000); let number: u16 = rng.gen_range(0..10000);
format!("{}{}{}", adj, noun, number) format!("{}{}{}", adj, noun, number)
} }

View file

@ -1,50 +0,0 @@
use std::sync::Arc;
use mtp::crypto::Keyring;
use crate::{
calls::{call_manager::CallManager, call_util::LiveKitService},
config::Config,
omega::omega_connection::OmegaConnection,
rho::rho_manager::RhoManager,
services::call_state::CallStateAggregator,
};
/*
* Holds startup dependencies so listener and connection constructors can take
* one explicit handle while the remaining manager migrations are completed.
*/
pub struct AppState {
pub config: Config,
pub keyring: Keyring,
pub omega: Arc<OmegaConnection>,
pub rho: Arc<RhoManager>,
pub call_manager: Arc<CallManager>,
pub call_state_aggregator: Arc<CallStateAggregator>,
pub livekit: Arc<LiveKitService>,
}
impl AppState {
pub fn new(
config: Config,
keyring: Keyring,
omega: Arc<OmegaConnection>,
rho: Arc<RhoManager>,
call_manager: Arc<CallManager>,
livekit: Arc<LiveKitService>,
) -> Arc<Self> {
Arc::new(Self {
config,
keyring,
omega,
rho,
call_state_aggregator: Arc::new(CallStateAggregator::new(call_manager.clone())),
call_manager,
livekit,
})
}
pub fn keyring_for_host(&self) -> Result<Keyring, String> {
Keyring::from_bytes(&self.keyring.to_bytes()).map_err(|error| error.to_string())
}
}

View file

@ -1,14 +1,13 @@
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, TypeMap}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue, TypeMap};
use serde_json::Map; use serde_json::Map;
use std::{collections::BTreeMap, sync::Arc, time::Duration}; use std::{collections::BTreeMap, env, sync::Arc, time::Duration};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
calls::{call_util::LiveKitService, caller::Caller, error::CallError}, calls::{call_util, caller::Caller},
omega::omega_connection::OmegaConnection, omega::omega_connection::get_omega_connection,
util::data_type_id,
}; };
pub struct CallGroup { pub struct CallGroup {
@ -18,7 +17,6 @@ pub struct CallGroup {
pub anonymous_joining: RwLock<bool>, pub anonymous_joining: RwLock<bool>,
pub short_link: RwLock<Option<String>>, pub short_link: RwLock<Option<String>>,
pub secrets: RwLock<BTreeMap<u64, CallSecretEnvelope>>, pub secrets: RwLock<BTreeMap<u64, CallSecretEnvelope>>,
livekit: Arc<LiveKitService>,
} }
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Debug, Eq, PartialEq)]
@ -34,27 +32,27 @@ impl CallSecretEnvelope {
pub fn from_data_value(value: &DataValue) -> Option<Self> { pub fn from_data_value(value: &DataValue) -> Option<Self> {
let tm = TypeMap::latest(); let tm = TypeMap::latest();
let secret_id = value let secret_id = value
.get_field(data_type_id(DataType::SecretId, &tm))? .get_field(DataType::SecretId.to_id(&tm))?
.as_str()? .as_str()?
.to_string(); .to_string();
let version_number = value let version_number = value
.get_field(data_type_id(DataType::VersionNumber, &tm))? .get_field(DataType::VersionNumber.to_id(&tm))?
.as_signed_number() .as_signed_number()
.and_then(|n| i64::try_from(n).ok()) .and_then(|n| i64::try_from(n).ok())
.or_else(|| { .or_else(|| {
value value
.get_field(data_type_id(DataType::VersionNumber, &tm))? .get_field(DataType::VersionNumber.to_id(&tm))?
.as_number() .as_number()
.and_then(|n| i64::try_from(n).ok()) .and_then(|n| i64::try_from(n).ok())
})?; })?;
let encrypted_secret = value let encrypted_secret = value
.get_field(data_type_id(DataType::EncryptedSecret, &tm))? .get_field(DataType::EncryptedSecret.to_id(&tm))?
.as_bytes()?; .as_bytes()?;
let kem_ciphertext = value let kem_ciphertext = value
.get_field(data_type_id(DataType::KemCiphertext, &tm))? .get_field(DataType::KemCiphertext.to_id(&tm))?
.as_bytes()?; .as_bytes()?;
let wrapping_scheme = value let wrapping_scheme = value
.get_field(data_type_id(DataType::WrappingScheme, &tm))? .get_field(DataType::WrappingScheme.to_id(&tm))?
.as_str()? .as_str()?
.to_string(); .to_string();
@ -71,23 +69,23 @@ impl CallSecretEnvelope {
let tm = TypeMap::latest(); let tm = TypeMap::latest();
let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new(); let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new();
map.insert( map.insert(
data_type_id(DataType::SecretId, &tm), DataType::SecretId.to_id(&tm),
DataValue::Str(self.secret_id.clone()), DataValue::Str(self.secret_id.clone()),
); );
map.insert( map.insert(
data_type_id(DataType::VersionNumber, &tm), DataType::VersionNumber.to_id(&tm),
DataValue::SignedNumber(self.version_number.into()), DataValue::SignedNumber(self.version_number.into()),
); );
map.insert( map.insert(
data_type_id(DataType::EncryptedSecret, &tm), DataType::EncryptedSecret.to_id(&tm),
DataValue::Bytes(self.encrypted_secret.clone()), DataValue::Bytes(self.encrypted_secret.clone()),
); );
map.insert( map.insert(
data_type_id(DataType::KemCiphertext, &tm), DataType::KemCiphertext.to_id(&tm),
DataValue::Bytes(self.kem_ciphertext.clone()), DataValue::Bytes(self.kem_ciphertext.clone()),
); );
map.insert( map.insert(
data_type_id(DataType::WrappingScheme, &tm), DataType::WrappingScheme.to_id(&tm),
DataValue::Str(self.wrapping_scheme.clone()), DataValue::Str(self.wrapping_scheme.clone()),
); );
DataValue::container_from_map(&map) DataValue::container_from_map(&map)
@ -100,14 +98,6 @@ pub fn call_invite_secret_from_cv(cv: &CommunicationValue) -> Option<CallSecretE
impl CallGroup { impl CallGroup {
pub fn new(call_id: Uuid, user: Arc<Caller>) -> Self { pub fn new(call_id: Uuid, user: Arc<Caller>) -> Self {
Self::new_with_service(call_id, user, Arc::new(LiveKitService::new(None)))
}
pub fn new_with_service(
call_id: Uuid,
user: Arc<Caller>,
livekit: Arc<LiveKitService>,
) -> Self {
CallGroup { CallGroup {
call_id, call_id,
members: RwLock::new(vec![user]), members: RwLock::new(vec![user]),
@ -115,7 +105,6 @@ impl CallGroup {
anonymous_joining: RwLock::new(false), anonymous_joining: RwLock::new(false),
short_link: RwLock::new(None), short_link: RwLock::new(None),
secrets: RwLock::new(BTreeMap::new()), secrets: RwLock::new(BTreeMap::new()),
livekit,
} }
} }
@ -137,7 +126,7 @@ impl CallGroup {
} }
pub async fn update_admins(&self) { pub async fn update_admins(&self) {
let call_metadata = match self.livekit.get_room_metadata(self.call_id).await { let call_metadata = match call_util::get_room_metadata(self.call_id).await {
Ok(metadata) => metadata, Ok(metadata) => metadata,
Err(_) => "{}".to_string(), Err(_) => "{}".to_string(),
}; };
@ -161,31 +150,17 @@ impl CallGroup {
call_metadata.insert("admins".to_string(), serde_json::json!(admin_ids)); call_metadata.insert("admins".to_string(), serde_json::json!(admin_ids));
if let Err(error) = self let _ = call_util::set_room_metadata(
.livekit
.set_room_metadata(
self.call_id, self.call_id,
serde_json::Value::Object(call_metadata).to_string(), serde_json::Value::Object(call_metadata).to_string(),
) )
.await .await;
{
log::warn!(
"Unable to update administrators for call {}: {}",
self.call_id,
error
);
}
} }
pub async fn set_anonymous_joining( pub async fn set_anonymous_joining(&self, enable: bool) {
&self,
enable: bool,
omega: &OmegaConnection,
omikron_id: u64,
) {
*self.anonymous_joining.write().await = enable; *self.anonymous_joining.write().await = enable;
let call_metadata = match self.livekit.get_room_metadata(self.call_id).await { let call_metadata = match call_util::get_room_metadata(self.call_id).await {
Ok(metadata) => metadata, Ok(metadata) => metadata,
Err(_) => "{}".to_string(), Err(_) => "{}".to_string(),
}; };
@ -200,27 +175,22 @@ impl CallGroup {
call_metadata.insert("anonymous_joining".to_string(), serde_json::json!(enable)); call_metadata.insert("anonymous_joining".to_string(), serde_json::json!(enable));
if let Err(error) = self let _ = call_util::set_room_metadata(
.livekit
.set_room_metadata(
self.call_id, self.call_id,
serde_json::Value::Object(call_metadata).to_string(), serde_json::Value::Object(call_metadata).to_string(),
) )
.await .await;
{
log::warn!(
"Unable to update anonymous access for call {}: {}",
self.call_id,
error
);
}
if self.short_link.read().await.is_none() { if self.short_link.read().await.is_none() {
let long_link = format!( let long_link = format!(
"https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}", "https://app.tensamin.net/call/anonymous?call_id={}&omikron_id={}",
self.call_id, omikron_id, self.call_id,
env::var("ID")
.unwrap_or("0".to_string())
.parse::<i64>()
.unwrap_or(0),
); );
let response_cv = omega let response_cv = get_omega_connection()
.await_response( .await_response(
&CommunicationValue::new(CommunicationType::ShortenLink) &CommunicationValue::new(CommunicationType::ShortenLink)
.add_typed_default(DataType::Link, DataValue::Str(long_link)), .add_typed_default(DataType::Link, DataValue::Str(long_link)),
@ -228,33 +198,33 @@ impl CallGroup {
) )
.await; .await;
if let Ok(response) = response_cv { if let Ok(response) = response_cv {
if let Some(link) = response.get_data(DataType::Link).as_str() { *self.short_link.write().await = Some(
*self.short_link.write().await = Some(link.to_string()); response
log::info!("Shortened link for call {}", self.call_id); .get_data(DataType::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) -> Result<Option<String>, CallError> { pub async fn create_anonymous_token(&self, user_id: u64) -> Option<String> {
if self.is_anonymous().await { if self.is_anonymous().await {
return self if let Ok(token) = call_util::create_token(user_id, self.call_id, false) {
.livekit return Some(token);
.create_token(user_id, self.call_id, false)
.map(Some);
} }
Ok(None) }
None
} }
pub async fn remove_caller(&self, user_id: u64) { pub async fn remove_caller(&self, user_id: u64) {
if let Err(error) = self.livekit.remove_participant(self.call_id, user_id).await { let _ = call_util::remove_participant(self.call_id, user_id).await;
log::warn!(
"Unable to remove user {} from call {}: {}",
user_id,
self.call_id,
error
);
}
self.members self.members
.write() .write()
.await .await
@ -293,19 +263,19 @@ mod tests {
let tm = TypeMap::latest(); let tm = TypeMap::latest();
let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new(); let mut map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new();
map.insert( map.insert(
data_type_id(DataType::SecretId, &tm), DataType::SecretId.to_id(&tm),
DataValue::Str("call:test:main".to_string()), DataValue::Str("call:test:main".to_string()),
); );
map.insert( map.insert(
data_type_id(DataType::VersionNumber, &tm), DataType::VersionNumber.to_id(&tm),
DataValue::SignedNumber(1), DataValue::SignedNumber(1),
); );
map.insert( map.insert(
data_type_id(DataType::EncryptedSecret, &tm), DataType::EncryptedSecret.to_id(&tm),
DataValue::Bytes(vec![1, 2, 3]), DataValue::Bytes(vec![1, 2, 3]),
); );
map.insert( map.insert(
data_type_id(DataType::WrappingScheme, &tm), DataType::WrappingScheme.to_id(&tm),
DataValue::Str("mtp-call-secret-kem-chacha20poly1305-hkdf-sha256-v1".to_string()), DataValue::Str("mtp-call-secret-kem-chacha20poly1305-hkdf-sha256-v1".to_string()),
); );

View file

@ -1,45 +1,19 @@
use dashmap::DashMap; use dashmap::DashMap;
use once_cell::sync::Lazy;
use std::sync::Arc; use std::sync::Arc;
use uuid::Uuid; use uuid::Uuid;
use crate::calls::{ use crate::calls::{
call_group::{CallGroup, CallSecretEnvelope}, call_group::{CallGroup, CallSecretEnvelope},
call_util::LiveKitService, call_util,
caller::Caller, caller::Caller,
error::CallError,
}; };
pub struct CallManager { pub static CALL_GROUPS: Lazy<DashMap<Uuid, Arc<CallGroup>>> = Lazy::new(|| DashMap::new());
pub(crate) groups: DashMap<Uuid, Arc<CallGroup>>,
pub livekit: Arc<LiveKitService>,
}
impl Default for CallManager {
fn default() -> Self {
Self {
groups: DashMap::new(),
livekit: Arc::new(LiveKitService::new(None)),
}
}
}
impl CallManager {
pub fn new(livekit: Arc<LiveKitService>) -> Self {
Self {
groups: DashMap::new(),
livekit,
}
}
#[allow(dead_code)] #[allow(dead_code)]
pub async fn get_call_invites(&self, user_id: u64) -> Vec<Arc<Caller>> { pub async fn get_call_invites(user_id: u64) -> Vec<Arc<Caller>> {
let mut callers = Vec::new(); let mut callers = Vec::new();
let call_groups = self for (_, cg) in CALL_GROUPS.clone().into_iter() {
.groups
.iter()
.map(|entry| entry.value().clone())
.collect::<Vec<_>>();
for cg in call_groups {
let members = cg.members.read().await; let members = cg.members.read().await;
for member in members.iter() { for member in members.iter() {
if member.user_id == user_id { if member.user_id == user_id {
@ -50,22 +24,17 @@ impl CallManager {
callers callers
} }
pub async fn get_call(&self, call_id: Uuid) -> Option<Arc<CallGroup>> { pub async fn get_call(call_id: Uuid) -> Option<Arc<CallGroup>> {
if let Some(b) = self.groups.get(&call_id) { if let Some(b) = CALL_GROUPS.get(&call_id) {
Some(b.clone()) Some(b.clone())
} else { } else {
None None
} }
} }
pub async fn get_call_groups(&self, user_id: u64) -> Vec<Arc<CallGroup>> { pub async fn get_call_groups(user_id: u64) -> Vec<Arc<CallGroup>> {
let mut call_groups = Vec::new(); let mut call_groups = Vec::new();
let tracked_groups = self for (_, cg) in CALL_GROUPS.clone().into_iter() {
.groups
.iter()
.map(|entry| entry.value().clone())
.collect::<Vec<_>>();
for cg in tracked_groups {
let is_member = { let is_member = {
let members = cg.members.read().await; let members = cg.members.read().await;
members.iter().any(|m| m.user_id == user_id) members.iter().any(|m| m.user_id == user_id)
@ -78,53 +47,40 @@ impl CallManager {
call_groups call_groups
} }
pub async fn get_call_token(&self, user_id: u64, call_id: Uuid) -> Result<String, CallError> { pub async fn get_call_token(user_id: u64, call_id: Uuid) -> Option<String> {
if let Some(cg) = self.groups.get(&call_id) { if let Some(cg) = CALL_GROUPS.get(&call_id) {
let mut members = cg.members.write().await; let mut members = cg.members.write().await;
if let Some(member) = members.iter().find(|m| m.user_id == user_id) { if let Some(member) = members.iter().find(|m| m.user_id == user_id) {
return self.livekit.create_token( return Some(member.create_token());
member.user_id,
member.call_id,
member.has_admin(),
);
} }
let new_caller = Arc::new(Caller::new(user_id, call_id, false)); let new_caller = Arc::new(Caller::new(user_id, call_id, false));
let token = self.livekit.create_token( let token = new_caller.create_token();
new_caller.user_id,
new_caller.call_id,
new_caller.has_admin(),
)?;
members.push(new_caller); members.push(new_caller);
return Ok(token); return Some(token);
} }
let caller = Arc::new(Caller::new(user_id, call_id, true)); let caller = Arc::new(Caller::new(user_id, call_id, true));
let call_group = Arc::new(CallGroup::new_with_service( let call_group = Arc::new(CallGroup::new(call_id, caller.clone()));
call_id,
caller.clone(),
self.livekit.clone(),
));
self.livekit.create_room(call_id).await?; CALL_GROUPS.insert(call_id, call_group.clone());
self.groups.insert(call_id, call_group.clone());
let _ = call_util::create_room(call_id).await;
call_group.update_admins().await; call_group.update_admins().await;
self.livekit Some(caller.create_token())
.create_token(caller.user_id, caller.call_id, caller.has_admin())
} }
pub async fn add_invite( pub async fn add_invite(
&self,
call_id: Uuid, call_id: Uuid,
inviter_id: u64, inviter_id: u64,
invitee_id: u64, invitee_id: u64,
secret: CallSecretEnvelope, secret: CallSecretEnvelope,
) -> bool { ) -> bool {
if let Some(cg) = self.groups.get(&call_id) { if let Some(cg) = CALL_GROUPS.get(&call_id) {
let mut members = cg.members.write().await; let mut members = cg.members.write().await;
let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id); let is_inviter_member = members.iter().any(|m| m.user_id == inviter_id);
@ -143,10 +99,9 @@ impl CallManager {
false false
} }
pub fn should_forward_invite(&self, inviter_id: u64, invitee_id: u64) -> bool { pub fn should_forward_invite(inviter_id: u64, invitee_id: u64) -> bool {
inviter_id != invitee_id inviter_id != invitee_id
} }
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -171,22 +126,19 @@ mod tests {
call_id, call_id,
Arc::new(Caller::new(sender_id, call_id, true)), Arc::new(Caller::new(sender_id, call_id, true)),
)); ));
let manager = CallManager::default(); CALL_GROUPS.insert(call_id, group.clone());
manager.groups.insert(call_id, group.clone());
let receiver_secret = envelope("receiver"); let receiver_secret = envelope("receiver");
assert!( assert!(add_invite(call_id, sender_id, receiver_id, receiver_secret.clone(),).await);
manager
.add_invite(call_id, sender_id, receiver_id, receiver_secret.clone(),)
.await
);
assert_eq!( assert_eq!(
group.get_secret_for_user(receiver_id).await, group.get_secret_for_user(receiver_id).await,
Some(receiver_secret.clone()) Some(receiver_secret.clone())
); );
assert_eq!(group.get_secret_for_user(sender_id).await, None); assert_eq!(group.get_secret_for_user(sender_id).await, None);
CALL_GROUPS.remove(&call_id);
} }
#[tokio::test] #[tokio::test]
@ -197,16 +149,11 @@ mod tests {
call_id, call_id,
Arc::new(Caller::new(sender_id, call_id, true)), Arc::new(Caller::new(sender_id, call_id, true)),
)); ));
let manager = CallManager::default(); CALL_GROUPS.insert(call_id, group.clone());
manager.groups.insert(call_id, group.clone());
let secret = envelope("self"); let secret = envelope("self");
assert!( assert!(add_invite(call_id, sender_id, sender_id, secret.clone()).await);
manager
.add_invite(call_id, sender_id, sender_id, secret.clone())
.await
);
assert_eq!(group.get_secret_for_user(sender_id).await, Some(secret)); assert_eq!(group.get_secret_for_user(sender_id).await, Some(secret));
assert_eq!( assert_eq!(
@ -219,12 +166,13 @@ mod tests {
.count(), .count(),
1 1
); );
CALL_GROUPS.remove(&call_id);
} }
#[test] #[test]
fn self_invites_are_not_forwarded() { fn self_invites_are_not_forwarded() {
let manager = CallManager::default(); assert!(!should_forward_invite(44, 44));
assert!(!manager.should_forward_invite(44, 44)); assert!(should_forward_invite(44, 55));
assert!(manager.should_forward_invite(44, 55));
} }
} }

View file

@ -1,66 +1,59 @@
use std::{str::FromStr, sync::Arc, time::Duration}; use livekit_api::services::room::CreateRoomOptions;
use livekit_api::{ use livekit_api::{
access_token::{self}, access_token::{self},
services::room::{CreateRoomOptions, RoomClient}, services::room::RoomClient,
}; };
use livekit_protocol::Room; use livekit_protocol::Room;
use std::env;
use std::str::FromStr;
use std::time::Duration;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{calls::call_manager::CALL_GROUPS, log, log_err, util::logger::PrintType};
calls::{call_manager::CallManager, error::CallError},
config::LiveKitConfig, pub fn get_livekit() -> Result<(String, String, String), ()> {
log, log_err, let hostname = match env::var("LIVEKI_HOSTNAME") {
util::logger::PrintType, Ok(secret) => secret,
Err(_) => {
log_err!(0, PrintType::General, "LIVEKI_HOSTNAME not set!");
return Err(());
}
}; };
let api_key = match env::var("LIVEKIT_API_KEY") {
const LIVEKIT_REQUEST_TIMEOUT: Duration = Duration::from_secs(10); Ok(key) => key,
Err(_) => {
pub struct LiveKitService { log_err!(0, PrintType::General, "LIVEKIT_API_KEY not set!");
config: Option<LiveKitConfig>, 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))
} }
impl LiveKitService { pub async fn create_room(call_id: Uuid) -> Result<(), ()> {
pub fn new(config: Option<LiveKitConfig>) -> Self { let (hostname, api_key, api_secret) = get_livekit()?;
Self { config } let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
}
fn livekit_config(&self) -> Result<&LiveKitConfig, CallError> { let options = CreateRoomOptions::default();
self.config.as_ref().ok_or(CallError::NotConfigured)
}
fn room_client(&self) -> Result<RoomClient, CallError> { room_service
let config = self.livekit_config()?; .create_room(&call_id.to_string(), options)
Ok(RoomClient::with_api_key(
&config.hostname,
&config.api_key,
&config.api_secret,
))
}
pub async fn create_room(&self, call_id: Uuid) -> Result<(), CallError> {
tokio::time::timeout(
LIVEKIT_REQUEST_TIMEOUT,
self.room_client()?
.create_room(&call_id.to_string(), CreateRoomOptions::default()),
)
.await .await
.map_err(|_| CallError::RequestTimedOut { call_id })? .map_err(|_| ())?;
.map_err(|error| CallError::RoomCreationFailed {
call_id,
detail: error.to_string(),
})?;
Ok(()) Ok(())
} }
pub fn create_token( pub fn create_token(user_id: u64, call_id: Uuid, has_admin: bool) -> Result<String, ()> {
&self, let (_, api_key, api_secret) = get_livekit()?;
user_id: u64,
call_id: Uuid, let token = access_token::AccessToken::with_api_key(&api_key, &api_secret)
has_admin: bool,
) -> Result<String, CallError> {
let config = self.livekit_config()?;
access_token::AccessToken::with_api_key(&config.api_key, &config.api_secret)
.with_identity(&user_id.to_string()) .with_identity(&user_id.to_string())
.with_grants(access_token::VideoGrants { .with_grants(access_token::VideoGrants {
room_join: true, room_join: true,
@ -69,107 +62,78 @@ impl LiveKitService {
room: call_id.to_string(), room: call_id.to_string(),
..Default::default() ..Default::default()
}) })
.to_jwt() .to_jwt();
.map_err(|error| CallError::TokenCreationFailed { if let Ok(token) = token {
detail: error.to_string(), 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)] #[allow(dead_code)]
pub async fn get_room(&self, call_id: Uuid) -> Result<(RoomClient, Room), CallError> { pub async fn get_room_metadata(call_id: Uuid) -> Result<String, ()> {
let room_service = self.room_client()?; if let Ok((_, room)) = get_room(call_id).await {
let rooms =
tokio::time::timeout(LIVEKIT_REQUEST_TIMEOUT, room_service.list_rooms(Vec::new()))
.await
.map_err(|_| CallError::RequestTimedOut { call_id })?
.map_err(|error| CallError::RoomLookupFailed {
detail: error.to_string(),
})?;
rooms
.into_iter()
.find(|room| room.name == call_id.to_string())
.map(|room| (room_service, room))
.ok_or(CallError::RoomNotFound { call_id })
}
pub async fn remove_participant(&self, call_id: Uuid, user_id: u64) -> Result<(), CallError> {
tokio::time::timeout(
LIVEKIT_REQUEST_TIMEOUT,
self.room_client()?
.remove_participant(&call_id.to_string(), &user_id.to_string()),
)
.await
.map_err(|_| CallError::RequestTimedOut { call_id })?
.map_err(|error| CallError::ParticipantRemovalFailed {
call_id,
user_id,
detail: error.to_string(),
})?;
Ok(())
}
#[allow(dead_code)]
pub async fn get_room_metadata(&self, call_id: Uuid) -> Result<String, CallError> {
let (_, room) = self.get_room(call_id).await?;
Ok(room.metadata) Ok(room.metadata)
} else {
Err(())
}
} }
pub async fn set_room_metadata( pub async fn set_room_metadata(call_id: Uuid, metadata: String) -> Result<(), ()> {
&self, let (hostname, api_key, api_secret) = get_livekit()?;
call_id: Uuid, let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
metadata: String,
) -> Result<(), CallError> { room_service
tokio::time::timeout( .update_room_metadata(&call_id.to_string(), &metadata)
LIVEKIT_REQUEST_TIMEOUT,
self.room_client()?
.update_room_metadata(&call_id.to_string(), &metadata),
)
.await .await
.map_err(|_| CallError::RequestTimedOut { call_id })? .map_err(|_| ())?;
.map_err(|error| CallError::MetadataUpdateFailed {
call_id,
detail: error.to_string(),
})?;
Ok(()) Ok(())
} }
pub fn garbage_collect_calls(self: Arc<Self>, manager: Arc<CallManager>) { pub fn garbage_collect_calls() {
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {
match self.room_client() { if let Ok((hostname, api_key, api_secret)) = get_livekit() {
Ok(room_service) => clean_calls(&manager, room_service).await, let room_service = RoomClient::with_api_key(&hostname, &api_key, &api_secret);
Err(CallError::NotConfigured) => return, clean_calls(room_service).await;
Err(error) => log_err!(0, PrintType::Call, "Call cleanup skipped: {error}"),
} }
tokio::time::sleep(Duration::from_secs(2)).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();
pub async fn clean_calls(manager: &CallManager, room_service: RoomClient) { let mut call_ids: Vec<Uuid> = Vec::new();
let rooms = let mut no_users: Vec<Uuid> = Vec::new();
match tokio::time::timeout(LIVEKIT_REQUEST_TIMEOUT, room_service.list_rooms(Vec::new()))
.await
{
Ok(Ok(rooms)) => rooms,
Ok(Err(error)) => {
log_err!(
0,
PrintType::Call,
"Unable to list LiveKit rooms during cleanup: {error}"
);
return;
}
Err(_) => {
log_err!(0, PrintType::Call, "LiveKit room cleanup timed out");
return;
}
};
let mut call_ids = Vec::new();
let mut no_users = Vec::new();
for room in rooms { for room in rooms {
if let Ok(id) = Uuid::from_str(&room.name) { if let Ok(id) = Uuid::from_str(&room.name) {
if room.num_participants == 0 { if room.num_participants == 0 {
@ -178,35 +142,23 @@ pub async fn clean_calls(manager: &CallManager, room_service: RoomClient) {
call_ids.push(id); call_ids.push(id);
} }
} }
let size_pre = CALL_GROUPS.len();
let size_pre = manager.groups.len(); for (id, _) in CALL_GROUPS.clone().into_iter() {
let tracked_calls = manager
.groups
.iter()
.map(|entry| *entry.key())
.collect::<Vec<_>>();
for id in tracked_calls {
if !call_ids.contains(&id) { if !call_ids.contains(&id) {
manager.groups.remove(&id); CALL_GROUPS.remove(&id);
} }
} }
for (_, cg) in CALL_GROUPS.clone().into_iter() {
*cg.show.write().await = !no_users.contains(&cg.call_id);
}
let call_groups = manager let size_post = CALL_GROUPS.len();
.groups if size_pre - size_post != 0 {
.iter()
.map(|entry| entry.value().clone())
.collect::<Vec<_>>();
for call_group in call_groups {
*call_group.show.write().await = !no_users.contains(&call_group.call_id);
}
let size_post = manager.groups.len();
if size_pre != size_post {
log!( log!(
0, 0,
PrintType::Call, PrintType::Call,
"Cleaned {} calls, {} remaining", "Cleaned {} calls, {} remaining",
size_pre.saturating_sub(size_post), size_pre - size_post,
size_post size_post
); );
} }

View file

@ -3,7 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use uuid::Uuid; use uuid::Uuid;
use crate::calls::{call_util::LiveKitService, error::CallError}; use crate::calls::call_util;
pub struct Caller { pub struct Caller {
pub user_id: u64, pub user_id: u64,
@ -39,7 +39,11 @@ impl Caller {
pub async fn set_timeout(&self, timeout: i64) { pub async fn set_timeout(&self, timeout: i64) {
*self.timeout.write().await = timeout; *self.timeout.write().await = timeout;
} }
pub fn create_token(&self, livekit: &LiveKitService) -> Result<String, CallError> { pub fn create_token(&self) -> String {
livekit.create_token(self.user_id, self.call_id, self.has_admin()) if let Ok(token) = call_util::create_token(self.user_id, self.call_id, self.has_admin()) {
token
} else {
String::new()
}
} }
} }

View file

@ -1,25 +0,0 @@
use thiserror::Error;
#[derive(Debug, Error)]
pub enum CallError {
#[error("LiveKit is not configured")]
NotConfigured,
#[error("LiveKit request for room {call_id} timed out")]
RequestTimedOut { call_id: uuid::Uuid },
#[error("failed to create room {call_id}: {detail}")]
RoomCreationFailed { call_id: uuid::Uuid, detail: String },
#[error("room {call_id} was not found")]
RoomNotFound { call_id: uuid::Uuid },
#[error("failed to list rooms: {detail}")]
RoomLookupFailed { detail: String },
#[error("failed to remove participant {user_id} from room {call_id}: {detail}")]
ParticipantRemovalFailed {
call_id: uuid::Uuid,
user_id: u64,
detail: String,
},
#[error("failed to update room {call_id} metadata: {detail}")]
MetadataUpdateFailed { call_id: uuid::Uuid, detail: String },
#[error("failed to create access token: {detail}")]
TokenCreationFailed { detail: String },
}

View file

@ -2,4 +2,3 @@ pub mod call_group;
pub mod call_manager; pub mod call_manager;
pub mod call_util; pub mod call_util;
pub mod caller; pub mod caller;
pub mod error;

View file

@ -1,133 +0,0 @@
use std::env;
use thiserror::Error;
const DEFAULT_RHO_PORT: u16 = 443;
const DEFAULT_OMEGA_HOST: &str = "tensamin.net";
const DEFAULT_OMEGA_PORT: u16 = 9187;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LiveKitConfig {
pub hostname: String,
pub api_key: String,
pub api_secret: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Config {
pub rho_port: u16,
pub omega_host: String,
pub omega_port: u16,
pub omikron_id: u64,
pub livekit: Option<LiveKitConfig>,
}
#[derive(Debug, Error, Eq, PartialEq)]
pub enum ConfigError {
#[error("{name} must be a valid {kind}")]
InvalidValue {
name: &'static str,
kind: &'static str,
},
#[error("LIVEKIT_HOSTNAME, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET must be set together")]
IncompleteLiveKitCredentials,
}
impl Config {
pub fn from_environment() -> Result<Self, ConfigError> {
let rho_port = parse_or_default("RHO_PORT", DEFAULT_RHO_PORT)?;
let omega_port = parse_or_default("OMEGA_PORT", DEFAULT_OMEGA_PORT)?;
let omikron_id = parse_or_default("ID", 0_u64)?;
let omega_host = env::var("OMEGA_HOST")
.unwrap_or_else(|_| DEFAULT_OMEGA_HOST.to_string())
.trim()
.to_string();
if omega_host.is_empty() {
return Err(ConfigError::InvalidValue {
name: "OMEGA_HOST",
kind: "non-empty host name",
});
}
Ok(Self {
rho_port,
omega_host,
omega_port,
omikron_id,
livekit: livekit_from_environment()?,
})
}
}
fn parse_or_default<T>(name: &'static str, default: T) -> Result<T, ConfigError>
where
T: std::str::FromStr,
{
match env::var(name) {
Ok(value) => value.trim().parse().map_err(|_| ConfigError::InvalidValue {
name,
kind: "number",
}),
Err(_) => Ok(default),
}
}
fn livekit_from_environment() -> Result<Option<LiveKitConfig>, ConfigError> {
livekit_from_values(
env::var("LIVEKIT_HOSTNAME").ok(),
env::var("LIVEKIT_API_KEY").ok(),
env::var("LIVEKIT_API_SECRET").ok(),
)
}
fn livekit_from_values(
hostname: Option<String>,
api_key: Option<String>,
api_secret: Option<String>,
) -> Result<Option<LiveKitConfig>, ConfigError> {
match (hostname, api_key, api_secret) {
(None, None, None) => Ok(None),
(Some(hostname), Some(api_key), Some(api_secret))
if !hostname.trim().is_empty()
&& !api_key.trim().is_empty()
&& !api_secret.trim().is_empty() =>
{
Ok(Some(LiveKitConfig {
hostname,
api_key,
api_secret,
}))
}
_ => Err(ConfigError::IncompleteLiveKitCredentials),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn livekit_credentials_must_be_complete() {
assert_eq!(
livekit_from_values(Some("host".to_string()), Some("key".to_string()), None),
Err(ConfigError::IncompleteLiveKitCredentials)
);
}
#[test]
fn complete_livekit_credentials_are_loaded() {
assert_eq!(
livekit_from_values(
Some("https://livekit.example".to_string()),
Some("key".to_string()),
Some("secret".to_string()),
),
Ok(Some(LiveKitConfig {
hostname: "https://livekit.example".to_string(),
api_key: "key".to_string(),
api_secret: "secret".to_string(),
}))
);
}
}

View file

@ -1,13 +1,11 @@
mod anonymous_clients; mod anonymous_clients;
mod app_state;
mod calls; mod calls;
mod config;
mod data; mod data;
mod omega; mod omega;
mod rho; mod rho;
mod services;
mod util; mod util;
use std::env;
use std::path::PathBuf; use std::path::PathBuf;
use dotenv::dotenv; use dotenv::dotenv;
@ -21,30 +19,29 @@ use mtp::crypto::Keyring;
use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle}; use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle};
use crate::{ use crate::{
app_state::AppState, calls::call_util::garbage_collect_calls, omega::omega_connection::get_omega_connection,
calls::{call_manager::CallManager, call_util::LiveKitService}, rho::server::start, util::logger::startup,
config::Config,
omega::omega_connection::{OmegaConnection, start_task_cleanup_loop},
rho::rho_manager::RhoManager,
rho::server::start,
util::logger::startup,
}; };
const KEYRING_PATH: &str = "./omikron.mk"; const KEYRING_PATH: &str = "./omikron.mk";
const PUBLIC_KEY_PATH: &str = "./omikron.mpkb"; const PUBLIC_KEY_PATH: &str = "./omikron.mpkb";
fn load_keyring() -> Result<Keyring, String> { static KEYRING: Lazy<Keyring> = Lazy::new(|| {
match load_keyring_raw(KEYRING_PATH) { load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| {
Ok(keyring) => Ok(keyring),
Err(_) => {
let kr = Keyring::generate(); let kr = Keyring::generate();
save_keyring_raw(&kr, KEYRING_PATH).map_err(|error| error.to_string())?; save_keyring_raw(&kr, KEYRING_PATH).expect("Failed to save generated keyring");
save_public_key_bundle(&kr.public_key_bundle(), PUBLIC_KEY_PATH) save_public_key_bundle(&kr.public_key_bundle(), PUBLIC_KEY_PATH)
.map_err(|error| error.to_string())?; .expect("Failed to save generated public key bundle");
eprintln!("Generated new keyring at {}", KEYRING_PATH); eprintln!("Generated new keyring at {}", KEYRING_PATH);
Ok(kr) kr
} })
});
pub fn get_keyring() -> &'static Keyring {
&KEYRING
} }
pub fn load_keyring() -> Keyring {
Keyring::from_bytes(&KEYRING.to_bytes()).unwrap()
} }
#[tokio::main] #[tokio::main]
@ -56,51 +53,18 @@ async fn main() {
dotenv().ok(); dotenv().ok();
startup(); startup();
let config = match Config::from_environment() { let rho_port = env::var("RHO_PORT")
Ok(config) => config, .ok()
Err(error) => { .and_then(|s| s.parse().ok())
eprintln!("Invalid configuration: {error}"); .unwrap_or(443);
return;
}
};
let keyring = match load_keyring() { get_omega_connection();
Ok(keyring) => keyring,
Err(error) => {
eprintln!("Unable to load keyring: {error}");
return;
}
};
let omega_keyring = match keyring_for_omega(&keyring) {
Ok(keyring) => keyring,
Err(error) => {
eprintln!("Unable to copy keyring for Omega: {error}");
return;
}
};
let rho = std::sync::Arc::new(RhoManager::new());
let omega = std::sync::Arc::new(OmegaConnection::from_config(
&config,
omega_keyring,
rho.clone(),
));
omega.clone().start().await;
start_task_cleanup_loop(omega.clone());
let livekit = std::sync::Arc::new(LiveKitService::new(config.livekit.clone()));
let call_manager = std::sync::Arc::new(CallManager::new(livekit.clone()));
let state = AppState::new(config.clone(), keyring, omega, rho, call_manager, livekit);
tokio::spawn(async move { tokio::spawn(async move {
if let Err(e) = start(state).await { if let Err(e) = start(rho_port).await {
log_err!(0, util::logger::PrintType::General, "{}", e); log_err!(0, util::logger::PrintType::General, "{}", e);
} }
}); });
garbage_collect_calls();
if let Err(error) = tokio::signal::ctrl_c().await { tokio::signal::ctrl_c().await.unwrap();
eprintln!("Unable to wait for shutdown signal: {error}");
}
}
fn keyring_for_omega(keyring: &Keyring) -> Result<Keyring, String> {
Keyring::from_bytes(&keyring.to_bytes()).map_err(|error| error.to_string())
} }

View file

@ -1,6 +1,8 @@
use crate::{ use crate::{
config::Config, data::user::UserStatus, log_cv_in, log_cv_out, log_err, log_in, data::user::UserStatus,
rho::rho_manager::RhoManager, util::logger::PrintType, load_keyring, log_cv_in, log_cv_out, log_err, log_in,
rho::rho_manager::{self, RHO_CONNECTIONS, connection_count},
util::logger::PrintType,
}; };
use dashmap::DashMap; use dashmap::DashMap;
use mtp::client::{Client, Receiver, Sender}; use mtp::client::{Client, Receiver, Sender};
@ -11,7 +13,8 @@ use mtp::{
host::{Policy, SendMode}, host::{Policy, SendMode},
}; };
use mtp_transport::ConnectionHandle; use mtp_transport::ConnectionHandle;
use std::{collections::HashMap, sync::Arc, time::Duration}; use once_cell::sync::Lazy;
use std::{collections::HashMap, env, sync::Arc, time::Duration};
use tokio::{ use tokio::{
sync::{Mutex, RwLock, mpsc, watch}, sync::{Mutex, RwLock, mpsc, watch},
task::JoinHandle, task::JoinHandle,
@ -20,15 +23,32 @@ use tokio::{
use uuid::Uuid; use uuid::Uuid;
// ============================================================================
// Configuration
// ============================================================================
const OMEGA_HOST_DEFAULT: &str = "tensamin.net";
const OMEGA_PORT_DEFAULT: u16 = 9187;
fn omega_host_and_port() -> (String, u16) {
let host = env::var("OMEGA_HOST")
.map(|s| s.trim().to_string())
.unwrap_or_else(|_| OMEGA_HOST_DEFAULT.to_string());
let port = env::var("OMEGA_PORT")
.ok()
.and_then(|s| s.trim().parse().ok())
.unwrap_or(OMEGA_PORT_DEFAULT);
(host, port)
}
const RECONNECT_DELAY: Duration = Duration::from_secs(5); const RECONNECT_DELAY: Duration = Duration::from_secs(5);
const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300); const MAX_RECONNECT_DELAY: Duration = Duration::from_secs(300);
const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10); const CONNECTION_TIMEOUT: Duration = Duration::from_secs(10);
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60); const TASK_CLEANUP_INTERVAL: Duration = Duration::from_secs(60);
const TASK_MAX_AGE: Duration = Duration::from_secs(60); const TASK_MAX_AGE: Duration = Duration::from_secs(60);
const MAX_CONCURRENT_REQUESTS: usize = 128;
const CIRCUIT_BREAKER_FAILURE_THRESHOLD: u32 = 3;
const CIRCUIT_BREAKER_COOLDOWN: Duration = Duration::from_secs(30);
// ============================================================================ // ============================================================================
// Waiting Task System // Waiting Task System
@ -39,40 +59,13 @@ pub struct WaitingTask {
pub inserted_at: Instant, pub inserted_at: Instant,
} }
struct CircuitBreaker { pub static WAITING_TASKS: Lazy<DashMap<u32, WaitingTask>> = Lazy::new(DashMap::new);
consecutive_failures: u32,
open_until: Option<Instant>,
}
impl CircuitBreaker { pub fn start_task_cleanup_loop() {
fn allow_request(&mut self) -> bool { tokio::spawn(async {
if self.open_until.is_some_and(|until| until > Instant::now()) {
return false;
}
self.open_until = None;
true
}
fn record_success(&mut self) {
self.consecutive_failures = 0;
self.open_until = None;
}
fn record_failure(&mut self) {
self.consecutive_failures += 1;
if self.consecutive_failures >= CIRCUIT_BREAKER_FAILURE_THRESHOLD {
self.open_until = Some(Instant::now() + CIRCUIT_BREAKER_COOLDOWN);
}
}
}
pub fn start_task_cleanup_loop(omega: Arc<OmegaConnection>) {
tokio::spawn(async move {
loop { loop {
sleep(TASK_CLEANUP_INTERVAL).await; sleep(TASK_CLEANUP_INTERVAL).await;
omega WAITING_TASKS.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE);
.waiting_tasks
.retain(|_, v| v.inserted_at.elapsed() < TASK_MAX_AGE);
} }
}); });
} }
@ -116,7 +109,6 @@ pub struct OmegaConnection {
connection_loop_handle: Arc<Mutex<Option<JoinHandle<()>>>>, connection_loop_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
host: String, host: String,
port: u16, port: u16,
omikron_id: u64,
last_ping: Arc<Mutex<i64>>, last_ping: Arc<Mutex<i64>>,
heartbeat_handle: Arc<Mutex<Option<JoinHandle<()>>>>, heartbeat_handle: Arc<Mutex<Option<JoinHandle<()>>>>,
message_send_times: Arc<Mutex<HashMap<Uuid, Instant>>>, message_send_times: Arc<Mutex<HashMap<Uuid, Instant>>>,
@ -124,42 +116,31 @@ pub struct OmegaConnection {
shutdown_tx: Arc<Mutex<Option<watch::Sender<bool>>>>, shutdown_tx: Arc<Mutex<Option<watch::Sender<bool>>>>,
// Track if we should reconnect on close // Track if we should reconnect on close
reconnect_on_close: Arc<RwLock<bool>>, reconnect_on_close: Arc<RwLock<bool>>,
request_slots: tokio::sync::Semaphore,
circuit_breaker: Mutex<CircuitBreaker>,
keyring: mtp::crypto::Keyring,
rho: Arc<RhoManager>,
waiting_tasks: DashMap<u32, WaitingTask>,
} }
impl OmegaConnection { impl OmegaConnection {
pub fn from_config( pub fn new() -> Self {
config: &Config, let (host, port) = omega_host_and_port();
keyring: mtp::crypto::Keyring, Self::with_host(&host, port)
rho: Arc<RhoManager>, }
) -> Self {
// omikron and omega are both served over publicly trusted (fullchain) certs on
// the same domain, so the client uses system root trust - no pinning needed.
pub fn with_host(host: &str, port: u16) -> Self {
let (shutdown_tx, _) = watch::channel(false); let (shutdown_tx, _) = watch::channel(false);
OmegaConnection { OmegaConnection {
state: Arc::new(RwLock::new(ConnectionState::Disconnected)), state: Arc::new(RwLock::new(ConnectionState::Disconnected)),
sender: Arc::new(RwLock::new(None)), sender: Arc::new(RwLock::new(None)),
connection_loop_handle: Arc::new(Mutex::new(None)), connection_loop_handle: Arc::new(Mutex::new(None)),
host: config.omega_host.clone(), host: host.to_string(),
port: config.omega_port, port,
omikron_id: config.omikron_id,
last_ping: Arc::new(Mutex::new(-1)), last_ping: Arc::new(Mutex::new(-1)),
heartbeat_handle: Arc::new(Mutex::new(None)), heartbeat_handle: Arc::new(Mutex::new(None)),
message_send_times: Arc::new(Mutex::new(HashMap::new())), message_send_times: Arc::new(Mutex::new(HashMap::new())),
connection_id: Uuid::new_v4(), connection_id: Uuid::new_v4(),
shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))), shutdown_tx: Arc::new(Mutex::new(Some(shutdown_tx))),
reconnect_on_close: Arc::new(RwLock::new(true)), reconnect_on_close: Arc::new(RwLock::new(true)),
request_slots: tokio::sync::Semaphore::new(MAX_CONCURRENT_REQUESTS),
circuit_breaker: Mutex::new(CircuitBreaker {
consecutive_failures: 0,
open_until: None,
}),
keyring,
rho,
waiting_tasks: DashMap::new(),
} }
} }
@ -209,7 +190,7 @@ impl OmegaConnection {
// Close sender if connected // Close sender if connected
if let Some(sender) = self.sender.read().await.as_ref() { if let Some(sender) = self.sender.read().await.as_ref() {
sender.close().await; sender.close();
} }
*self.state.write().await = ConnectionState::Disconnected; *self.state.write().await = ConnectionState::Disconnected;
@ -276,7 +257,12 @@ impl OmegaConnection {
*self.state.write().await = ConnectionState::Connecting; *self.state.write().await = ConnectionState::Connecting;
let client_config = ClientConfig::new(format!("https://{}:{}", self.host, self.port)) let client_config = ClientConfig::new(format!("https://{}:{}", self.host, self.port))
.with_client_id(self.omikron_id) .with_client_id(
env::var("ID")
.unwrap_or("0".to_string())
.parse::<u64>()
.unwrap_or(0),
)
.with_policy( .with_policy(
Policy::default() Policy::default()
.with_send_mode(SendMode::SingleStreamPerMessage) .with_send_mode(SendMode::SingleStreamPerMessage)
@ -296,7 +282,7 @@ impl OmegaConnection {
let host_public_key = load_public_key_bundle("./omega.mpkb") let host_public_key = load_public_key_bundle("./omega.mpkb")
.map_err(|e| format!("Failed to load omega.mpkb: {}", e))?; .map_err(|e| format!("Failed to load omega.mpkb: {}", e))?;
let mut connection = Client::auth_connect(client_config, &self.keyring, &host_public_key) let mut connection = Client::auth_connect(client_config, &load_keyring(), &host_public_key)
.await .await
.map_err(|e| format!("Connection failed: {}", e))?; .map_err(|e| format!("Connection failed: {}", e))?;
@ -363,14 +349,13 @@ impl OmegaConnection {
let mut connected_iota_ids: Vec<DataValue> = Vec::new(); let mut connected_iota_ids: Vec<DataValue> = Vec::new();
let mut connected_user_ids: Vec<DataValue> = Vec::new(); let mut connected_user_ids: Vec<DataValue> = Vec::new();
let iota_ids = self.rho.iota_ids().await; let rho_connections_reader = RHO_CONNECTIONS.read().await;
let rho_connections = self.rho.connections().await;
for iota_id in iota_ids { for iota_id in rho_connections_reader.keys() {
connected_iota_ids.push(DataValue::SignedNumber(iota_id.into())); connected_iota_ids.push(DataValue::SignedNumber((*iota_id).into()));
} }
for rho in rho_connections { for rho in rho_connections_reader.values() {
for client_conn in rho.get_client_connections().await { for client_conn in rho.get_client_connections().await {
connected_user_ids.push(DataValue::SignedNumber( connected_user_ids.push(DataValue::SignedNumber(
client_conn.get_user_id().await.into(), client_conn.get_user_id().await.into(),
@ -378,12 +363,14 @@ impl OmegaConnection {
} }
} }
drop(rho_connections_reader);
let sync_msg = CommunicationValue::new(CommunicationType::SyncClientIotaStatus) let sync_msg = CommunicationValue::new(CommunicationType::SyncClientIotaStatus)
.add_typed_default(DataType::IotaIds, DataValue::Array(connected_iota_ids)) .add_typed_default(DataType::IotaIds, DataValue::Array(connected_iota_ids))
.add_typed_default(DataType::UserIds, DataValue::Array(connected_user_ids)) .add_typed_default(DataType::UserIds, DataValue::Array(connected_user_ids))
.add_typed_default( .add_typed_default(
DataType::RhoConnections, DataType::RhoConnections,
DataValue::SignedNumber(self.rho.connection_count().await as i128), DataValue::SignedNumber(connection_count().await as i128),
); );
self.send_message(&sync_msg).await; self.send_message(&sync_msg).await;
@ -416,7 +403,7 @@ impl OmegaConnection {
} }
let msg_id = cv.get_id(); let msg_id = cv.get_id();
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { if let Some((_, task)) = WAITING_TASKS.remove(&msg_id) {
if (task.task)(self.clone(), cv.clone()) { if (task.task)(self.clone(), cv.clone()) {
continue; continue;
} }
@ -424,20 +411,20 @@ impl OmegaConnection {
if cv.is_type(CommunicationType::IotaUserData) { if cv.is_type(CommunicationType::IotaUserData) {
if let DataValue::Array(users) = cv.get_data(DataType::UserIds) { if let DataValue::Array(users) = cv.get_data(DataType::UserIds) {
let mut user_ids = Vec::new(); let mut user_ids: Vec<u64> = Vec::new();
for value in users { for value in users {
if let DataValue::SignedNumber(user_id) = value { if let DataValue::SignedNumber(user_id) = value {
if let Ok(user_id) = i64::try_from(*user_id) { user_ids.push(user_id.clone() as u64);
user_ids.push(user_id);
}
} }
} }
let connections = crate::rho::rho_manager::RHO_CONNECTIONS.read().await;
if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() { if let Some(iota_id) = cv.get_data(DataType::IotaId).as_number() {
self.rho.replace_users_for_iota(iota_id as i64, user_ids).await; if let Some(rho) = connections.get(&(iota_id as i64)) {
rho.get_iota_connection().set_user_ids(user_ids).await;
}
} else { } else {
let iota_ids = self.rho.iota_ids().await; for rho in connections.values() {
for iota_id in iota_ids { rho.get_iota_connection().set_user_ids(user_ids.clone()).await;
self.rho.replace_users_for_iota(iota_id, user_ids.clone()).await;
} }
} }
} }
@ -529,7 +516,7 @@ impl OmegaConnection {
drop(sender_guard); drop(sender_guard);
// Trigger reconnection by closing the connection state // Trigger reconnection by closing the connection state
if let Some(sender) = self.sender.write().await.take() { if let Some(sender) = self.sender.write().await.take() {
sender.close().await; sender.close();
} }
return; return;
} }
@ -573,38 +560,13 @@ impl OmegaConnection {
&self, &self,
cv: &CommunicationValue, cv: &CommunicationValue,
timeout_duration: Option<Duration>, timeout_duration: Option<Duration>,
) -> Result<CommunicationValue, String> {
let _permit = self
.request_slots
.acquire()
.await
.map_err(|_| "Omega request queue is unavailable".to_string())?;
if !self.circuit_breaker.lock().await.allow_request() {
return Err("Omega circuit breaker is open".to_string());
}
let result = self.await_response_inner(cv, timeout_duration).await;
let mut breaker = self.circuit_breaker.lock().await;
if result.is_ok() {
breaker.record_success();
} else {
breaker.record_failure();
}
result
}
async fn await_response_inner(
&self,
cv: &CommunicationValue,
timeout_duration: Option<Duration>,
) -> Result<CommunicationValue, String> { ) -> Result<CommunicationValue, String> {
self.await_connection(timeout_duration).await?; self.await_connection(timeout_duration).await?;
let (tx, mut rx) = mpsc::channel(1); let (tx, mut rx) = mpsc::channel(1);
let msg_id = cv.get_id(); let msg_id = cv.get_id();
self.waiting_tasks.insert( WAITING_TASKS.insert(
msg_id, msg_id,
WaitingTask { WaitingTask {
task: Box::new(move |_, response_cv| { task: Box::new(move |_, response_cv| {
@ -626,7 +588,7 @@ impl OmegaConnection {
Ok(Some(response_cv)) => Ok(response_cv), Ok(Some(response_cv)) => Ok(response_cv),
Ok(_) => Err("Channel closed".to_string()), Ok(_) => Err("Channel closed".to_string()),
Err(_) => { Err(_) => {
self.waiting_tasks.remove(&msg_id); WAITING_TASKS.remove(&msg_id);
Err("Request timed out".to_string()) Err("Request timed out".to_string())
} }
} }
@ -642,13 +604,13 @@ impl OmegaConnection {
} }
#[allow(dead_code)] #[allow(dead_code)]
pub async fn close_iota(&self, iota_id: i64) { pub async fn close_iota(iota_id: i64) {
let cv = CommunicationValue::new(CommunicationType::IotaDisconnected) let cv = CommunicationValue::new(CommunicationType::IotaDisconnected)
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); .add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()));
self.send_message(&cv).await; OMEGA_CONNECTION.send_message(&cv).await;
} }
pub async fn client_changed(&self, _iota_id: i64, user_id: i64, state: UserStatus) { pub async fn client_changed(_iota_id: i64, user_id: i64, state: UserStatus) {
let msg_type = match state { let msg_type = match state {
UserStatus::iota_offline => CommunicationType::UserDisconnected, UserStatus::iota_offline => CommunicationType::UserDisconnected,
UserStatus::user_offline => CommunicationType::UserDisconnected, UserStatus::user_offline => CommunicationType::UserDisconnected,
@ -659,10 +621,10 @@ impl OmegaConnection {
let cv = CommunicationValue::new(msg_type) let cv = CommunicationValue::new(msg_type)
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into())) .add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
.add_typed_default(DataType::UserState, DataValue::Str(state.to_string())); .add_typed_default(DataType::UserState, DataValue::Str(state.to_string()));
self.send_message(&cv).await; OMEGA_CONNECTION.send_message(&cv).await;
} }
pub async fn user_states(&self, user_id: i64, user_ids: Vec<i64>) { pub async fn user_states(user_id: i64, user_ids: Vec<i64>) {
let user_ids = user_ids let user_ids = user_ids
.iter() .iter()
.map(|v| DataValue::SignedNumber((*v).into())) .map(|v| DataValue::SignedNumber((*v).into()))
@ -672,15 +634,13 @@ impl OmegaConnection {
.add_typed_default(DataType::UserIds, DataValue::Array(user_ids)); .add_typed_default(DataType::UserIds, DataValue::Array(user_ids));
let msg_id = cv.get_id(); let msg_id = cv.get_id();
let rho_manager = self.rho.clone(); WAITING_TASKS.insert(
self.waiting_tasks.insert(
msg_id, msg_id,
WaitingTask { WaitingTask {
task: Box::new( task: Box::new(
move |_: Arc<OmegaConnection>, response: CommunicationValue| { move |_: Arc<OmegaConnection>, response: CommunicationValue| {
let rho_manager = rho_manager.clone();
tokio::spawn(async move { tokio::spawn(async move {
let rho = rho_manager.get_for_user(user_id).await; let rho = rho_manager::get_rho_con_for_user(user_id).await;
if let Some(rho) = rho { if let Some(rho) = rho {
for client in rho.get_client_connections_for_user(user_id).await { for client in rho.get_client_connections_for_user(user_id).await {
client.send_message(&response).await; client.send_message(&response).await;
@ -694,6 +654,28 @@ impl OmegaConnection {
}, },
); );
self.send_message(&cv).await; OMEGA_CONNECTION.send_message(&cv).await;
} }
} }
// ============================================================================
// Global Instance
// ============================================================================
static OMEGA_CONNECTION: Lazy<Arc<OmegaConnection>> = 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<OmegaConnection> {
OMEGA_CONNECTION.clone()
}

View file

@ -1,7 +1,7 @@
use crate::anonymous_clients::anonymous_manager; use crate::anonymous_clients::anonymous_manager;
use crate::app_state::AppState; use crate::omega::omega_connection::get_omega_connection;
use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender}; use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender};
use crate::rho::rho_connection::RhoConnection; use crate::rho::{rho_connection::RhoConnection, rho_manager};
use crate::util::logger::PrintType; use crate::util::logger::PrintType;
use crate::{log_cv_in, log_cv_out, log_err, log_in, log_out}; use crate::{log_cv_in, log_cv_out, log_err, log_in, log_out};
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
@ -11,7 +11,6 @@ use tokio::sync::RwLock;
use uuid::Uuid; use uuid::Uuid;
pub struct AppConnection { pub struct AppConnection {
pub state: Arc<AppState>,
pub user_id: u64, pub user_id: u64,
pub app_identifier: String, pub app_identifier: String,
pub app_session: Uuid, pub app_session: Uuid,
@ -28,7 +27,6 @@ pub struct AppConnection {
impl AppConnection { impl AppConnection {
pub async fn from_general(general: Arc<GeneralConnection>, user_id: u64) -> Arc<Self> { pub async fn from_general(general: Arc<GeneralConnection>, user_id: u64) -> Arc<Self> {
Arc::new(Self { Arc::new(Self {
state: general.state.clone(),
ping: Arc::new(RwLock::new(0)), ping: Arc::new(RwLock::new(0)),
pub_key: Arc::new(RwLock::new(None)), pub_key: Arc::new(RwLock::new(None)),
rho_connection: general.rho_connection.clone(), rho_connection: general.rho_connection.clone(),
@ -156,10 +154,7 @@ impl AppConnection {
async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) { async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) {
let app_for_closure = self.clone(); let app_for_closure = self.clone();
tokio::spawn(async move { tokio::spawn(async move {
let response_cv = self let response_cv = get_omega_connection()
.state
.omega
.clone()
.await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20)))
.await; .await;
if let Ok(response_cv) = response_cv { if let Ok(response_cv) = response_cv {
@ -174,7 +169,7 @@ impl AppConnection {
if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) {
let current = SystemTime::now() let current = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap_or_default() .unwrap()
.as_millis(); .as_millis();
let mut ping_guard = self.ping.write().await; let mut ping_guard = self.ping.write().await;
*ping_guard = (current as i128 - *last_ping) as i64; *ping_guard = (current as i128 - *last_ping) as i64;
@ -232,10 +227,7 @@ impl AppConnection {
return; return;
} }
let load_uuid_response = self let load_uuid_response = get_omega_connection()
.state
.omega
.clone()
.await_response( .await_response(
&CommunicationValue::new(CommunicationType::GetUserData) &CommunicationValue::new(CommunicationType::GetUserData)
.with_id(cv.clone().get_id()) .with_id(cv.clone().get_id())
@ -336,7 +328,7 @@ impl AppConnection {
/// Handle connection close /// Handle connection close
pub async fn handle_close(&self) { pub async fn handle_close(&self) {
let user_id = self.get_user_id().await; let user_id = self.get_user_id().await;
if let Some(rho_conn) = self.state.rho.get_for_user(user_id as i64).await { if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id as i64).await {
rho_conn.close_app_connection(Arc::new(self.clone())).await; rho_conn.close_app_connection(Arc::new(self.clone())).await;
} }
} }
@ -346,7 +338,6 @@ impl AppConnection {
impl Clone for AppConnection { impl Clone for AppConnection {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
state: self.state.clone(),
sender: Arc::clone(&self.sender), sender: Arc::clone(&self.sender),
receiver: Arc::clone(&self.receiver), receiver: Arc::clone(&self.receiver),
user_id: self.user_id, user_id: self.user_id,

View file

@ -1,10 +1,10 @@
use crate::anonymous_clients::anonymous_manager; use crate::anonymous_clients::anonymous_manager;
use crate::app_state::AppState; use crate::calls::{call_group::call_invite_secret_from_cv, call_manager, call_util};
use crate::calls::call_group::call_invite_secret_from_cv; use crate::omega::omega_connection::get_omega_connection;
use crate::data::user::UserStatus;
use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender}; use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender};
use crate::rho::rho_connection::RhoConnection; use crate::rho::{rho_connection::RhoConnection, rho_manager};
use crate::util::logger::PrintType; 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 crate::{log_cv_in, log_cv_out, log_err, log_in, log_out};
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
use std::str::FromStr; use std::str::FromStr;
@ -15,7 +15,6 @@ use trust_dns_resolver::TokioAsyncResolver;
use uuid::Uuid; use uuid::Uuid;
pub struct ClientConnection { pub struct ClientConnection {
pub state: Arc<AppState>,
pub user_id: u64, pub user_id: u64,
pub session_id: u64, pub session_id: u64,
pub client_version: String, pub client_version: String,
@ -27,13 +26,11 @@ pub struct ClientConnection {
pub rho_connection: Arc<RwLock<Option<Arc<RhoConnection>>>>, pub rho_connection: Arc<RwLock<Option<Arc<RhoConnection>>>>,
pub interested_users: Arc<RwLock<Vec<i64>>>, pub interested_users: Arc<RwLock<Vec<i64>>>,
is_open: Arc<RwLock<bool>>, is_open: Arc<RwLock<bool>>,
message_slots: Arc<tokio::sync::Semaphore>,
} }
impl ClientConnection { impl ClientConnection {
pub async fn from_general(general: Arc<GeneralConnection>, user_id: u64) -> Arc<Self> { pub async fn from_general(general: Arc<GeneralConnection>, user_id: u64) -> Arc<Self> {
Arc::new(Self { Arc::new(Self {
state: general.state.clone(),
ping: Arc::new(RwLock::new(0)), ping: Arc::new(RwLock::new(0)),
pub_key: Arc::new(RwLock::new(None)), pub_key: Arc::new(RwLock::new(None)),
rho_connection: general.rho_connection.clone(), rho_connection: general.rho_connection.clone(),
@ -44,7 +41,6 @@ impl ClientConnection {
user_id: user_id, user_id: user_id,
session_id: general.session_id.read().await.clone(), session_id: general.session_id.read().await.clone(),
client_version: general.client_version.read().await.clone(), client_version: general.client_version.read().await.clone(),
message_slots: Arc::new(tokio::sync::Semaphore::new(32)),
}) })
} }
pub fn start(self: Arc<Self>) { pub fn start(self: Arc<Self>) {
@ -100,11 +96,7 @@ impl ClientConnection {
/// Handle incoming message from client /// Handle incoming message from client
pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) { pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) {
let Ok(permit) = self.message_slots.clone().acquire_owned().await else {
return;
};
tokio::spawn(async move { tokio::spawn(async move {
let _permit = permit;
if cv.is_type(CommunicationType::Ping) { if cv.is_type(CommunicationType::Ping) {
self.handle_ping(cv).await; self.handle_ping(cv).await;
return; return;
@ -278,15 +270,14 @@ impl ClientConnection {
} }
async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) { async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) {
let client_for_closure = self.clone(); let client_for_closure = self.clone();
let response_cv = self tokio::spawn(async move {
.state let response_cv = get_omega_connection()
.omega
.clone()
.await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20))) .await_response(&cv.with_sender(self.user_id), Some(Duration::from_secs(20)))
.await; .await;
if let Ok(response_cv) = response_cv { if let Ok(response_cv) = response_cv {
client_for_closure.send_message(&response_cv).await; client_for_closure.send_message(&response_cv).await;
} }
});
} }
/// Handle ping message /// Handle ping message
@ -295,7 +286,7 @@ impl ClientConnection {
if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) { if let DataValue::SignedNumber(last_ping) = cv.get_data(DataType::LastPing) {
let current = SystemTime::now() let current = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap_or_default() .unwrap()
.as_millis(); .as_millis();
let mut ping_guard = self.ping.write().await; let mut ping_guard = self.ping.write().await;
*ping_guard = (current as i128 - *last_ping) as i64; *ping_guard = (current as i128 - *last_ping) as i64;
@ -325,9 +316,7 @@ impl ClientConnection {
if let DataValue::Str(status_str) = cv.get_data(DataType::UserState) { if let DataValue::Str(status_str) = cv.get_data(DataType::UserState) {
let user_status = UserStatus::from_str(&status_str).unwrap_or(UserStatus::user_online); let user_status = UserStatus::from_str(&status_str).unwrap_or(UserStatus::user_online);
if let Some(rho_conn) = self.get_rho_connection().await { if let Some(rho_conn) = self.get_rho_connection().await {
self.state OmegaConnection::client_changed(
.omega
.client_changed(
rho_conn.get_iota_id().await as i64, rho_conn.get_iota_id().await as i64,
user_id as i64, user_id as i64,
user_status, user_status,
@ -370,10 +359,8 @@ impl ClientConnection {
return; return;
} }
}; };
let invited = self let invited =
.state call_manager::add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
.call_manager
.add_invite(call_id, self.user_id, receiver_id as u64, secret.clone())
.await; .await;
if !invited { if !invited {
self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId) self.send_error_response(cv.get_id(), CommunicationType::ErrorInvalidCallId)
@ -381,18 +368,14 @@ impl ClientConnection {
return; return;
} }
if !self if !call_manager::should_forward_invite(self.user_id, receiver_id as u64) {
.state
.call_manager
.should_forward_invite(self.user_id, receiver_id as u64)
{
let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id()); let response = CommunicationValue::new(CommunicationType::Success).with_id(cv.get_id());
self.send_message(&response).await; self.send_message(&response).await;
return; return;
} }
// Find target RhoConnection // Find target RhoConnection
let target_rho = match self.state.rho.get_for_user(receiver_id as i64).await { let target_rho = match rho_manager::get_rho_con_for_user(receiver_id as i64).await {
Some(rho) => rho, Some(rho) => rho,
_ => { _ => {
// Get sender user ID // Get sender user ID
@ -411,7 +394,7 @@ impl ClientConnection {
DataValue::Str("call_invite".to_string()), DataValue::Str("call_invite".to_string()),
); );
let omega_conn = self.state.omega.clone(); let omega_conn = get_omega_connection();
// Send fire-and-forget, don't await to avoid blocking // Send fire-and-forget, don't await to avoid blocking
tokio::spawn(async move { tokio::spawn(async move {
let _ = omega_conn.send_message(&push_cv).await; let _ = omega_conn.send_message(&push_cv).await;
@ -472,26 +455,18 @@ impl ClientConnection {
} }
}; };
match self if let Some(token) = call_manager::get_call_token(user_id, call_id).await {
.state
.call_manager
.get_call_token(user_id, call_id)
.await
{
Ok(token) => {
let response = CommunicationValue::new(CommunicationType::CallToken) let response = CommunicationValue::new(CommunicationType::CallToken)
.with_id(cv.get_id()) .with_id(cv.get_id())
.with_receiver(user_id as u64) .with_receiver(user_id as u64)
.add_typed_default(DataType::CallToken, DataValue::Str(token)); .add_typed_default(DataType::CallToken, DataValue::Str(token));
self.send_message(&response).await; self.send_message(&response).await;
} } else {
Err(error) => {
log::warn!("Unable to create call token for {}: {}", call_id, error);
let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId) let error_cv = CommunicationValue::new(CommunicationType::ErrorNoCallId)
.with_id(cv.get_id()) .with_id(cv.get_id())
.add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string())); .add_typed_default(DataType::CallId, DataValue::Str(call_id.to_string()));
self.send_message(&error_cv).await; self.send_message(&error_cv).await;
} return;
} }
} }
async fn handle_get_call_data(self: Arc<Self>, cv: CommunicationValue) { async fn handle_get_call_data(self: Arc<Self>, cv: CommunicationValue) {
@ -513,7 +488,7 @@ impl ClientConnection {
} }
}; };
if let Some(call) = self.state.call_manager.get_call(call_id).await { if let Some(call) = call_manager::get_call(call_id).await {
if let Some(_) = call.get_caller(user_id).await { if let Some(_) = call.get_caller(user_id).await {
let mut user_ids: Vec<DataValue> = Vec::new(); let mut user_ids: Vec<DataValue> = Vec::new();
let members = call.members.read().await.clone(); let members = call.members.read().await.clone();
@ -560,7 +535,7 @@ impl ClientConnection {
.as_signed_number() .as_signed_number()
.unwrap_or(0); .unwrap_or(0);
let Some(call) = self.state.call_manager.get_call(call_id).await else { let Some(call) = call_manager::get_call(call_id).await else {
self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound) self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound)
.await; .await;
return; return;
@ -572,11 +547,7 @@ impl ClientConnection {
return; return;
}; };
if caller.has_admin() { if caller.has_admin() {
let _ = self let _ = call_util::remove_participant(call_id, user_id as u64).await;
.state
.livekit
.remove_participant(call_id, user_id as u64)
.await;
if let Some(target) = call.get_caller(user_id as u64).await { if let Some(target) = call.get_caller(user_id as u64).await {
target.set_timeout(untill as i64).await; target.set_timeout(untill as i64).await;
} }
@ -594,7 +565,7 @@ impl ClientConnection {
.as_signed_number() .as_signed_number()
.unwrap_or(0); .unwrap_or(0);
let Some(call) = self.state.call_manager.get_call(call_id).await else { let Some(call) = call_manager::get_call(call_id).await else {
self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound) self.send_error_response(cv.get_id(), CommunicationType::ErrorNotFound)
.await; .await;
return; return;
@ -617,18 +588,13 @@ impl ClientConnection {
}; };
let enable = cv.get_data(DataType::Enabled).as_bool().unwrap_or(true); let enable = cv.get_data(DataType::Enabled).as_bool().unwrap_or(true);
let call = self.state.call_manager.get_call(call_id).await; let call = call_manager::get_call(call_id).await;
let mut short_link = None; let mut short_link = None;
if let Some(call) = call { if let Some(call) = call {
if let Some(caller) = call.get_caller(self.get_user_id().await).await { if let Some(caller) = call.get_caller(self.get_user_id().await).await {
if caller.has_admin() { if caller.has_admin() {
call.set_anonymous_joining( call.set_anonymous_joining(enable).await;
enable,
&self.state.omega,
self.state.config.omikron_id,
)
.await;
} }
} }
short_link = call.get_short_link().await; short_link = call.get_short_link().await;
@ -660,22 +626,11 @@ impl ClientConnection {
match resolver.txt_lookup(path).await { match resolver.txt_lookup(path).await {
Ok(txt_lookup) => { Ok(txt_lookup) => {
if let Some(txt_record) = txt_lookup.iter().next() { if let Some(txt_record) = txt_lookup.iter().next() {
let record_bytes: Vec<u8> = txt_record let record_text: String = txt_record
.txt_data() .txt_data()
.iter() .iter()
.flat_map(|chunk| chunk.iter().copied()) .map(|b| String::from_utf8_lossy(b))
.collect(); .collect();
let record_text = match String::from_utf8(record_bytes) {
Ok(text) => text,
Err(_) => {
self.send_error_response(
cv.get_id(),
CommunicationType::ErrorInvalidData,
)
.await;
return;
}
};
let response = CommunicationValue::new(CommunicationType::LoadTxtRecord) let response = CommunicationValue::new(CommunicationType::LoadTxtRecord)
.with_id(cv.get_id()) .with_id(cv.get_id())
@ -746,10 +701,7 @@ impl ClientConnection {
return; return;
} }
let load_uuid_response = self let load_uuid_response = get_omega_connection()
.state
.omega
.clone()
.await_response( .await_response(
&CommunicationValue::new(CommunicationType::GetUserData) &CommunicationValue::new(CommunicationType::GetUserData)
.with_id(cv.clone().get_id()) .with_id(cv.clone().get_id())
@ -878,7 +830,7 @@ impl ClientConnection {
/// Handle connection close /// Handle connection close
pub async fn handle_close(&self) { pub async fn handle_close(&self) {
let user_id = self.get_user_id().await; let user_id = self.get_user_id().await;
if let Some(rho_conn) = self.state.rho.get_for_user(user_id as i64).await { if let Some(rho_conn) = rho_manager::get_rho_con_for_user(user_id as i64).await {
rho_conn rho_conn
.close_client_connection(Arc::new(self.clone())) .close_client_connection(Arc::new(self.clone()))
.await; .await;
@ -890,7 +842,6 @@ impl ClientConnection {
impl Clone for ClientConnection { impl Clone for ClientConnection {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
state: self.state.clone(),
sender: Arc::clone(&self.sender), sender: Arc::clone(&self.sender),
receiver: Arc::clone(&self.receiver), receiver: Arc::clone(&self.receiver),
user_id: self.user_id, user_id: self.user_id,
@ -901,7 +852,6 @@ impl Clone for ClientConnection {
rho_connection: Arc::clone(&self.rho_connection), rho_connection: Arc::clone(&self.rho_connection),
interested_users: Arc::clone(&self.interested_users), interested_users: Arc::clone(&self.interested_users),
is_open: Arc::clone(&self.is_open), is_open: Arc::clone(&self.is_open),
message_slots: Arc::clone(&self.message_slots),
} }
} }
} }

View file

@ -1,18 +1,19 @@
use std::{sync::Arc, time::Duration}; use std::{collections::BTreeMap, collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
anonymous_clients::anonymous_client_connection::AnonymousClientConnection, anonymous_clients::anonymous_client_connection::AnonymousClientConnection,
app_state::AppState, calls::call_manager,
log_cv_out, log_err, log_in, log_out, log_cv_out, log_err, log_in, log_out,
omega::omega_connection::get_omega_connection,
rho::{ rho::{
app_connection::AppConnection, client_connection::ClientConnection, app_connection::AppConnection, client_connection::ClientConnection,
iota_connection::IotaConnection, rho_connection::RhoConnection, iota_connection::IotaConnection, rho_connection::RhoConnection, rho_manager,
}, },
util::logger::PrintType, util::logger::PrintType,
}; };
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataTypeId, DataValue};
use mtp::host::AuthState; use mtp::host::AuthState;
use mtp::webserver::{WebMTPConnection, WebMtpReceiver, WebMtpSender}; use mtp::webserver::{WebMTPConnection, WebMtpReceiver, WebMtpSender};
@ -37,7 +38,6 @@ pub enum ConnectionKind {
} }
pub struct GeneralConnection { pub struct GeneralConnection {
pub state: Arc<AppState>,
pub sender: Arc<MtpSender>, pub sender: Arc<MtpSender>,
pub receiver: Arc<MtpReceiver>, pub receiver: Arc<MtpReceiver>,
@ -61,7 +61,7 @@ impl GeneralConnection {
* to a known, appropriately-authenticated kind is rejected here instead * to a known, appropriately-authenticated kind is rejected here instead
* of being handed off to a connection handler. * of being handed off to a connection handler.
*/ */
pub fn new(conn: WebMTPConnection, state: Arc<AppState>) -> Option<Arc<Self>> { pub fn new(conn: WebMTPConnection) -> Option<Arc<Self>> {
let kind = match (conn.description.as_deref(), &conn.auth_state) { let kind = match (conn.description.as_deref(), &conn.auth_state) {
(Some("iota"), AuthState::Authenticated) => ConnectionKind::Iota, (Some("iota"), AuthState::Authenticated) => ConnectionKind::Iota,
(Some("client"), AuthState::Authenticated) => ConnectionKind::Client, (Some("client"), AuthState::Authenticated) => ConnectionKind::Client,
@ -70,7 +70,6 @@ impl GeneralConnection {
}; };
Some(Arc::new(Self { Some(Arc::new(Self {
state,
sender: Arc::new(conn.sender), sender: Arc::new(conn.sender),
receiver: Arc::new(conn.receiver), receiver: Arc::new(conn.receiver),
connection_kind: kind, connection_kind: kind,
@ -90,29 +89,199 @@ impl GeneralConnection {
} }
async fn migrate(self: &Arc<Self>) -> bool { async fn migrate(self: &Arc<Self>) -> bool {
match self.connection_kind {
ConnectionKind::Client => self.migrate_client().await,
ConnectionKind::Iota => self.migrate_iota().await,
ConnectionKind::AnonymousClient => self.migrate_anonymous_client().await,
ConnectionKind::Phi => self.migrate_phi().await,
}
true
}
async fn migrate_client(self: &Arc<Self>) {
let id = self.id; let id = self.id;
match self.connection_kind {
ConnectionKind::Client => {
let notify = CommunicationValue::new(CommunicationType::UserConnected)
.add_typed_default(
DataType::UserId,
DataValue::SignedNumber((id as i64).into()),
);
get_omega_connection().send_message(&notify).await;
let user_id = id as i64; let user_id = id as i64;
self.notify_user_connected(user_id).await;
let client = ClientConnection::from_general(self.clone(), id).await; let client = ClientConnection::from_general(self.clone(), id).await;
let rho = self.find_user_rho(user_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::GetUserData)
.add_typed_default(
DataType::UserId,
DataValue::SignedNumber(user_id.into()),
);
if let Ok(user_data_cv) = get_omega_connection()
.await_response(&get_user_msg, Some(Duration::from_secs(20)))
.await
{
if let DataValue::SignedNumber(iota_id) =
user_data_cv.get_data(DataType::IotaId)
{
if let Some(bound_rho) =
rho_manager::bind_user_to_iota(user_id, *iota_id as i64).await
{
bound_rho.bind_user_id(user_id).await;
rho = Some(bound_rho);
}
}
}
}
*self.rho_connection.write().await = rho.clone(); *self.rho_connection.write().await = rho.clone();
if let Some(rho_conn) = rho { if let Some(rho_conn) = rho {
if let Some(response) = self.request_initial_client_state(&rho_conn, user_id).await { let session_id = *self.session_id.read().await as i64;
let response = self.add_call_state(response, user_id).await; let iota_msg = CommunicationValue::new(CommunicationType::ClientConnected)
log_cv_out!(response); .add_typed_default(
let _ = self.sender.send(&response).await; DataType::UserId,
DataValue::SignedNumber(user_id.into()),
)
.add_typed_default(
DataType::SessionId,
DataValue::SignedNumber(session_id.into()),
);
if let Ok(resp) = rho_conn
.get_iota_connection()
.clone()
.await_response(&iota_msg, Some(Duration::from_secs(20)))
.await
{
/*
* mtp's handshake already confirmed this login; this
* message is an unsolicited push carrying the initial
* contacts/calls state, not a reply to a client-side
* request id (the old raw hello frame this used to
* reply to no longer reaches application code).
*/
let mut ident_resp =
CommunicationValue::new(CommunicationType::IdentificationResponse);
let tm = mtp::codec::TypeMap::latest();
for (k, v) in resp.iter_typed_data() {
let value_to_add = if k == Some(DataType::Contacts) {
if let Some(contacts) = v.as_array() {
let call_groups =
call_manager::get_call_groups(user_id as u64).await;
let mut invites: HashMap<i64, Vec<DataValue>> = HashMap::new();
let mut global_calls: Vec<DataValue> = Vec::new();
for call in call_groups {
let members = call.members.read().await;
let call_self =
members.iter().find(|m| m.user_id == user_id as u64);
if let Some(call_self) = call_self {
let timeout = *call_self.timeout.read().await;
let admin = call_self.has_admin();
// List of all members in the call
let member_ids: Vec<DataValue> = members
.iter()
.map(|m| DataValue::SignedNumber(m.user_id.into()))
.collect();
let mut base_call_map: BTreeMap<DataTypeId, DataValue> =
BTreeMap::new();
base_call_map.insert(
DataType::CallId.to_id(&tm),
DataValue::Str(call.call_id.to_string()),
);
base_call_map.insert(
DataType::CallMembers.to_id(&tm),
DataValue::Array(member_ids),
);
if timeout > 0 {
base_call_map.insert(
DataType::Timeout.to_id(&tm),
DataValue::SignedNumber(
(timeout as i64).into(),
),
);
}
if admin {
base_call_map.insert(
DataType::HasAdmin.to_id(&tm),
DataValue::Bool(true),
);
}
if let Some(secret) =
call.get_secret_for_user(user_id as u64).await
{
base_call_map.insert(
DataType::CallSecret.to_id(&tm),
secret.to_data_value(),
);
}
// Add to global calls with only this user's recipient-specific secret.
global_calls.push(DataValue::container_from_map(
&base_call_map,
));
for member in members.iter() {
let member_id = member.user_id;
if member_id == user_id as u64 {
continue;
}
let contact_call_map = base_call_map.clone();
invites
.entry(member_id as i64)
.or_insert_with(Vec::new)
.push(DataValue::container_from_map(
&contact_call_map,
));
}
}
}
let mut new_contacts: Vec<DataValue> = Vec::new();
for contact in contacts {
if let Some(mut contact_map) = contact.as_map() {
if let Some(DataValue::SignedNumber(id)) =
contact_map.get(&DataType::UserId.to_id(&tm))
{
if let Some(call_list) = invites.get(&(*id as i64))
{
contact_map.insert(
DataType::Calls.to_id(&tm),
DataValue::Array(call_list.clone()),
);
}
}
new_contacts
.push(DataValue::container_from_map(&contact_map));
} else {
new_contacts.push(contact.clone());
}
}
ident_resp = ident_resp.add_typed_default(
DataType::Calls,
DataValue::Array(global_calls),
);
DataValue::Array(new_contacts)
} else {
v.clone()
}
} else {
v.clone()
};
if let Some(dt) = k {
ident_resp = ident_resp.add_typed_default(dt, value_to_add);
}
}
log_cv_out!(ident_resp);
let _ = self.sender.send(&ident_resp).await;
rho_conn.bind_user_id(user_id).await; rho_conn.bind_user_id(user_id).await;
rho_conn.add_client_connection(client.clone()).await; rho_conn.add_client_connection(client.clone()).await;
} }
@ -127,144 +296,86 @@ impl GeneralConnection {
client.start(); client.start();
} }
ConnectionKind::Iota => {
async fn notify_user_connected(&self, user_id: i64) {
let notify = CommunicationValue::new(CommunicationType::UserConnected)
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()));
self.state.omega.clone().send_message(&notify).await;
}
async fn find_user_rho(&self, user_id: i64) -> Option<Arc<RhoConnection>> {
if let Some(rho) = self.state.rho.get_for_user(user_id).await {
return Some(rho);
}
let get_user_msg = CommunicationValue::new(CommunicationType::GetUserData)
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()));
let Ok(user_data_cv) = self
.state
.omega
.clone()
.await_response(&get_user_msg, Some(Duration::from_secs(20)))
.await
else {
return None;
};
let DataValue::SignedNumber(iota_id) = user_data_cv.get_data(DataType::IotaId) else {
return None;
};
let rho = self
.state
.rho
.bind_user_to_iota(user_id, *iota_id as i64)
.await?;
rho.bind_user_id(user_id).await;
Some(rho)
}
async fn request_initial_client_state(
&self,
rho: &Arc<RhoConnection>,
user_id: i64,
) -> Option<CommunicationValue> {
let session_id = *self.session_id.read().await as i64;
let request = CommunicationValue::new(CommunicationType::ClientConnected)
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
.add_typed_default(
DataType::SessionId,
DataValue::SignedNumber(session_id.into()),
);
rho.get_iota_connection()
.clone()
.await_response(&request, Some(Duration::from_secs(20)))
.await
.ok()
}
async fn add_call_state(
&self,
response: CommunicationValue,
user_id: i64,
) -> CommunicationValue {
let mut output = CommunicationValue::new(CommunicationType::IdentificationResponse);
for (key, value) in response.iter_typed_data() {
if key == Some(DataType::Contacts) {
if let Some(contacts) = value.as_array() {
let (contacts, global_calls) = self
.state
.call_state_aggregator
.augment_contacts(user_id as u64, contacts.clone())
.await;
output =
output.add_typed_default(DataType::Contacts, DataValue::Array(contacts));
output =
output.add_typed_default(DataType::Calls, DataValue::Array(global_calls));
continue;
}
}
if let Some(data_type) = key {
output = output.add_typed_default(data_type, value.clone());
}
}
output
}
async fn migrate_iota(self: &Arc<Self>) {
let id = self.id;
log_in!(id as i64, PrintType::Iota, "Iota {} connected", id); log_in!(id as i64, PrintType::Iota, "Iota {} connected", id);
let notify = CommunicationValue::new(CommunicationType::IotaConnected) let notify = CommunicationValue::new(CommunicationType::IotaConnected)
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into())); .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into()));
self.state.omega.clone().send_message(&notify).await; get_omega_connection().send_message(&notify).await;
let iota = IotaConnection::from_general(self.clone(), id).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;
self.state.rho.add(rho).await;
self.load_iota_users(&iota, id).await;
iota.start();
}
async fn load_iota_users(&self, iota: &Arc<IotaConnection>, iota_id: u64) { let rho = Arc::new(RhoConnection::new(iota.clone(), Vec::new()).await);
let request = CommunicationValue::new(CommunicationType::GetIotaData)
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into())); iota.set_rho_connection(rho.clone()).await;
let Ok(response) = self
.state rho_manager::add_rho(rho).await;
.omega
.clone() let get_iota_msg = CommunicationValue::new(CommunicationType::GetIotaData)
.await_response(&request, Some(Duration::from_secs(20))) .add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into()));
if let Ok(iota_data_cv) = get_omega_connection()
.await_response(&get_iota_msg, Some(Duration::from_secs(20)))
.await .await
else { {
return; if let DataValue::Array(users) = iota_data_cv.get_data(DataType::UserIds) {
}; let mut user_ids: Vec<u64> = Vec::new();
let DataValue::Array(users) = response.get_data(DataType::UserIds) else { for value in users {
return; if let DataValue::SignedNumber(user_id) = value {
}; user_ids.push(*user_id as u64);
let user_ids = users }
.iter() }
.filter_map(|value| match value {
DataValue::SignedNumber(id) => Some(*id as u64),
_ => None,
})
.collect();
iota.set_user_ids(user_ids).await; iota.set_user_ids(user_ids).await;
} }
async fn migrate_anonymous_client(self: &Arc<Self>) {
let client = AnonymousClientConnection::from_general(self.clone(), self.id).await;
client.start();
} }
async fn migrate_phi(self: &Arc<Self>) { // mtp's handshake already confirmed this login/registration to the Iota.
let user_id = self.id as i64; iota.clone().start();
let rho = self.find_user_rho(user_id).await; }
ConnectionKind::AnonymousClient => {
let client = AnonymousClientConnection::from_general(self.clone(), id).await;
client.start();
}
ConnectionKind::Phi => {
let user_id = id as i64;
let mut rho = rho_manager::get_rho_con_for_user(user_id).await;
if rho.is_none() {
let get_user_msg = CommunicationValue::new(CommunicationType::GetUserData)
.add_typed_default(
DataType::UserId,
DataValue::SignedNumber(user_id.into()),
);
if let Ok(user_data_cv) = get_omega_connection()
.await_response(&get_user_msg, Some(Duration::from_secs(20)))
.await
{
if let DataValue::SignedNumber(iota_id) =
user_data_cv.get_data(DataType::IotaId)
{
if let Some(bound_rho) =
rho_manager::bind_user_to_iota(user_id, *iota_id as i64).await
{
bound_rho.bind_user_id(user_id).await;
rho = Some(bound_rho);
}
}
}
}
*self.rho_connection.write().await = rho.clone(); *self.rho_connection.write().await = rho.clone();
let app_conn = AppConnection::from_general(self.clone(), self.id).await;
let app_conn = AppConnection::from_general(self.clone(), id).await;
if let Some(rho_conn) = rho { if let Some(rho_conn) = rho {
rho_conn.bind_user_id(user_id).await; rho_conn.bind_user_id(user_id).await;
rho_conn.add_app_connection(app_conn.clone()).await; rho_conn.add_app_connection(app_conn.clone()).await;
} }
app_conn.start(); app_conn.start();
} }
} }
true
}
}

View file

@ -1,12 +1,12 @@
use crate::app_state::AppState;
use crate::calls::call_group::CallGroup; use crate::calls::call_group::CallGroup;
use crate::calls::call_manager;
use crate::log_cv_in; use crate::log_cv_in;
use crate::log_cv_out; use crate::log_cv_out;
use crate::log_err; use crate::log_err;
use crate::log_in; use crate::log_in;
use crate::log_out; use crate::log_out;
use crate::omega::omega_connection::get_omega_connection;
use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender}; use crate::rho::connection::{GeneralConnection, MtpReceiver, MtpSender};
use crate::util::data_type_id;
use crate::util::logger::PrintType; use crate::util::logger::PrintType;
use dashmap::DashMap; use dashmap::DashMap;
use mtp::codec::CommunicationType; use mtp::codec::CommunicationType;
@ -17,15 +17,18 @@ use mtp::codec::DataValue;
use mtp::codec::TypeMap; use mtp::codec::TypeMap;
use mtp::crypto::KemPublicKey; use mtp::crypto::KemPublicKey;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::{collections::HashMap, sync::Arc, time::Duration}; use std::{collections::HashMap, sync::Arc, sync::LazyLock, time::Duration};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use tokio::sync::mpsc; use tokio::sync::mpsc;
use super::rho_connection::RhoConnection; use super::{rho_connection::RhoConnection, rho_manager};
use crate::omega::omega_connection::OmegaConnection;
static PENDING_CHAT_SECRETS: LazyLock<DashMap<u64, Vec<CommunicationValue>>> =
LazyLock::new(DashMap::new);
#[allow(dead_code)] #[allow(dead_code)]
pub struct IotaConnection { pub struct IotaConnection {
pub state: Arc<AppState>,
pub iota_id: u64, pub iota_id: u64,
pub client_version: String, pub client_version: String,
pub sender: Arc<MtpSender>, pub sender: Arc<MtpSender>,
@ -36,14 +39,11 @@ pub struct IotaConnection {
pub waiting_tasks: pub waiting_tasks:
DashMap<u32, Box<dyn Fn(Arc<IotaConnection>, CommunicationValue) -> bool + Send + Sync>>, DashMap<u32, Box<dyn Fn(Arc<IotaConnection>, CommunicationValue) -> bool + Send + Sync>>,
pub rho_connection: Arc<RwLock<Option<Arc<RhoConnection>>>>, pub rho_connection: Arc<RwLock<Option<Arc<RhoConnection>>>>,
pending_chat_secrets: DashMap<u64, Vec<CommunicationValue>>,
message_slots: Arc<tokio::sync::Semaphore>,
} }
impl IotaConnection { impl IotaConnection {
pub async fn from_general(general: Arc<GeneralConnection>, iota_id: u64) -> Arc<Self> { pub async fn from_general(general: Arc<GeneralConnection>, iota_id: u64) -> Arc<Self> {
Arc::new(Self { Arc::new(Self {
state: general.state.clone(),
ping: Arc::new(RwLock::new(0)), ping: Arc::new(RwLock::new(0)),
pub_key: Arc::new(RwLock::new(None)), pub_key: Arc::new(RwLock::new(None)),
rho_connection: general.rho_connection.clone(), rho_connection: general.rho_connection.clone(),
@ -53,8 +53,6 @@ impl IotaConnection {
iota_id: iota_id, iota_id: iota_id,
client_version: general.client_version.read().await.clone(), client_version: general.client_version.read().await.clone(),
waiting_tasks: DashMap::new(), waiting_tasks: DashMap::new(),
pending_chat_secrets: DashMap::new(),
message_slots: Arc::new(tokio::sync::Semaphore::new(32)),
}) })
} }
pub fn start(self: Arc<Self>) { pub fn start(self: Arc<Self>) {
@ -130,7 +128,7 @@ impl IotaConnection {
} }
async fn flush_pending_chat_secrets(&self, user_id: u64) { async fn flush_pending_chat_secrets(&self, user_id: u64) {
let Some((_, messages)) = self.pending_chat_secrets.remove(&user_id) else { let Some((_, messages)) = PENDING_CHAT_SECRETS.remove(&user_id) else {
return; return;
}; };
@ -139,13 +137,13 @@ impl IotaConnection {
} }
} }
fn store_pending_chat_secret(&self, cv: CommunicationValue) { fn store_pending_chat_secret(cv: CommunicationValue) {
let receiver_id = cv.get_receiver(); let receiver_id = cv.get_receiver();
if receiver_id == 0 || !cv.is_type(CommunicationType::SetChatSecret) { if receiver_id == 0 || !cv.is_type(CommunicationType::SetChatSecret) {
return; return;
} }
self.pending_chat_secrets PENDING_CHAT_SECRETS
.entry(receiver_id) .entry(receiver_id)
.or_default() .or_default()
.push(cv); .push(cv);
@ -189,10 +187,6 @@ impl IotaConnection {
/// Handle incoming message from Iota /// Handle incoming message from Iota
pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) { pub async fn handle_message(self: Arc<Self>, cv: CommunicationValue) {
let Ok(permit) = self.message_slots.clone().acquire_owned().await else {
return;
};
let _permit = permit;
let msg_id = cv.get_id(); let msg_id = cv.get_id();
if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) { if let Some((_, task)) = self.waiting_tasks.remove(&msg_id) {
if (task)(self.clone(), cv.clone()) { if (task)(self.clone(), cv.clone()) {
@ -225,10 +219,7 @@ impl IotaConnection {
} }
if cv.is_type(CommunicationType::CompleteRegisterUser) { if cv.is_type(CommunicationType::CompleteRegisterUser) {
let response_cv = self let response_cv = get_omega_connection()
.state
.omega
.clone()
.await_response( .await_response(
&cv.clone().with_sender(self.iota_id), &cv.clone().with_sender(self.iota_id),
Some(Duration::from_secs(20)), Some(Duration::from_secs(20)),
@ -274,10 +265,7 @@ impl IotaConnection {
async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) { async fn handle_omega_forward(self: Arc<Self>, cv: CommunicationValue) {
let iota_for_closure = self.clone(); let iota_for_closure = self.clone();
let response_cv = self let response_cv = get_omega_connection()
.state
.omega
.clone()
.await_response(&cv.with_sender(self.iota_id), Some(Duration::from_secs(20))) .await_response(&cv.with_sender(self.iota_id), Some(Duration::from_secs(20)))
.await; .await;
if let Ok(response_cv) = response_cv { if let Ok(response_cv) = response_cv {
@ -305,13 +293,10 @@ impl IotaConnection {
.map(|(k, v)| { .map(|(k, v)| {
let mut map = BTreeMap::new(); let mut map = BTreeMap::new();
if let Ok(uid) = k.parse::<i128>() { if let Ok(uid) = k.parse::<i128>() {
map.insert( map.insert(DataType::UserId.to_id(&tm), DataValue::SignedNumber(uid));
data_type_id(DataType::UserId, &tm),
DataValue::SignedNumber(uid),
);
} }
map.insert( map.insert(
data_type_id(DataType::LastPing, &tm), DataType::LastPing.to_id(&tm),
DataValue::SignedNumber(v.into()), DataValue::SignedNumber(v.into()),
); );
DataValue::container_from_map(&map) DataValue::container_from_map(&map)
@ -342,11 +327,11 @@ impl IotaConnection {
); );
if my_user_ids.contains(&(sender_id as u64)) { if my_user_ids.contains(&(sender_id as u64)) {
if let Some(target_rho) = self.state.rho.get_for_user(receiver_id as i64).await { if let Some(target_rho) = rho_manager::get_rho_con_for_user(receiver_id as i64).await {
target_rho.message_to_iota(cv).await; target_rho.message_to_iota(cv).await;
} else { } else {
if cv.is_type(CommunicationType::SetChatSecret) { if cv.is_type(CommunicationType::SetChatSecret) {
self.store_pending_chat_secret(cv.clone()); Self::store_pending_chat_secret(cv.clone());
let success = CommunicationValue::new(CommunicationType::Success) let success = CommunicationValue::new(CommunicationType::Success)
.with_id(cv.get_id()) .with_id(cv.get_id())
.with_sender(cv.get_sender()) .with_sender(cv.get_sender())
@ -402,7 +387,7 @@ impl IotaConnection {
// ============================ // ============================
// Load Calls // Load Calls
// ============================ // ============================
let calls: Vec<Arc<CallGroup>> = self.state.call_manager.get_call_groups(user_id).await; let calls: Vec<Arc<CallGroup>> = call_manager::get_call_groups(user_id).await;
let mut invites: HashMap<i64, Vec<DataValue>> = HashMap::new(); let mut invites: HashMap<i64, Vec<DataValue>> = HashMap::new();
let mut global_calls: Vec<DataValue> = Vec::new(); let mut global_calls: Vec<DataValue> = Vec::new();
@ -427,31 +412,27 @@ impl IotaConnection {
// Build base call container // Build base call container
let mut base_call_map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new(); let mut base_call_map: BTreeMap<DataTypeId, DataValue> = BTreeMap::new();
base_call_map.insert( base_call_map.insert(
data_type_id(DataType::CallId, &tm), DataType::CallId.to_id(&tm),
DataValue::Str(call.call_id.to_string()), DataValue::Str(call.call_id.to_string()),
); );
base_call_map.insert( base_call_map.insert(
data_type_id(DataType::CallMembers, &tm), DataType::CallMembers.to_id(&tm),
DataValue::Array(member_ids), DataValue::Array(member_ids),
); );
if timeout > 0 { if timeout > 0 {
base_call_map.insert( base_call_map.insert(
data_type_id(DataType::Timeout, &tm), DataType::Timeout.to_id(&tm),
DataValue::SignedNumber(timeout.into()), DataValue::SignedNumber(timeout.into()),
); );
} }
if admin { if admin {
base_call_map base_call_map.insert(DataType::HasAdmin.to_id(&tm), DataValue::Bool(true));
.insert(data_type_id(DataType::HasAdmin, &tm), DataValue::Bool(true));
} }
if let Some(secret) = call.get_secret_for_user(user_id).await { if let Some(secret) = call.get_secret_for_user(user_id).await {
base_call_map.insert( base_call_map.insert(DataType::CallSecret.to_id(&tm), secret.to_data_value());
data_type_id(DataType::CallSecret, &tm),
secret.to_data_value(),
);
} }
// Add to global calls with only this user's recipient-specific secret. // Add to global calls with only this user's recipient-specific secret.
@ -492,7 +473,7 @@ impl IotaConnection {
entries.iter().cloned().collect(); entries.iter().cloned().collect();
if let Some(DataValue::SignedNumber(id)) = if let Some(DataValue::SignedNumber(id)) =
user_map.get(&data_type_id(DataType::UserId, &tm)) user_map.get(&DataType::UserId.to_id(&tm))
{ {
interested_ids.push(*id as i64); interested_ids.push(*id as i64);
@ -500,7 +481,7 @@ impl IotaConnection {
&& !call_list.is_empty() && !call_list.is_empty()
{ {
user_map.insert( user_map.insert(
data_type_id(DataType::Calls, &tm), DataType::Calls.to_id(&tm),
DataValue::Array(call_list.clone()), DataValue::Array(call_list.clone()),
); );
} }
@ -517,10 +498,7 @@ impl IotaConnection {
// ============================ // ============================
// Notify Omega // Notify Omega
// ============================ // ============================
self.state OmegaConnection::user_states(user_id as i64, interested_ids.clone()).await;
.omega
.user_states(user_id as i64, interested_ids.clone())
.await;
// ============================ // ============================
// Notify Rho // Notify Rho

View file

@ -1,7 +1,7 @@
use super::{client_connection::ClientConnection, iota_connection::IotaConnection}; use super::{client_connection::ClientConnection, iota_connection::IotaConnection, rho_manager};
use crate::omega::omega_connection::OmegaConnection;
use crate::{data::user::UserStatus, rho::app_connection::AppConnection}; use crate::{data::user::UserStatus, rho::app_connection::AppConnection};
use dashmap::DashMap;
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue}; use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -11,8 +11,8 @@ use uuid::Uuid;
pub struct RhoConnection { pub struct RhoConnection {
iota_connection: Arc<IotaConnection>, iota_connection: Arc<IotaConnection>,
user_ids: Arc<RwLock<Vec<i64>>>, user_ids: Arc<RwLock<Vec<i64>>>,
client_connections: DashMap<(u64, u64), Arc<ClientConnection>>, client_connections: Arc<RwLock<Vec<Arc<ClientConnection>>>>,
app_connections: DashMap<(u64, String, Uuid), Arc<AppConnection>>, app_connections: Arc<RwLock<Vec<Arc<AppConnection>>>>,
} }
impl RhoConnection { impl RhoConnection {
@ -21,8 +21,8 @@ impl RhoConnection {
let rho_connection = Self { let rho_connection = Self {
iota_connection, iota_connection,
user_ids: Arc::new(RwLock::new(user_ids.clone())), user_ids: Arc::new(RwLock::new(user_ids.clone())),
client_connections: DashMap::new(), client_connections: Arc::new(RwLock::new(Vec::new())),
app_connections: DashMap::new(), app_connections: Arc::new(RwLock::new(Vec::new())),
}; };
rho_connection rho_connection
@ -58,10 +58,8 @@ impl RhoConnection {
} }
pub async fn get_client_connections(&self) -> Vec<Arc<ClientConnection>> { pub async fn get_client_connections(&self) -> Vec<Arc<ClientConnection>> {
self.client_connections let connections = self.client_connections.read().await;
.iter() connections.clone()
.map(|entry| entry.value().clone())
.collect()
} }
/// Get client connections for a specific user /// Get client connections for a specific user
@ -69,11 +67,14 @@ impl RhoConnection {
&self, &self,
user_id: i64, user_id: i64,
) -> Vec<Arc<ClientConnection>> { ) -> Vec<Arc<ClientConnection>> {
self.client_connections let connections = self.client_connections.read().await;
.iter() let mut collections = Vec::new();
.filter(|entry| entry.key().0 == user_id as u64) for con in connections.iter() {
.map(|entry| entry.value().clone()) if con.get_user_id().await == user_id as u64 {
.collect() collections.push(con.clone());
}
}
collections
} }
#[allow(unused)] #[allow(unused)]
@ -83,10 +84,10 @@ impl RhoConnection {
app_identifier: Option<String>, app_identifier: Option<String>,
app_session: Option<Uuid>, app_session: Option<Uuid>,
) -> Vec<Arc<AppConnection>> { ) -> Vec<Arc<AppConnection>> {
self.app_connections let connections = self.app_connections.read().await;
connections
.iter() .iter()
.filter(|entry| { .filter(|conn| {
let conn = entry.value();
if let Some(uid) = userid { if let Some(uid) = userid {
if conn.user_id != uid as u64 { if conn.user_id != uid as u64 {
return false; return false;
@ -104,22 +105,18 @@ impl RhoConnection {
} }
true true
}) })
.map(|entry| entry.value().clone()) .cloned()
.collect() .collect()
} }
pub async fn add_app_connection(&self, connection: Arc<AppConnection>) { pub async fn add_app_connection(&self, connection: Arc<AppConnection>) {
let key = ( let mut connections = self.app_connections.write().await;
connection.user_id, connections.push(connection);
connection.app_identifier.clone(),
connection.app_session,
);
self.app_connections.insert(key, connection);
} }
pub async fn close_app_connection(&self, connection: Arc<AppConnection>) { pub async fn close_app_connection(&self, connection: Arc<AppConnection>) {
self.app_connections let mut connections = self.app_connections.write().await;
.retain(|_, c| c.app_session != connection.app_session); connections.retain(|c| c.app_session != connection.app_session);
} }
/// Add a client connection /// Add a client connection
@ -133,20 +130,32 @@ impl RhoConnection {
self.iota_connection.send_message(&notification).await; self.iota_connection.send_message(&notification).await;
let should_notify_online = !self let mut should_notify_online = false;
.client_connections {
.iter() let mut connections = self.client_connections.write().await;
.any(|entry| entry.key().0 == user_id as u64); let mut keep = Vec::new();
self.client_connections let mut had_user = false;
.remove(&(user_id as u64, session_id as u64));
self.client_connections for con in connections.drain(..) {
.insert((user_id as u64, session_id as u64), Arc::clone(&connection)); if con.user_id as i64 == user_id {
had_user = true;
if con.session_id as i64 == session_id {
continue;
}
}
keep.push(con);
}
if !had_user {
should_notify_online = true;
}
keep.push(Arc::clone(&connection));
*connections = keep;
}
if should_notify_online { if should_notify_online {
self.iota_connection OmegaConnection::client_changed(
.state
.omega
.client_changed(
self.get_iota_id().await as i64, self.get_iota_id().await as i64,
user_id, user_id,
UserStatus::user_online, UserStatus::user_online,
@ -159,23 +168,27 @@ impl RhoConnection {
pub async fn close_client_connection(&self, connection: Arc<ClientConnection>) { pub async fn close_client_connection(&self, connection: Arc<ClientConnection>) {
let target_user_id = connection.user_id as i64; let target_user_id = connection.user_id as i64;
let target_session_id = connection.session_id as i64; let target_session_id = connection.session_id as i64;
self.client_connections let mut remaining_for_user = false;
.remove(&(target_user_id as u64, target_session_id as u64));
let remaining_for_user = self {
.client_connections let mut connections = self.client_connections.write().await;
.iter() let mut keep = Vec::new();
.any(|entry| entry.key().0 == target_user_id as u64); for con in connections.drain(..) {
if con.user_id as i64 == target_user_id
&& con.session_id as i64 == target_session_id
{
continue;
}
if con.user_id as i64 == target_user_id {
remaining_for_user = true;
}
keep.push(con);
}
*connections = keep;
}
if !remaining_for_user { if !remaining_for_user {
self.iota_connection OmegaConnection::client_changed(
.state
.rho
.remove_user_binding(target_user_id, self.get_iota_id().await as i64)
.await;
self.iota_connection
.state
.omega
.client_changed(
self.get_iota_id().await as i64, self.get_iota_id().await as i64,
target_user_id, target_user_id,
UserStatus::user_offline, UserStatus::user_offline,
@ -193,23 +206,15 @@ impl RhoConnection {
} }
// Remove from manager // Remove from manager
self.iota_connection rho_manager::remove_rho(self.get_iota_id().await as i64).await;
.state
.rho
.remove(self.get_iota_id().await as i64)
.await;
// Notify OmegaConnection // Notify OmegaConnection
self.iota_connection OmegaConnection::close_iota(self.get_iota_id().await as i64).await;
.state
.omega
.close_iota(self.get_iota_id().await as i64)
.await;
} }
/// Send message from Iota to specific client /// Send message from Iota to specific client
pub async fn message_to_client(&self, cv: CommunicationValue) { pub async fn message_to_client(&self, cv: CommunicationValue) {
let connections = self.get_client_connections().await; let connections = self.client_connections.read().await;
let receiver_id = cv.get_receiver(); let receiver_id = cv.get_receiver();
let session_id = cv.get_data(DataType::SessionId).as_number(); let session_id = cv.get_data(DataType::SessionId).as_number();
@ -233,7 +238,7 @@ impl RhoConnection {
/// Set interested users for a specific client /// Set interested users for a specific client
pub async fn set_interested(&self, user_id: i64, interested_ids: Vec<i64>) { pub async fn set_interested(&self, user_id: i64, interested_ids: Vec<i64>) {
let connections = self.get_client_connections().await; let connections = self.client_connections.read().await;
for connection in connections.iter() { for connection in connections.iter() {
let conn_user_id = connection.get_user_id().await; let conn_user_id = connection.get_user_id().await;
if conn_user_id == user_id as u64 { if conn_user_id == user_id as u64 {
@ -249,7 +254,7 @@ impl RhoConnection {
/// Check if clients are interested in a user /// Check if clients are interested in a user
#[allow(dead_code)] #[allow(dead_code)]
pub async fn are_they_interested(&self, user_id: i64, user_status: &str) { pub async fn are_they_interested(&self, user_id: i64, user_status: &str) {
let connections = self.get_client_connections().await; let connections = self.client_connections.read().await;
for connection in connections.iter() { for connection in connections.iter() {
connection connection
.clone() .clone()
@ -260,7 +265,7 @@ impl RhoConnection {
/// Get ping information for all clients /// Get ping information for all clients
pub async fn get_client_pings(&self) -> HashMap<String, i64> { pub async fn get_client_pings(&self) -> HashMap<String, i64> {
let connections = self.get_client_connections().await; let connections = self.client_connections.read().await;
let mut pings = HashMap::new(); let mut pings = HashMap::new();
for connection in connections.iter() { for connection in connections.iter() {
@ -280,6 +285,7 @@ impl RhoConnection {
/// Get count of active client connections /// Get count of active client connections
#[allow(dead_code)] #[allow(dead_code)]
pub async fn client_count(&self) -> usize { pub async fn client_count(&self) -> usize {
self.client_connections.len() let connections = self.client_connections.read().await;
connections.len()
} }
} }

View file

@ -1,129 +1,83 @@
use std::sync::Arc;
use dashmap::DashMap;
use super::rho_connection::RhoConnection; 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<Arc<RwLock<HashMap<i64, Arc<RhoConnection>>>>> =
* Owns both indices for active Iota connections. Keeping the user index next LazyLock::new(|| Arc::new(RwLock::new(HashMap::new())));
* to the Iota index makes binding updates atomic from the manager's caller's
* perspective and lets separate Omikron instances keep separate routing state. pub async fn get_rho_con_for_user(user_id: i64) -> Option<Arc<RhoConnection>> {
*/ let connections = RHO_CONNECTIONS.read().await;
#[derive(Default)] for rho_connection in connections.values() {
pub struct RhoManager { let rho_user_ids = rho_connection.get_user_ids().await;
connections: DashMap<i64, Arc<RhoConnection>>, log_in!(
users: DashMap<i64, Arc<RhoConnection>>, user_id,
PrintType::Client,
"Comparing user IDs: {:?}",
rho_user_ids
);
if rho_user_ids.contains(&user_id) {
return Some(Arc::clone(rho_connection));
}
}
None
} }
impl RhoManager { #[allow(dead_code)]
pub fn new() -> Self { pub async fn contains_iota(iota_id: i64) -> bool {
Self::default() let connections = RHO_CONNECTIONS.read().await;
connections.contains_key(&iota_id)
} }
pub async fn get_for_user(&self, user_id: i64) -> Option<Arc<RhoConnection>> { /// Bind a user ID to an already tracked iota/rho connection.
self.users.get(&user_id).map(|entry| entry.value().clone()) pub async fn bind_user_to_iota(user_id: i64, iota_id: i64) -> Option<Arc<RhoConnection>> {
} let connections = RHO_CONNECTIONS.read().await;
if let Some(rho_connection) = connections.get(&iota_id) {
let rho = Arc::clone(rho_connection);
drop(connections);
pub async fn contains_iota(&self, iota_id: i64) -> bool {
self.connections.contains_key(&iota_id)
}
pub async fn bind_user_to_iota(
&self,
user_id: i64,
iota_id: i64,
) -> Option<Arc<RhoConnection>> {
let rho = self
.connections
.get(&iota_id)
.map(|entry| entry.value().clone());
if let Some(rho) = rho {
rho.add_user_id(user_id).await; rho.add_user_id(user_id).await;
self.users.insert(user_id, rho.clone());
log_in!(
user_id,
PrintType::Client,
"Bound user {} to iota {}",
user_id,
iota_id
);
Some(rho) Some(rho)
} else { } else {
None None
} }
} }
pub async fn replace_users_for_iota(&self, iota_id: i64, user_ids: Vec<i64>) { /// Remove a RhoConnection by Iota ID
let rho = self pub async fn remove_rho(iota_id: i64) -> Option<Arc<RhoConnection>> {
.connections let mut connections = RHO_CONNECTIONS.write().await;
.get(&iota_id) connections.remove(&iota_id)
.map(|entry| entry.value().clone());
let Some(rho) = rho else {
return;
};
let previous_users = rho.get_user_ids().await;
rho.set_user_ids(user_ids.clone()).await;
rho.get_iota_connection()
.set_user_ids(
user_ids
.iter()
.filter_map(|user_id| u64::try_from(*user_id).ok())
.collect(),
)
.await;
for user_id in previous_users {
if self
.users
.get(&user_id)
.is_some_and(|entry| Arc::ptr_eq(entry.value(), &rho))
{
self.users.remove(&user_id);
}
}
for user_id in user_ids {
self.users.insert(user_id, rho.clone());
}
} }
pub async fn remove_user_binding(&self, user_id: i64, iota_id: i64) { /// Add a RhoConnection to the manager
let rho = self.users.get(&user_id).map(|entry| entry.value().clone()); pub async fn add_rho(rho_connection: Arc<RhoConnection>) {
if let Some(rho) = rho { let mut connections = RHO_CONNECTIONS.write().await;
if rho.get_iota_id().await as i64 == iota_id { let iota_id = rho_connection.get_iota_id().await;
self.users.remove(&user_id); connections.insert(iota_id as i64, rho_connection);
}
}
} }
pub async fn remove(&self, iota_id: i64) -> Option<Arc<RhoConnection>> { /// Get a RhoConnection by Iota ID directly
let rho = self.connections.remove(&iota_id).map(|(_, rho)| rho); #[allow(dead_code)]
if let Some(rho) = rho.as_ref() { pub async fn get_rho_by_iota(iota_id: i64) -> Option<Arc<RhoConnection>> {
self.users.retain(|_, mapped| !Arc::ptr_eq(mapped, rho)); let connections = RHO_CONNECTIONS.read().await;
} connections.get(&iota_id).map(Arc::clone)
rho
} }
pub async fn add(&self, rho_connection: Arc<RhoConnection>) { /// Get the count of active connections
let iota_id = rho_connection.get_iota_id().await as i64; pub async fn connection_count() -> usize {
let user_ids = rho_connection.get_user_ids().await; let connections = RHO_CONNECTIONS.read().await;
self.connections.insert(iota_id, rho_connection.clone()); connections.len()
for user_id in user_ids {
self.users.insert(user_id, rho_connection.clone());
}
}
pub async fn get_by_iota(&self, iota_id: i64) -> Option<Arc<RhoConnection>> {
self.connections
.get(&iota_id)
.map(|entry| entry.value().clone())
}
pub async fn connection_count(&self) -> usize {
self.connections.len()
}
pub async fn iota_ids(&self) -> Vec<i64> {
self.connections.iter().map(|entry| *entry.key()).collect()
}
pub async fn connections(&self) -> Vec<Arc<RhoConnection>> {
self.connections
.iter()
.map(|entry| entry.value().clone())
.collect()
}
} }

View file

@ -1,14 +1,13 @@
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc;
use std::time::Duration; use std::time::Duration;
use base64::Engine as _; use base64::Engine as _;
use base64::engine::general_purpose::STANDARD as BASE64_STD; use base64::engine::general_purpose::STANDARD as BASE64_STD;
use crate::load_keyring;
use crate::{ use crate::{
app_state::AppState,
log, log_err, log, log_err,
omega::omega_connection::OmegaConnection, omega::omega_connection::get_omega_connection,
rho::connection::GeneralConnection, rho::connection::GeneralConnection,
util::{file_util::load_file_vec, logger::PrintType}, util::{file_util::load_file_vec, logger::PrintType},
}; };
@ -25,7 +24,6 @@ use mtp::webserver::{MTPWebServer, WebServerConfig};
* through mtp's unauthenticated fallback instead of rejecting it outright. * through mtp's unauthenticated fallback instead of rejecting it outright.
*/ */
pub async fn get_by_connector_id( pub async fn get_by_connector_id(
omega: Arc<OmegaConnection>,
client_id: u64, client_id: u64,
description: Option<String>, description: Option<String>,
) -> Option<PublicKeyBundle> { ) -> Option<PublicKeyBundle> {
@ -40,7 +38,7 @@ pub async fn get_by_connector_id(
_ => return None, _ => return None,
}; };
let response = match omega let response = match get_omega_connection()
.await_response(&request, Some(Duration::from_secs(20))) .await_response(&request, Some(Duration::from_secs(20)))
.await .await
{ {
@ -64,11 +62,7 @@ pub async fn get_by_connector_id(
} }
/* Only Iota registration goes through mtp's Register flow; users are registered out of band. */ /* Only Iota registration goes through mtp's Register flow; users are registered out of band. */
pub async fn complete_register( pub async fn complete_register(pub_key: PublicKeyBundle, description: Option<String>) -> u64 {
omega: Arc<OmegaConnection>,
pub_key: PublicKeyBundle,
description: Option<String>,
) -> u64 {
println!("Iota register start"); println!("Iota register start");
if description.as_deref() != Some("iota") { if description.as_deref() != Some("iota") {
return 0; return 0;
@ -81,7 +75,7 @@ pub async fn complete_register(
DataValue::Str(BASE64_STD.encode(pub_key.as_bytes())), DataValue::Str(BASE64_STD.encode(pub_key.as_bytes())),
); );
let response = match omega let response = match get_omega_connection()
.await_response(&request, Some(Duration::from_secs(20))) .await_response(&request, Some(Duration::from_secs(20)))
.await .await
{ {
@ -103,14 +97,14 @@ pub async fn complete_register(
} }
} }
pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error>> { pub async fn start(port: u16) -> Result<(), Box<dyn std::error::Error>> {
let cert_pem = load_file_vec("certs", "cert.pem").expect("Error loading Pemfile"); 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 key_pem = load_file_vec("certs", "key.pem").expect("Error loading Keyfile");
let host_config = HostConfig::new( let host_config = HostConfig::new(
IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)),
state.config.rho_port, port,
cert_pem, cert_pem,
key_pem, key_pem,
) )
@ -130,33 +124,16 @@ pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error
.with_persistent_stream_retries(5, Duration::from_secs(5)), .with_persistent_stream_retries(5, Duration::from_secs(5)),
) )
.with_authentication( .with_authentication(
state load_keyring(),
.keyring_for_host() Box::new(|user_id, description| Box::pin(get_by_connector_id(user_id, description))),
.map_err(|error| format!("Unable to copy keyring for host: {error}"))?, Box::new(|pub_key, description| Box::pin(complete_register(pub_key, description))),
Box::new({
let omega = state.omega.clone();
move |user_id, description| {
Box::pin(get_by_connector_id(omega.clone(), user_id, description))
}
}),
Box::new({
let omega = state.omega.clone();
move |pub_key, description| {
Box::pin(complete_register(omega.clone(), pub_key, description))
}
}),
) )
.with_authentication_policy(AuthenticationPolicy::AllowAuthentication); .with_authentication_policy(AuthenticationPolicy::AllowAuthentication);
let web_config = WebServerConfig::new() let web_config = WebServerConfig::new()
.route("/", |_request, response| async move { response.body("OK") })?; .route("/", |_request, response| async move { response.body("OK") })?;
let mut host = MTPWebServer::new(host_config, web_config).await?; let mut host = MTPWebServer::new(host_config, web_config).await?;
log!( log!(0, PrintType::General, "Server listening on port {}.", port);
0,
PrintType::General,
"Server listening on port {}.",
state.config.rho_port
);
loop { loop {
let conn = match host.accept().await { let conn = match host.accept().await {
@ -176,9 +153,8 @@ pub async fn start(state: Arc<AppState>) -> Result<(), Box<dyn std::error::Error
} }
}; };
let state = state.clone();
tokio::spawn(async move { tokio::spawn(async move {
let Some(conn) = GeneralConnection::new(conn, state) else { let Some(conn) = GeneralConnection::new(conn) else {
log_err!( log_err!(
0, 0,
PrintType::General, PrintType::General,

View file

@ -1,29 +0,0 @@
use std::sync::Arc;
use uuid::Uuid;
use crate::{
app_state::AppState,
calls::{call_group::CallGroup, error::CallError},
};
pub struct CallService {
state: Arc<AppState>,
}
impl CallService {
pub fn new(state: Arc<AppState>) -> Self {
Self { state }
}
pub async fn get_call(&self, call_id: Uuid) -> Option<Arc<CallGroup>> {
self.state.call_manager.get_call(call_id).await
}
pub async fn token_for_user(&self, user_id: u64, call_id: Uuid) -> Result<String, CallError> {
self.state
.call_manager
.get_call_token(user_id, call_id)
.await
}
}

View file

@ -1,148 +0,0 @@
use std::collections::BTreeMap;
use std::sync::Arc;
use mtp::codec::{DataType, DataValue, TypeMap};
use crate::{calls::call_manager::CallManager, util::data_type_id};
pub struct CallStateAggregator {
call_manager: Arc<CallManager>,
}
impl CallStateAggregator {
pub fn new(call_manager: Arc<CallManager>) -> Self {
Self { call_manager }
}
pub async fn augment_contacts(
&self,
user_id: u64,
contacts: Vec<DataValue>,
) -> (Vec<DataValue>, Vec<DataValue>) {
let tm = TypeMap::latest();
let mut invites = std::collections::HashMap::new();
let mut global_calls = Vec::new();
for call in self.call_manager.get_call_groups(user_id).await {
let members = call.members.read().await;
let Some(call_self) = members.iter().find(|member| member.user_id == user_id) else {
continue;
};
let mut call_map = BTreeMap::new();
call_map.insert(
data_type_id(DataType::CallId, &tm),
DataValue::Str(call.call_id.to_string()),
);
call_map.insert(
data_type_id(DataType::CallMembers, &tm),
DataValue::Array(
members
.iter()
.map(|member| DataValue::SignedNumber(member.user_id.into()))
.collect(),
),
);
let timeout = *call_self.timeout.read().await;
if timeout > 0 {
call_map.insert(
data_type_id(DataType::Timeout, &tm),
DataValue::SignedNumber((timeout as i64).into()),
);
}
if call_self.has_admin() {
call_map.insert(data_type_id(DataType::HasAdmin, &tm), DataValue::Bool(true));
}
if let Some(secret) = call.get_secret_for_user(user_id).await {
call_map.insert(
data_type_id(DataType::CallSecret, &tm),
secret.to_data_value(),
);
}
let call_value = DataValue::container_from_map(&call_map);
global_calls.push(call_value.clone());
for member in members.iter().filter(|member| member.user_id != user_id) {
invites
.entry(member.user_id as i64)
.or_insert_with(Vec::new)
.push(call_value.clone());
}
}
let user_id_type = data_type_id(DataType::UserId, &tm);
let calls_type = data_type_id(DataType::Calls, &tm);
let augmented_contacts = contacts
.into_iter()
.map(|contact| {
let Some(mut contact_map) = contact.as_map() else {
return contact;
};
let Some(DataValue::SignedNumber(contact_id)) = contact_map.get(&user_id_type)
else {
return DataValue::container_from_map(&contact_map);
};
if let Some(calls) = invites.get(&(*contact_id as i64)) {
contact_map.insert(calls_type, DataValue::Array(calls.clone()));
}
DataValue::container_from_map(&contact_map)
})
.collect();
(augmented_contacts, global_calls)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::calls::{call_group::CallGroup, caller::Caller};
use uuid::Uuid;
#[tokio::test]
async fn augment_contacts_preserves_contacts_without_call_invites() {
let aggregator = CallStateAggregator::new(Arc::new(CallManager::default()));
let contacts = vec![DataValue::Str("unchanged".to_string())];
let (augmented, global_calls) = aggregator.augment_contacts(7, contacts.clone()).await;
assert_eq!(augmented, contacts);
assert!(global_calls.is_empty());
}
#[tokio::test]
async fn augment_contacts_adds_call_to_member_contact() {
let call_id = Uuid::new_v4();
let manager = Arc::new(CallManager::default());
let group = Arc::new(CallGroup::new(
call_id,
Arc::new(Caller::new(1, call_id, true)),
));
group
.members
.write()
.await
.push(Arc::new(Caller::new(2, call_id, false)));
manager.groups.insert(call_id, group);
let tm = TypeMap::latest();
let user_id_type = data_type_id(DataType::UserId, &tm);
let calls_type = data_type_id(DataType::Calls, &tm);
let mut contact = BTreeMap::new();
contact.insert(user_id_type, DataValue::SignedNumber(2.into()));
let (contacts, global_calls) = CallStateAggregator::new(manager)
.augment_contacts(1, vec![DataValue::container_from_map(&contact)])
.await;
assert_eq!(global_calls.len(), 1);
let Some(contact) = contacts[0].as_map() else {
panic!("expected contact map");
};
assert!(matches!(
contact.get(&calls_type),
Some(DataValue::Array(_))
));
}
}

View file

@ -1,4 +0,0 @@
pub mod call_service;
pub mod call_state;
pub mod routing_service;
pub mod user_service;

View file

@ -1,25 +0,0 @@
use std::sync::Arc;
use mtp::codec::CommunicationValue;
use crate::{app_state::AppState, rho::rho_connection::RhoConnection};
pub struct RoutingService {
state: Arc<AppState>,
}
impl RoutingService {
pub fn new(state: Arc<AppState>) -> Self {
Self { state }
}
pub async fn route_to_user(&self, user_id: i64, message: CommunicationValue) {
if let Some(connection) = self.state.rho.get_for_user(user_id).await {
connection.message_to_iota(message).await;
}
}
pub async fn connection_for_user(&self, user_id: i64) -> Option<Arc<RhoConnection>> {
self.state.rho.get_for_user(user_id).await
}
}

View file

@ -1,17 +0,0 @@
use std::sync::Arc;
use crate::{app_state::AppState, rho::rho_connection::RhoConnection};
pub struct UserService {
state: Arc<AppState>,
}
impl UserService {
pub fn new(state: Arc<AppState>) -> Self {
Self { state }
}
pub async fn connection_for_user(&self, user_id: i64) -> Option<Arc<RhoConnection>> {
self.state.rho.get_for_user(user_id).await
}
}

View file

@ -1,16 +1,2 @@
pub mod file_util; pub mod file_util;
pub mod logger; pub mod logger;
use mtp::codec::{DataType, DataTypeId, TypeMap};
/*
* Omikron serializes fields using the latest TypeMap, whose built-in fields
* are required by the protocol. Keep the invariant in one place while using
* the non-deprecated conversion API.
*/
pub fn data_type_id(data_type: DataType, type_map: &TypeMap) -> DataTypeId {
let Some(id) = data_type.try_to_id(type_map) else {
panic!("required data type is missing from the TypeMap: {data_type:?}");
};
id
}