[Updt] Mtp 0.3.0
This commit is contained in:
parent
2695a81aa0
commit
b3441a8902
33 changed files with 1480 additions and 1531 deletions
514
Cargo.lock
generated
514
Cargo.lock
generated
|
|
@ -2,12 +2,6 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "adler2"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.5.2"
|
||||
|
|
@ -18,17 +12,6 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58"
|
||||
dependencies = [
|
||||
"cipher 0.5.2",
|
||||
"cpubits",
|
||||
"cpufeatures 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
|
|
@ -212,7 +195,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -233,15 +215,6 @@ version = "1.12.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "bzip2"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3a53fac24f34a81bc9954b5d6cfce0c21e18ec6959f44f56e8e90e4bb7c346c"
|
||||
dependencies = [
|
||||
"libbz2-rs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.3"
|
||||
|
|
@ -273,7 +246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.4.4",
|
||||
"cipher",
|
||||
"cpufeatures 0.2.17",
|
||||
]
|
||||
|
||||
|
|
@ -296,7 +269,7 @@ checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
|
|||
dependencies = [
|
||||
"aead",
|
||||
"chacha20 0.9.1",
|
||||
"cipher 0.4.4",
|
||||
"cipher",
|
||||
"poly1305",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -308,20 +281,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common 0.1.7",
|
||||
"inout 0.1.4",
|
||||
"inout",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c"
|
||||
dependencies = [
|
||||
"crypto-common 0.2.2",
|
||||
"inout 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cmake"
|
||||
version = "0.1.58"
|
||||
|
|
@ -359,22 +322,6 @@ version = "0.10.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.10.1"
|
||||
|
|
@ -391,12 +338,6 @@ version = "0.8.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||
|
||||
[[package]]
|
||||
name = "cpubits"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
|
|
@ -430,15 +371,6 @@ version = "2.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.13"
|
||||
|
|
@ -547,12 +479,6 @@ version = "2.11.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
|
||||
|
||||
[[package]]
|
||||
name = "deflate64"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac6b926516df9c60bfa16e107b21086399f8285a44ca9711344b9e553c5146e2"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
|
|
@ -617,7 +543,6 @@ dependencies = [
|
|||
"const-oid 0.10.2",
|
||||
"crypto-common 0.2.2",
|
||||
"ctutils",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -683,15 +608,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
|
|
@ -765,16 +681,6 @@ version = "0.1.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
||||
dependencies = [
|
||||
"miniz_oxide",
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
|
|
@ -1199,22 +1105,6 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1223,23 +1113,12 @@ version = "0.1.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"ipnet",
|
||||
"libc",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"system-configuration",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1365,21 +1244,6 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7"
|
||||
dependencies = [
|
||||
"hybrid-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
|
|
@ -1484,12 +1348,6 @@ dependencies = [
|
|||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libbz2-rs-sys"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34b357333733e8260735ba5894eb928c02ecc69c78715f01a8019e7fa7f2db4c"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
|
|
@ -1551,15 +1409,6 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rust2"
|
||||
version = "0.16.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca93e534d1142d1d0dcca6d25fe302508a5dfb40b302802904577725ea0b695b"
|
||||
dependencies = [
|
||||
"sha2 0.11.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
|
|
@ -1576,28 +1425,12 @@ version = "2.8.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.8.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
||||
dependencies = [
|
||||
"adler2",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.2.2"
|
||||
|
|
@ -1665,7 +1498,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"mtp-client",
|
||||
"mtp-codec",
|
||||
|
|
@ -1681,7 +1514,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-client"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
|
|
@ -1694,7 +1527,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-codec"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"byteorder",
|
||||
|
|
@ -1708,7 +1541,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-common"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"quinn",
|
||||
"rustls",
|
||||
|
|
@ -1719,7 +1552,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-crypto"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"argon2",
|
||||
"base64 0.22.1",
|
||||
|
|
@ -1742,7 +1575,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-files"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"mtp-crypto",
|
||||
"rand 0.10.2",
|
||||
|
|
@ -1753,7 +1586,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-host"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
|
|
@ -1769,7 +1602,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-transport"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"mtp-codec",
|
||||
|
|
@ -1789,7 +1622,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-type-map"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
|
|
@ -1798,7 +1631,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "mtp-webserver"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#d11eb04d12e35dbfb42dd9a3f201d2da15f4af70"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
|
@ -1922,17 +1755,15 @@ dependencies = [
|
|||
"mtp",
|
||||
"once_cell",
|
||||
"rand 0.10.2",
|
||||
"reqwest",
|
||||
"rustls",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"thiserror 2.0.20",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"uuid",
|
||||
"zip",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1993,16 +1824,6 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112d82ceb8c5bf524d9af484d4e4970c9fd5a0cc15ba14ad93dccd28873b0629"
|
||||
dependencies = [
|
||||
"digest 0.11.3",
|
||||
"hmac 0.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem"
|
||||
version = "3.0.6"
|
||||
|
|
@ -2118,12 +1939,6 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||
|
||||
[[package]]
|
||||
name = "ppmd-rust"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efca4c95a19a79d1c98f791f10aebd5c1363b473244630bb7dbde1dc98455a24"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.21"
|
||||
|
|
@ -2306,44 +2121,6 @@ dependencies = [
|
|||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"rustls-platform-verifier",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.17.14"
|
||||
|
|
@ -2446,7 +2223,7 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
|
||||
dependencies = [
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"jni",
|
||||
"log",
|
||||
|
|
@ -2522,7 +2299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation 0.10.1",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
|
|
@ -2623,17 +2400,6 @@ dependencies = [
|
|||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures 0.3.0",
|
||||
"digest 0.11.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.9"
|
||||
|
|
@ -2713,12 +2479,6 @@ dependencies = [
|
|||
"rand_core 0.10.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simd-adler32"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea"
|
||||
|
||||
[[package]]
|
||||
name = "simd_cesu8"
|
||||
version = "1.2.0"
|
||||
|
|
@ -2918,7 +2678,7 @@ dependencies = [
|
|||
"rand 0.8.7",
|
||||
"rsa",
|
||||
"serde",
|
||||
"sha1 0.10.7",
|
||||
"sha1",
|
||||
"sha2 0.10.9",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
|
|
@ -3006,24 +2766,6 @@ dependencies = [
|
|||
"unicode-properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
|
|
@ -3052,15 +2794,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.13.2"
|
||||
|
|
@ -3072,27 +2805,6 @@ dependencies = [
|
|||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation 0.9.4",
|
||||
"system-configuration-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.69"
|
||||
|
|
@ -3140,7 +2852,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"js-sys",
|
||||
"num-conv",
|
||||
"powerfmt",
|
||||
"serde_core",
|
||||
|
|
@ -3247,56 +2958,12 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"libc",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.44"
|
||||
|
|
@ -3329,18 +2996,6 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typed-path"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.20.1"
|
||||
|
|
@ -3453,15 +3108,6 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
|
||||
dependencies = [
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
|
|
@ -3487,16 +3133,6 @@ dependencies = [
|
|||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.77"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b7777d5cc23d0e91404e53ce2d5e8ec7acae3026b16233dba62cd3246457950"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.127"
|
||||
|
|
@ -3529,16 +3165,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c435338968042f4f59a557f690a253676d47ce13ceb55d70100e7facf6620a30"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
|
|
@ -3605,35 +3231,6 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
|
@ -3982,81 +3579,8 @@ dependencies = [
|
|||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zip"
|
||||
version = "8.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"bzip2",
|
||||
"constant_time_eq",
|
||||
"crc32fast",
|
||||
"deflate64",
|
||||
"flate2",
|
||||
"getrandom 0.4.3",
|
||||
"hmac 0.13.0",
|
||||
"indexmap",
|
||||
"lzma-rust2",
|
||||
"memchr",
|
||||
"pbkdf2",
|
||||
"ppmd-rust",
|
||||
"sha1 0.11.0",
|
||||
"time",
|
||||
"typed-path",
|
||||
"zeroize",
|
||||
"zopfli",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zlib-rs"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||
|
||||
[[package]]
|
||||
name = "zopfli"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"crc32fast",
|
||||
"log",
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.16+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ edition = "2024"
|
|||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git", features = [
|
||||
"crypto",
|
||||
"files",
|
||||
"raw",
|
||||
"web-server",
|
||||
] }
|
||||
|
||||
|
|
@ -19,7 +18,6 @@ dotenv = "0.15.0"
|
|||
http = "1"
|
||||
once_cell = "1.21.4"
|
||||
rand = "0.10.2"
|
||||
reqwest = "0.13.4"
|
||||
rustls = { version = "0.23.42", default-features = false, features = [
|
||||
"std",
|
||||
"tls12",
|
||||
|
|
@ -27,11 +25,10 @@ rustls = { version = "0.23.42", default-features = false, features = [
|
|||
"prefer-post-quantum",
|
||||
] }
|
||||
sqlx = { version = "0.8.6", features = ["mysql", "runtime-tokio", "migrate"] }
|
||||
strum = "0.28.0"
|
||||
strum_macros = "0.28.0"
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
tokio-util = { version = "0.7.19", features = ["rt"] }
|
||||
uuid = { version = "1.24.0", features = ["v4", "v7"] }
|
||||
zip = "8.6.0"
|
||||
thiserror = "2.0.19"
|
||||
serde = { version = "1.0.229", features = ["derive"] }
|
||||
serde_json = "1.0.151"
|
||||
zeroize = "1.9"
|
||||
|
|
|
|||
13
README.md
13
README.md
|
|
@ -1,2 +1,15 @@
|
|||
# Omega
|
||||
The Omega is Tensamin's central Server. It maintains the centralized user Registry & manages Omikron useage.
|
||||
|
||||
Omega requires the non-empty `OMEGA_IDENTITY_SECRET` environment variable at startup. On first start it creates the protected private identity at `./omega.mk` and the matching public bundle at `./omega.mpkb`. Existing identity files are loaded fail-closed, so a malformed keyring or mismatched public bundle stops startup. A missing public bundle is rebuilt from a valid private keyring without generating a new identity.
|
||||
|
||||
## MTP routing contract
|
||||
|
||||
Omega preserves the MTP distinction between an absent routing field and a
|
||||
present zero value on the wire. For Omega request and response operations,
|
||||
both forms are rejected:
|
||||
|
||||
- request IDs are required and nonzero for correlation;
|
||||
- authenticated sender and receiver IDs are required and nonzero;
|
||||
- fields that are optional for a particular notification remain `Option` and
|
||||
are not converted to zero.
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 486541b9483356ff49ff3ec7016f87d3ecbeaa0e
|
||||
Subproject commit a297dcce60bc6e84696c6a16f5fd510beb2ca643
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
use std::{env, time::Duration};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RateLimitConfig {
|
||||
|
|
@ -9,6 +10,26 @@ pub struct RateLimitConfig {
|
|||
pub transport_connections_per_ip: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DispatchConfig {
|
||||
pub omikron_handler_concurrency: usize,
|
||||
pub global_handler_concurrency: usize,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct OmegaConfig {
|
||||
pub rate_limits: RateLimitConfig,
|
||||
pub dispatch: DispatchConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum ConfigError {
|
||||
#[error("invalid configuration value for {name}: {value}")]
|
||||
InvalidValue { name: String, value: String },
|
||||
#[error("configuration value for {name} is not valid Unicode")]
|
||||
InvalidUnicode { name: String },
|
||||
}
|
||||
|
||||
pub const fn cors_origin() -> &'static str {
|
||||
"*"
|
||||
}
|
||||
|
|
@ -26,40 +47,75 @@ impl Default for RateLimitConfig {
|
|||
}
|
||||
|
||||
impl RateLimitConfig {
|
||||
pub fn from_env() -> Self {
|
||||
pub fn from_env() -> Result<Self, ConfigError> {
|
||||
let defaults = Self::default();
|
||||
Self {
|
||||
window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window),
|
||||
general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests),
|
||||
Ok(Self {
|
||||
window: env_duration("RATE_LIMIT_WINDOW_SECONDS", defaults.window)?,
|
||||
general_requests: env_usize("RATE_LIMIT_GENERAL_REQUESTS", defaults.general_requests)?,
|
||||
registration_requests: env_usize(
|
||||
"RATE_LIMIT_REGISTRATION_REQUESTS",
|
||||
defaults.registration_requests,
|
||||
),
|
||||
)?,
|
||||
transport_connections: env_usize(
|
||||
"RATE_LIMIT_TRANSPORT_CONNECTIONS",
|
||||
defaults.transport_connections,
|
||||
),
|
||||
)?,
|
||||
transport_connections_per_ip: env_usize(
|
||||
"RATE_LIMIT_TRANSPORT_CONNECTIONS_PER_IP",
|
||||
defaults.transport_connections_per_ip,
|
||||
),
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_env_or_default() -> Self {
|
||||
Self::from_env().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DispatchConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
omikron_handler_concurrency: 32,
|
||||
global_handler_concurrency: 256,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn env_usize(name: &str, fallback: usize) -> usize {
|
||||
env::var(name)
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.filter(|value| *value > 0)
|
||||
.unwrap_or(fallback)
|
||||
impl OmegaConfig {
|
||||
pub fn from_env() -> Result<Self, ConfigError> {
|
||||
let rate_limits = RateLimitConfig::from_env()?;
|
||||
let defaults = DispatchConfig::default();
|
||||
Ok(Self {
|
||||
rate_limits,
|
||||
dispatch: DispatchConfig {
|
||||
omikron_handler_concurrency: env_usize(
|
||||
"OMEGA_OMIKRON_HANDLER_CONCURRENCY",
|
||||
defaults.omikron_handler_concurrency,
|
||||
)?,
|
||||
global_handler_concurrency: env_usize(
|
||||
"OMEGA_GLOBAL_HANDLER_CONCURRENCY",
|
||||
defaults.global_handler_concurrency,
|
||||
)?,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn env_duration(name: &str, fallback: Duration) -> Duration {
|
||||
env::var(name)
|
||||
.ok()
|
||||
.and_then(|value| value.parse::<u64>().ok())
|
||||
.filter(|value| *value > 0)
|
||||
.map(Duration::from_secs)
|
||||
.unwrap_or(fallback)
|
||||
fn env_usize(name: &str, fallback: usize) -> Result<usize, ConfigError> {
|
||||
match env::var(name) {
|
||||
Ok(value) => value
|
||||
.parse::<usize>()
|
||||
.ok()
|
||||
.filter(|value| *value > 0)
|
||||
.ok_or_else(|| ConfigError::InvalidValue {
|
||||
name: name.into(),
|
||||
value,
|
||||
}),
|
||||
Err(env::VarError::NotPresent) => Ok(fallback),
|
||||
Err(env::VarError::NotUnicode(_)) => Err(ConfigError::InvalidUnicode { name: name.into() }),
|
||||
}
|
||||
}
|
||||
|
||||
fn env_duration(name: &str, fallback: Duration) -> Result<Duration, ConfigError> {
|
||||
env_usize(name, fallback.as_secs() as usize).map(|value| Duration::from_secs(value as u64))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,20 +48,12 @@ pub async fn register_complete_iota(id: IotaId, public_key: PublicKeyBundle) ->
|
|||
}
|
||||
sqlx::query("INSERT INTO iotas (id, public_key) VALUES (?, ?)")
|
||||
.bind(id.0)
|
||||
.bind(public_key.as_bytes())
|
||||
.bind(public_key.try_as_bytes()?)
|
||||
.execute(&pool().await?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn change_iota_key(id: IotaId, key: PublicKeyBundle) -> Result<()> {
|
||||
sqlx::query("UPDATE iotas SET public_key = ? WHERE id = ?")
|
||||
.bind(key.as_bytes())
|
||||
.bind(id.0)
|
||||
.execute(&pool().await?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
pub async fn delete_iota(id: IotaId) -> Result<()> {
|
||||
sqlx::query("DELETE FROM iotas WHERE id = ?")
|
||||
.bind(id.0)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ pub async fn get_register_id() -> Result<UserId> {
|
|||
.unwrap_or(0);
|
||||
|
||||
let ts = timestamp as i64;
|
||||
if ts >= 1 && ts <= MAX_PROTOCOL_ID {
|
||||
if (1..=MAX_PROTOCOL_ID).contains(&ts) {
|
||||
return Ok(UserId::from(ts));
|
||||
}
|
||||
|
||||
|
|
@ -335,15 +335,6 @@ pub async fn change_status(id: UserId, value: String) -> Result<()> {
|
|||
.await
|
||||
}
|
||||
|
||||
pub async fn change_presence_preference(id: UserId, value: String) -> Result<()> {
|
||||
update(
|
||||
id,
|
||||
"UPDATE users SET presence_preference = ? WHERE id = ?",
|
||||
value.into_bytes(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn change_iota_id(id: UserId, value: Option<IotaId>) -> Result<()> {
|
||||
sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?")
|
||||
.bind(value.map(|id| id.0))
|
||||
|
|
@ -360,14 +351,6 @@ pub async fn change_token(id: UserId, value: String) -> Result<()> {
|
|||
)
|
||||
.await
|
||||
}
|
||||
pub async fn delete_user(id: UserId) -> Result<()> {
|
||||
sqlx::query("DELETE FROM users WHERE id = ?")
|
||||
.bind(id.0)
|
||||
.execute(&pool().await?)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete the central identity while retaining a durable instruction for the
|
||||
/// last hosting Iota. The pending row is intentionally independent of users:
|
||||
/// it must outlive the account row.
|
||||
|
|
@ -422,8 +405,9 @@ pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Re
|
|||
}
|
||||
|
||||
pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> {
|
||||
let public_key = public_key.try_as_bytes()?;
|
||||
sqlx::query("UPDATE users SET public_key = ? WHERE id = ?")
|
||||
.bind(public_key.as_bytes())
|
||||
.bind(public_key)
|
||||
.bind(id.0)
|
||||
.execute(&pool().await?)
|
||||
.await?;
|
||||
|
|
@ -446,6 +430,7 @@ pub async fn register_complete_user(
|
|||
if !valid_username(&username) {
|
||||
return Err(OmegaError::Validation("invalid username".into()));
|
||||
}
|
||||
let public_key_bytes = public_key.try_as_bytes()?;
|
||||
|
||||
let mut transaction = pool().await?.begin().await?;
|
||||
let lease = sqlx::query(
|
||||
|
|
@ -472,7 +457,7 @@ pub async fn register_complete_user(
|
|||
)
|
||||
.bind(id.0)
|
||||
.bind(username.as_bytes())
|
||||
.bind(public_key.as_bytes())
|
||||
.bind(&public_key_bytes)
|
||||
.bind(iota_id.0)
|
||||
.bind(token.as_bytes())
|
||||
.execute(&mut *transaction)
|
||||
|
|
@ -485,20 +470,20 @@ pub async fn register_complete_user(
|
|||
.bind(id.0)
|
||||
.fetch_optional(&mut *transaction)
|
||||
.await?;
|
||||
match existing
|
||||
.map(User::try_from)
|
||||
.transpose()
|
||||
.map_err(OmegaError::from)?
|
||||
{
|
||||
Some(existing)
|
||||
if existing.iota_id == Some(iota_id)
|
||||
let existing_matches = match existing {
|
||||
Some(existing) => {
|
||||
let existing = User::try_from(existing).map_err(OmegaError::from)?;
|
||||
existing.iota_id == Some(iota_id)
|
||||
&& existing.username == username
|
||||
&& existing.public_key.as_bytes() == public_key.as_bytes()
|
||||
&& existing.token == token =>
|
||||
{
|
||||
Ok(())
|
||||
&& existing.public_key.try_as_bytes()? == public_key_bytes
|
||||
&& existing.token == token
|
||||
}
|
||||
_ => Err(insert_error.into()),
|
||||
None => false,
|
||||
};
|
||||
if existing_matches {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(insert_error.into())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
25
src/error.rs
25
src/error.rs
|
|
@ -1,11 +1,34 @@
|
|||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum IdentityError {
|
||||
#[error("identity storage error at {path}: {source}")]
|
||||
Storage {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: mtp::files::FileError,
|
||||
},
|
||||
#[error("identity I/O error at {path}: {source}")]
|
||||
Io {
|
||||
path: PathBuf,
|
||||
#[source]
|
||||
source: std::io::Error,
|
||||
},
|
||||
#[error("public key bundle at {path} does not match the private keyring")]
|
||||
PublicBundleMismatch { path: PathBuf },
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum OmegaError {
|
||||
#[error("database pool is not initialized")]
|
||||
DatabaseNotInitialized,
|
||||
#[error("database error: {0}")]
|
||||
Database(sqlx::Error),
|
||||
#[error("cryptographic error: {0}")]
|
||||
Crypto(#[from] mtp::crypto::CryptoError),
|
||||
#[error("identity error: {0}")]
|
||||
Identity(#[from] IdentityError),
|
||||
#[error("invalid input: {0}")]
|
||||
Validation(String),
|
||||
#[error("resource not found")]
|
||||
|
|
@ -45,6 +68,8 @@ impl OmegaError {
|
|||
Self::NotFound => http::StatusCode::NOT_FOUND,
|
||||
Self::DatabaseNotInitialized
|
||||
| Self::Database(_)
|
||||
| Self::Crypto(_)
|
||||
| Self::Identity(_)
|
||||
| Self::Transport(_)
|
||||
| Self::NotConnected
|
||||
| Self::NotAuthenticated
|
||||
|
|
|
|||
314
src/identity.rs
Normal file
314
src/identity.rs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
use crate::error::{IdentityError, Result};
|
||||
use mtp::crypto::{Keyring, PublicKeyBundle};
|
||||
use mtp::files::{
|
||||
FileError, load_keyring, load_public_key_bundle, save_keyring, save_public_key_bundle,
|
||||
};
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicU64, Ordering},
|
||||
},
|
||||
};
|
||||
|
||||
pub const KEYRING_PATH: &str = "./omega.mk";
|
||||
pub const PUBLIC_KEY_PATH: &str = "./omega.mpkb";
|
||||
|
||||
pub struct OmegaIdentity {
|
||||
keyring: Arc<Keyring>,
|
||||
}
|
||||
|
||||
static PUBLIC_BUNDLE_TEMP_COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
impl OmegaIdentity {
|
||||
pub fn load_or_create(passphrase: &[u8]) -> Result<Self> {
|
||||
Self::load_or_create_at(
|
||||
Path::new(KEYRING_PATH),
|
||||
Path::new(PUBLIC_KEY_PATH),
|
||||
passphrase,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn load_or_create_at(
|
||||
keyring_path: impl AsRef<Path>,
|
||||
public_key_path: impl AsRef<Path>,
|
||||
passphrase: &[u8],
|
||||
) -> Result<Self> {
|
||||
let keyring_path = keyring_path.as_ref();
|
||||
let public_key_path = public_key_path.as_ref();
|
||||
let keyring = match load_keyring(keyring_path, passphrase) {
|
||||
Ok(keyring) => keyring,
|
||||
Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
return Self::create_at(keyring_path, public_key_path, passphrase);
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(IdentityError::Storage {
|
||||
path: keyring_path.to_path_buf(),
|
||||
source: error,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
};
|
||||
|
||||
let identity = Self {
|
||||
keyring: Arc::new(keyring),
|
||||
};
|
||||
match load_public_key_bundle(public_key_path) {
|
||||
Ok(persisted_bundle) => {
|
||||
identity.verify_public_bundle(public_key_path, &persisted_bundle)?
|
||||
}
|
||||
Err(FileError::Io(error)) if error.kind() == std::io::ErrorKind::NotFound => {
|
||||
Self::persist_public_bundle(&identity.public_key_bundle(), public_key_path)?;
|
||||
}
|
||||
Err(error) => {
|
||||
return Err(IdentityError::Storage {
|
||||
path: public_key_path.to_path_buf(),
|
||||
source: error,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
}
|
||||
Ok(identity)
|
||||
}
|
||||
|
||||
fn create_at(keyring_path: &Path, public_key_path: &Path, passphrase: &[u8]) -> Result<Self> {
|
||||
let keyring = Keyring::generate();
|
||||
save_keyring(&keyring, keyring_path, passphrase).map_err(|error| {
|
||||
IdentityError::Storage {
|
||||
path: keyring_path.to_path_buf(),
|
||||
source: error,
|
||||
}
|
||||
})?;
|
||||
Self::persist_public_bundle(&keyring.public_key_bundle(), public_key_path)?;
|
||||
Ok(Self {
|
||||
keyring: Arc::new(keyring),
|
||||
})
|
||||
}
|
||||
|
||||
fn persist_public_bundle(bundle: &PublicKeyBundle, path: &Path) -> Result<()> {
|
||||
let temporary_path = temporary_path(path)?;
|
||||
let result = save_public_key_bundle(bundle, &temporary_path).map_err(|source| {
|
||||
IdentityError::Storage {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
}
|
||||
});
|
||||
if let Err(error) = result {
|
||||
let _ = fs::remove_file(&temporary_path);
|
||||
return Err(error.into());
|
||||
}
|
||||
if let Err(source) = fs::File::open(&temporary_path).and_then(|file| file.sync_all()) {
|
||||
let _ = fs::remove_file(&temporary_path);
|
||||
return Err(IdentityError::Io {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
if let Err(source) = fs::rename(&temporary_path, path) {
|
||||
let _ = fs::remove_file(&temporary_path);
|
||||
return Err(IdentityError::Io {
|
||||
path: path.to_path_buf(),
|
||||
source,
|
||||
}
|
||||
.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn verify_public_bundle(&self, path: &Path, persisted_bundle: &PublicKeyBundle) -> Result<()> {
|
||||
let expected = self.keyring.public_key_bundle().try_as_bytes()?;
|
||||
let actual = persisted_bundle.try_as_bytes()?;
|
||||
if expected != actual {
|
||||
return Err(IdentityError::PublicBundleMismatch {
|
||||
path: path.to_path_buf(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn keyring(&self) -> &Keyring {
|
||||
&self.keyring
|
||||
}
|
||||
|
||||
pub fn public_key_bundle(&self) -> PublicKeyBundle {
|
||||
self.keyring().public_key_bundle()
|
||||
}
|
||||
|
||||
pub fn clone_keyring(&self) -> Result<Keyring> {
|
||||
let bytes = self.keyring.try_to_bytes()?;
|
||||
Ok(Keyring::from_bytes(&bytes)?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn from_keyring(keyring: Keyring) -> Self {
|
||||
Self {
|
||||
keyring: Arc::new(keyring),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn temporary_path(path: &Path) -> Result<PathBuf> {
|
||||
let parent = path.parent().unwrap_or_else(|| Path::new("."));
|
||||
let file_name = path.file_name().ok_or_else(|| IdentityError::Io {
|
||||
path: path.to_path_buf(),
|
||||
source: std::io::Error::new(
|
||||
std::io::ErrorKind::InvalidInput,
|
||||
"identity path has no file name",
|
||||
),
|
||||
})?;
|
||||
let mut temporary_name = file_name.to_os_string();
|
||||
temporary_name.push(format!(
|
||||
".tmp-{}-{}",
|
||||
std::process::id(),
|
||||
PUBLIC_BUNDLE_TEMP_COUNTER.fetch_add(1, Ordering::Relaxed)
|
||||
));
|
||||
Ok(parent.join(temporary_name))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
fn test_directory() -> std::path::PathBuf {
|
||||
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||
let id = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||
let path =
|
||||
std::env::temp_dir().join(format!("omega-identity-test-{}-{id}", std::process::id()));
|
||||
fs::create_dir_all(&path).expect("create test directory");
|
||||
path
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generated_identity_survives_restart_with_separate_file_formats() {
|
||||
let directory = test_directory();
|
||||
let keyring_path = directory.join("omega.mk");
|
||||
let public_key_path = directory.join("omega.mpkb");
|
||||
let passphrase = b"test-passphrase";
|
||||
|
||||
let first = OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
|
||||
.expect("create identity");
|
||||
let first_bundle = first.public_key_bundle().try_as_bytes().expect("bundle");
|
||||
|
||||
let keyring_bytes = fs::read(&keyring_path).expect("read keyring");
|
||||
let bundle_bytes = fs::read(&public_key_path).expect("read bundle");
|
||||
assert_eq!(&keyring_bytes[..4], b"MTMK");
|
||||
assert_eq!(&bundle_bytes[..4], b"MPKB");
|
||||
|
||||
let restarted =
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
|
||||
.expect("reload identity");
|
||||
assert_eq!(
|
||||
restarted
|
||||
.public_key_bundle()
|
||||
.try_as_bytes()
|
||||
.expect("bundle"),
|
||||
first_bundle
|
||||
);
|
||||
assert_eq!(
|
||||
load_public_key_bundle(&public_key_path)
|
||||
.expect("load public bundle")
|
||||
.try_as_bytes()
|
||||
.expect("bundle"),
|
||||
first_bundle
|
||||
);
|
||||
|
||||
fs::remove_dir_all(directory).expect("remove test directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_existing_keyring_does_not_create_a_new_identity() {
|
||||
let directory = test_directory();
|
||||
let keyring_path = directory.join("omega.mk");
|
||||
let public_key_path = directory.join("omega.mpkb");
|
||||
fs::write(&keyring_path, b"not-a-keyring").expect("write invalid keyring");
|
||||
|
||||
let result =
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"test-passphrase");
|
||||
assert!(result.is_err());
|
||||
assert!(!public_key_path.exists());
|
||||
|
||||
fs::remove_dir_all(directory).expect("remove test directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_public_bundle_is_repaired_without_changing_keyring() {
|
||||
let directory = test_directory();
|
||||
let keyring_path = directory.join("omega.mk");
|
||||
let public_key_path = directory.join("omega.mpkb");
|
||||
let passphrase = b"test-passphrase";
|
||||
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
|
||||
.expect("create identity");
|
||||
let original_keyring = fs::read(&keyring_path).expect("read keyring");
|
||||
fs::remove_file(&public_key_path).expect("remove bundle");
|
||||
|
||||
let repaired =
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
|
||||
.expect("repair bundle");
|
||||
|
||||
assert_eq!(
|
||||
fs::read(&keyring_path).expect("read keyring"),
|
||||
original_keyring
|
||||
);
|
||||
assert_eq!(
|
||||
repaired.public_key_bundle().try_as_bytes().expect("bundle"),
|
||||
load_public_key_bundle(&public_key_path)
|
||||
.expect("load bundle")
|
||||
.try_as_bytes()
|
||||
.expect("bundle")
|
||||
);
|
||||
|
||||
fs::remove_dir_all(directory).expect("remove test directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_public_bundle_stops_startup() {
|
||||
let directory = test_directory();
|
||||
let keyring_path = directory.join("omega.mk");
|
||||
let public_key_path = directory.join("omega.mpkb");
|
||||
let passphrase = b"test-passphrase";
|
||||
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
|
||||
.expect("create identity");
|
||||
let other_keyring = Keyring::generate();
|
||||
save_public_key_bundle(&other_keyring.public_key_bundle(), &public_key_path)
|
||||
.expect("save mismatched bundle");
|
||||
|
||||
assert!(matches!(
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase),
|
||||
Err(crate::OmegaError::Identity(
|
||||
IdentityError::PublicBundleMismatch { .. }
|
||||
))
|
||||
));
|
||||
|
||||
fs::remove_dir_all(directory).expect("remove test directory");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wrong_passphrase_does_not_replace_existing_keyring() {
|
||||
let directory = test_directory();
|
||||
let keyring_path = directory.join("omega.mk");
|
||||
let public_key_path = directory.join("omega.mpkb");
|
||||
let passphrase = b"test-passphrase";
|
||||
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, passphrase)
|
||||
.expect("create identity");
|
||||
let original_keyring = fs::read(&keyring_path).expect("read keyring");
|
||||
|
||||
assert!(
|
||||
OmegaIdentity::load_or_create_at(&keyring_path, &public_key_path, b"wrong-passphrase")
|
||||
.is_err()
|
||||
);
|
||||
assert_eq!(
|
||||
fs::read(&keyring_path).expect("read keyring"),
|
||||
original_keyring
|
||||
);
|
||||
|
||||
fs::remove_dir_all(directory).expect("remove test directory");
|
||||
}
|
||||
}
|
||||
69
src/main.rs
69
src/main.rs
|
|
@ -2,6 +2,7 @@ mod api;
|
|||
mod config;
|
||||
mod db;
|
||||
pub mod error;
|
||||
mod identity;
|
||||
mod models;
|
||||
mod server;
|
||||
mod sql;
|
||||
|
|
@ -17,45 +18,18 @@ use crate::transport::omikron_connection;
|
|||
use crate::util::file_util::get_directory;
|
||||
use crate::util::logger::PrintType;
|
||||
use crate::util::logger::startup;
|
||||
use crate::{config::OmegaConfig, server::middleware};
|
||||
use dotenv::from_path;
|
||||
use mtp::crypto::Keyring;
|
||||
use mtp::files::{load_keyring_raw, save_keyring_raw, save_public_key_bundle};
|
||||
use once_cell::sync::Lazy;
|
||||
use rustls::crypto::aws_lc_rs::default_provider;
|
||||
use std::env;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use tokio::time::interval;
|
||||
|
||||
const KEYRING_PATH: &str = "./omega.mk";
|
||||
|
||||
static KEYRING: Lazy<Keyring> = Lazy::new(|| {
|
||||
load_keyring_raw(KEYRING_PATH).unwrap_or_else(|_| {
|
||||
let kr = Keyring::generate();
|
||||
if let Err(error) = save_keyring_raw(&kr, KEYRING_PATH) {
|
||||
eprintln!("Failed to save generated keyring: {error}");
|
||||
}
|
||||
if let Err(error) = save_public_key_bundle(&kr.public_key_bundle(), KEYRING_PATH) {
|
||||
eprintln!("Failed to save generated public key bundle: {error}");
|
||||
}
|
||||
eprintln!("Generated new keyring at {}", KEYRING_PATH);
|
||||
kr
|
||||
})
|
||||
});
|
||||
|
||||
pub fn get_keyring() -> &'static Keyring {
|
||||
&KEYRING
|
||||
}
|
||||
pub fn load_keyring() -> Keyring {
|
||||
Keyring::from_bytes(&KEYRING.to_bytes()).unwrap_or_else(|error| {
|
||||
eprintln!("Failed to clone keyring: {error}");
|
||||
Keyring::generate()
|
||||
})
|
||||
}
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
if let Err(_) = default_provider().install_default() {
|
||||
if default_provider().install_default().is_err() {
|
||||
println!("Error loading Provider");
|
||||
return;
|
||||
}
|
||||
|
|
@ -64,6 +38,39 @@ async fn main() {
|
|||
log_in!("Incoming messages");
|
||||
log_out!("Outgoing messages");
|
||||
|
||||
let identity_secret = match env::var("OMEGA_IDENTITY_SECRET") {
|
||||
Ok(secret) if !secret.is_empty() => secret,
|
||||
Ok(_) => {
|
||||
log!("[FATAL] OMEGA_IDENTITY_SECRET must not be empty");
|
||||
return;
|
||||
}
|
||||
Err(error) => {
|
||||
log!("[FATAL] Unable to load OMEGA_IDENTITY_SECRET: {}", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
let identity_secret = Zeroizing::new(identity_secret);
|
||||
let config = match OmegaConfig::from_env() {
|
||||
Ok(config) => config,
|
||||
Err(error) => {
|
||||
log!("[FATAL] Omega configuration is invalid: {}", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
if middleware::initialize_config(config.rate_limits.clone()).is_err() {
|
||||
log!("[FATAL] Omega rate-limit configuration was initialized more than once");
|
||||
return;
|
||||
}
|
||||
let identity = match identity::OmegaIdentity::load_or_create(identity_secret.as_bytes()) {
|
||||
Ok(identity) => identity,
|
||||
Err(error) => {
|
||||
log!("[FATAL] Omega identity initialization failed: {}", error);
|
||||
return;
|
||||
}
|
||||
};
|
||||
drop(identity_secret);
|
||||
let state = OmegaState::new(identity, config);
|
||||
|
||||
log!("Started");
|
||||
log!(" .env");
|
||||
if let Err(e) = initialize().await {
|
||||
|
|
@ -96,7 +103,7 @@ async fn main() {
|
|||
.unwrap_or(443);
|
||||
|
||||
tokio::select! {
|
||||
result = omikron_connection::start(port, OmegaState::new()) => {
|
||||
result = omikron_connection::start(port, state) => {
|
||||
if let Err(e) = result {
|
||||
log_err!(0, PrintType::General, "Server error: {:?}", e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ fn serialize_public_key<S>(
|
|||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(&key.to_base64())
|
||||
let encoded = key.try_to_base64().map_err(serde::ser::Error::custom)?;
|
||||
serializer.serialize_str(&encoded)
|
||||
}
|
||||
|
||||
pub use ids::{IotaId, OmikronId, UserId};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use crate::db::{
|
|||
user_repo::{get_by_user_id, get_by_username},
|
||||
};
|
||||
use crate::error::{OmegaError, Result};
|
||||
use crate::load_keyring;
|
||||
use crate::identity::OmegaIdentity;
|
||||
use crate::models::UserId;
|
||||
use crate::server::{
|
||||
middleware,
|
||||
|
|
@ -24,6 +24,7 @@ use bytes::Bytes;
|
|||
use http::{Method, StatusCode};
|
||||
use mtp::webserver::{HttpRequest, HttpResponse, RouteParams};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn error_body(error: &OmegaError) -> String {
|
||||
json(&StatusResponse {
|
||||
|
|
@ -35,11 +36,11 @@ fn error_body(error: &OmegaError) -> String {
|
|||
})
|
||||
}
|
||||
|
||||
fn user_response(user: crate::models::User) -> UserResponse {
|
||||
UserResponse {
|
||||
fn user_response(user: crate::models::User) -> Result<UserResponse> {
|
||||
Ok(UserResponse {
|
||||
status: "success",
|
||||
username: user.username,
|
||||
public_key: user.public_key.to_base64(),
|
||||
public_key: user.public_key.try_to_base64()?,
|
||||
user_id: user.id.0,
|
||||
iota_id: user.iota_id.map(|id| id.0),
|
||||
sub_level: user.sub_level,
|
||||
|
|
@ -50,10 +51,10 @@ fn user_response(user: crate::models::User) -> UserResponse {
|
|||
avatar: user
|
||||
.avatar
|
||||
.map(|value| base64::engine::general_purpose::STANDARD.encode(value)),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
||||
async fn route(path_parts: &[&str], identity: &OmegaIdentity) -> Result<(StatusCode, String)> {
|
||||
match path_parts {
|
||||
["api", "get", "omikron"] => {
|
||||
let connection = get_random_omikron()
|
||||
|
|
@ -69,7 +70,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
json(&OmikronResponse {
|
||||
status: "success",
|
||||
id,
|
||||
public_key: omikron.public_key.to_base64(),
|
||||
public_key: omikron.public_key.try_to_base64()?,
|
||||
ip_address: omikron.ip_address,
|
||||
port: omikron.port,
|
||||
}),
|
||||
|
|
@ -105,7 +106,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
json(&OmikronResponse {
|
||||
status: "success",
|
||||
id: omikron.id.0,
|
||||
public_key: omikron.public_key.to_base64(),
|
||||
public_key: omikron.public_key.try_to_base64()?,
|
||||
ip_address: omikron.ip_address,
|
||||
port: omikron.port,
|
||||
}),
|
||||
|
|
@ -120,12 +121,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
.map(|(omikron_id, iotas)| {
|
||||
let iotas = iotas
|
||||
.into_iter()
|
||||
.map(|(iota_id, users)| {
|
||||
(
|
||||
iota_id.to_string(),
|
||||
users.into_iter().map(i64::from).collect(),
|
||||
)
|
||||
})
|
||||
.map(|(iota_id, users)| (iota_id.to_string(), users.into_iter().collect()))
|
||||
.collect();
|
||||
(omikron_id.to_string(), iotas)
|
||||
})
|
||||
|
|
@ -146,7 +142,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
json(&IotaResponse {
|
||||
status: "success",
|
||||
iota_id: iota.id.0,
|
||||
public_key: iota.public_key.to_base64(),
|
||||
public_key: iota.public_key.try_to_base64()?,
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
|
@ -158,7 +154,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
json(&UsernameResponse {
|
||||
status: "success",
|
||||
username: user.username,
|
||||
public_key: user.public_key.to_base64(),
|
||||
public_key: user.public_key.try_to_base64()?,
|
||||
user_id: user.id.0,
|
||||
iota_id: user.iota_id.map(|id| id.0),
|
||||
sub_level: user.sub_level,
|
||||
|
|
@ -168,7 +164,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
}
|
||||
["api", "get", "public_key"] => {
|
||||
let public_key = base64::engine::general_purpose::STANDARD
|
||||
.encode(load_keyring().public_key_bundle().as_bytes());
|
||||
.encode(identity.public_key_bundle().try_as_bytes()?);
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
json(&PublicKeyResponse {
|
||||
|
|
@ -180,7 +176,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
["api", "get", "user", id] => {
|
||||
let id = parse_positive_id(id)?;
|
||||
let user = get_by_user_id(UserId::from(id)).await?;
|
||||
Ok((StatusCode::OK, json(&user_response(user))))
|
||||
Ok((StatusCode::OK, json(&user_response(user)?)))
|
||||
}
|
||||
_ => Ok((
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
|
|
@ -189,13 +185,17 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpResponse {
|
||||
pub async fn handle(
|
||||
request: HttpRequest,
|
||||
response: HttpResponse,
|
||||
identity: Arc<OmegaIdentity>,
|
||||
) -> HttpResponse {
|
||||
let method = request.method;
|
||||
let path = request.uri.path().to_string();
|
||||
if method != Method::OPTIONS && !middleware::allow(request.remote_addr.ip(), &path) {
|
||||
return response
|
||||
.status(StatusCode::TOO_MANY_REQUESTS)
|
||||
.header("access-control-allow-origin", &crate::config::cors_origin())
|
||||
.header("access-control-allow-origin", crate::config::cors_origin())
|
||||
.body(json(&StatusResponse {
|
||||
status: "error_rate_limited",
|
||||
}));
|
||||
|
|
@ -203,7 +203,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons
|
|||
if method == Method::OPTIONS {
|
||||
return response
|
||||
.status(StatusCode::OK)
|
||||
.header("access-control-allow-origin", &crate::config::cors_origin())
|
||||
.header("access-control-allow-origin", crate::config::cors_origin())
|
||||
.header("access-control-allow-methods", "GET, POST, OPTIONS")
|
||||
.header("access-control-allow-headers", "*");
|
||||
}
|
||||
|
|
@ -213,7 +213,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons
|
|||
return match std::fs::read(file_path) {
|
||||
Ok(bytes) => response
|
||||
.status(StatusCode::OK)
|
||||
.header("access-control-allow-origin", &crate::config::cors_origin())
|
||||
.header("access-control-allow-origin", crate::config::cors_origin())
|
||||
.header("content-type", "application/zip")
|
||||
.header(
|
||||
"content-disposition",
|
||||
|
|
@ -222,7 +222,7 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons
|
|||
.body(Bytes::from(bytes)),
|
||||
Err(_) => response
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.header("access-control-allow-origin", &crate::config::cors_origin())
|
||||
.header("access-control-allow-origin", crate::config::cors_origin())
|
||||
.body(json(&StatusResponse {
|
||||
status: "error_not_found",
|
||||
})),
|
||||
|
|
@ -237,12 +237,12 @@ pub async fn handle(request: HttpRequest, response: HttpResponse) -> HttpRespons
|
|||
.status(StatusCode::TEMPORARY_REDIRECT)
|
||||
.header("location", &location);
|
||||
}
|
||||
let (status, body) = route(&path_parts)
|
||||
let (status, body) = route(&path_parts, &identity)
|
||||
.await
|
||||
.unwrap_or_else(|error| (error.status_code(), error_body(&error)));
|
||||
response
|
||||
.status(status)
|
||||
.header("access-control-allow-origin", &crate::config::cors_origin())
|
||||
.header("access-control-allow-origin", crate::config::cors_origin())
|
||||
.header("access-control-allow-headers", "*")
|
||||
.header("access-control-allow-methods", "GET, POST, OPTIONS")
|
||||
.body(body)
|
||||
|
|
@ -252,6 +252,7 @@ pub async fn handle_pattern(
|
|||
request: HttpRequest,
|
||||
response: HttpResponse,
|
||||
_params: RouteParams,
|
||||
identity: Arc<OmegaIdentity>,
|
||||
) -> HttpResponse {
|
||||
handle(request, response).await
|
||||
handle(request, response, identity).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,25 @@
|
|||
use dashmap::DashMap;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::net::IpAddr;
|
||||
use std::{collections::VecDeque, time::Instant};
|
||||
use std::{collections::VecDeque, sync::OnceLock, time::Instant};
|
||||
use tokio::time::interval;
|
||||
|
||||
static REQUESTS: Lazy<DashMap<(IpAddr, String), VecDeque<Instant>>> = Lazy::new(DashMap::new);
|
||||
static CONFIG: Lazy<crate::config::RateLimitConfig> =
|
||||
Lazy::new(crate::config::RateLimitConfig::from_env);
|
||||
static CONFIG: OnceLock<crate::config::RateLimitConfig> = OnceLock::new();
|
||||
const MAX_TRACKED_CLIENT_BUCKETS: usize = 100_000;
|
||||
|
||||
pub(crate) fn initialize_config(
|
||||
config: crate::config::RateLimitConfig,
|
||||
) -> Result<(), crate::config::RateLimitConfig> {
|
||||
CONFIG.set(config)
|
||||
}
|
||||
|
||||
fn config() -> &'static crate::config::RateLimitConfig {
|
||||
static FALLBACK: Lazy<crate::config::RateLimitConfig> =
|
||||
Lazy::new(crate::config::RateLimitConfig::from_env_or_default);
|
||||
CONFIG.get().unwrap_or(&FALLBACK)
|
||||
}
|
||||
|
||||
pub fn allow(remote_addr: IpAddr, path: &str) -> bool {
|
||||
let key = if path.contains("register") {
|
||||
"registration"
|
||||
|
|
@ -23,15 +34,15 @@ pub fn allow(remote_addr: IpAddr, path: &str) -> bool {
|
|||
}
|
||||
}
|
||||
let limit = if key == "registration" {
|
||||
CONFIG.registration_requests
|
||||
config().registration_requests
|
||||
} else {
|
||||
CONFIG.general_requests
|
||||
config().general_requests
|
||||
};
|
||||
let now = Instant::now();
|
||||
let mut entries = REQUESTS.entry(map_key).or_default();
|
||||
while entries
|
||||
.front()
|
||||
.is_some_and(|time| now.duration_since(*time) >= CONFIG.window)
|
||||
.is_some_and(|time| now.duration_since(*time) >= config().window)
|
||||
{
|
||||
entries.pop_front();
|
||||
}
|
||||
|
|
@ -44,7 +55,7 @@ pub fn allow(remote_addr: IpAddr, path: &str) -> bool {
|
|||
|
||||
pub fn spawn_cleanup_task() -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async {
|
||||
let mut ticker = interval(CONFIG.window);
|
||||
let mut ticker = interval(config().window);
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
cleanup_expired();
|
||||
|
|
@ -57,7 +68,7 @@ fn cleanup_expired() {
|
|||
REQUESTS.retain(|_, entries| {
|
||||
while entries
|
||||
.front()
|
||||
.is_some_and(|time| now.duration_since(*time) >= CONFIG.window)
|
||||
.is_some_and(|time| now.duration_since(*time) >= config().window)
|
||||
{
|
||||
entries.pop_front();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
pub mod api;
|
||||
pub mod index;
|
||||
pub mod middleware;
|
||||
pub mod server;
|
||||
pub mod short_link;
|
||||
pub mod validation;
|
||||
pub mod web;
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
use crate::server::{api, index::index_handler};
|
||||
use mtp::webserver::WebServerConfig;
|
||||
|
||||
pub fn build_web_config() -> Result<WebServerConfig, mtp::webserver::RouterError> {
|
||||
WebServerConfig::new()
|
||||
.route("/api/download/iota_frontend", api::handle)?
|
||||
.route("/api/get/omikron", api::handle)?
|
||||
.route("/api/get/connections", api::handle)?
|
||||
.route("/api/get/public_key", api::handle)?
|
||||
.route_pattern("/api/get/omikron/{id}", api::handle_pattern)?
|
||||
.route_pattern("/api/get/iota/{id}", api::handle_pattern)?
|
||||
.route_pattern("/api/get/id/{username}", api::handle_pattern)?
|
||||
.route_pattern("/api/get/user/{id}", api::handle_pattern)?
|
||||
.route_pattern("/direct/{short}", api::handle_pattern)?
|
||||
.fallback(|_request, response| async move { index_handler(response) })
|
||||
}
|
||||
|
|
@ -34,7 +34,7 @@ pub async fn get_short_link(short: &str) -> Result<String, ()> {
|
|||
short
|
||||
};
|
||||
let frag = short.replace(key, "");
|
||||
let normalized = normalize_short(&key);
|
||||
let normalized = normalize_short(key);
|
||||
|
||||
let target = short_link_repo::get(&normalized)
|
||||
.await
|
||||
|
|
|
|||
47
src/server/web.rs
Normal file
47
src/server/web.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
use crate::identity::OmegaIdentity;
|
||||
use crate::server::{api, index::index_handler};
|
||||
use mtp::webserver::{HttpRequest, HttpResponse, RouteParams, WebServerConfig};
|
||||
use std::{future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
type RouteFuture = Pin<Box<dyn Future<Output = HttpResponse> + Send>>;
|
||||
|
||||
fn api_handler(
|
||||
identity: Arc<OmegaIdentity>,
|
||||
) -> impl Fn(HttpRequest, HttpResponse) -> RouteFuture + Send + Sync + 'static {
|
||||
move |request, response| Box::pin(api::handle(request, response, identity.clone()))
|
||||
}
|
||||
|
||||
fn api_pattern_handler(
|
||||
identity: Arc<OmegaIdentity>,
|
||||
) -> impl Fn(HttpRequest, HttpResponse, RouteParams) -> RouteFuture + Send + Sync + 'static {
|
||||
move |request, response, params| {
|
||||
Box::pin(api::handle_pattern(
|
||||
request,
|
||||
response,
|
||||
params,
|
||||
identity.clone(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_web_config(
|
||||
identity: Arc<OmegaIdentity>,
|
||||
) -> Result<WebServerConfig, mtp::webserver::RouterError> {
|
||||
WebServerConfig::new()
|
||||
.route("/api/download/iota_frontend", api_handler(identity.clone()))?
|
||||
.route("/api/get/omikron", api_handler(identity.clone()))?
|
||||
.route("/api/get/connections", api_handler(identity.clone()))?
|
||||
.route("/api/get/public_key", api_handler(identity.clone()))?
|
||||
.route_pattern(
|
||||
"/api/get/omikron/{id}",
|
||||
api_pattern_handler(identity.clone()),
|
||||
)?
|
||||
.route_pattern("/api/get/iota/{id}", api_pattern_handler(identity.clone()))?
|
||||
.route_pattern(
|
||||
"/api/get/id/{username}",
|
||||
api_pattern_handler(identity.clone()),
|
||||
)?
|
||||
.route_pattern("/api/get/user/{id}", api_pattern_handler(identity.clone()))?
|
||||
.route_pattern("/direct/{short}", api_pattern_handler(identity))?
|
||||
.fallback(|_request, response| async move { index_handler(response) })
|
||||
}
|
||||
|
|
@ -12,11 +12,7 @@ pub enum UserStatus {
|
|||
iota_online,
|
||||
iota_borked,
|
||||
}
|
||||
#[allow(unused)]
|
||||
impl UserStatus {
|
||||
pub fn to_string(&self) -> String {
|
||||
format!("{:?}", self)
|
||||
}
|
||||
pub fn from_client_preference(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
"user_online" => Some(Self::user_online),
|
||||
|
|
@ -38,11 +34,18 @@ impl UserStatus {
|
|||
/// Parse a value received from a client or persisted as an account
|
||||
/// preference. Derived connectivity and diagnostic states are never valid
|
||||
/// preferences.
|
||||
#[allow(dead_code)]
|
||||
pub fn from_str(s: &str) -> Option<Self> {
|
||||
Self::from_client_preference(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for UserStatus {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(formatter, "{self:?}")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::UserStatus;
|
||||
|
|
|
|||
|
|
@ -275,10 +275,6 @@ impl PresenceTracker {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn check_index_consistency(&self) -> Result<(), String> {
|
||||
Self::check_indices(&self.routes.read().unwrap())
|
||||
}
|
||||
|
||||
pub fn set_preference(&self, user_id: i64, status: UserStatus) {
|
||||
self.preferences.write().unwrap().insert(user_id, status);
|
||||
}
|
||||
|
|
@ -335,14 +331,6 @@ impl PresenceTracker {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn track_iota(&self, iota_id: i64, omikron_id: i64) {
|
||||
self.connect_iota(iota_id, omikron_id);
|
||||
}
|
||||
|
||||
pub fn untrack_iota(&self, iota_id: i64, omikron_id: i64) -> bool {
|
||||
self.disconnect_iota(iota_id, omikron_id)
|
||||
}
|
||||
|
||||
pub fn track_session(&self, user_id: i64, session_id: i64, omikron_id: i64, iota_id: i64) {
|
||||
let mut routes = self.routes.write().unwrap();
|
||||
Self::insert_session_locked(
|
||||
|
|
@ -373,15 +361,6 @@ impl PresenceTracker {
|
|||
.is_some_and(|route| route.omikron_id == omikron_id)
|
||||
}
|
||||
|
||||
pub fn session_route(&self, user_id: i64, session_id: i64) -> Option<ClientSessionRoute> {
|
||||
self.routes
|
||||
.read()
|
||||
.unwrap()
|
||||
.sessions
|
||||
.get(&(user_id, session_id))
|
||||
.map(|route| route.clone())
|
||||
}
|
||||
|
||||
pub fn resolve_public_state(&self, user_id: i64, iota_id: i64) -> UserStatus {
|
||||
if !self.has_iota_route(iota_id) {
|
||||
return UserStatus::iota_offline;
|
||||
|
|
@ -496,12 +475,6 @@ impl PresenceTracker {
|
|||
Self::debug_assert_indices(&routes);
|
||||
}
|
||||
|
||||
pub fn remove_subscription(&self, user_id: i64, session_id: i64) {
|
||||
let mut routes = self.routes.write().unwrap();
|
||||
Self::remove_subscription_locked(&mut routes, (user_id, session_id));
|
||||
Self::debug_assert_indices(&routes);
|
||||
}
|
||||
|
||||
pub fn subscribers(&self, target_user_id: i64) -> Vec<PresenceSubscriber> {
|
||||
let mut subscribers = self
|
||||
.routes
|
||||
|
|
@ -542,15 +515,6 @@ impl PresenceTracker {
|
|||
sessions
|
||||
}
|
||||
|
||||
pub fn replace_routes_owned_by(
|
||||
&self,
|
||||
omikron_id: i64,
|
||||
iota_ids: &[i64],
|
||||
sessions: &[(i64, i64, i64)],
|
||||
) {
|
||||
self.replace_omikron_snapshot(omikron_id, iota_ids, sessions);
|
||||
}
|
||||
|
||||
pub fn replace_omikron_snapshot(
|
||||
&self,
|
||||
omikron_id: i64,
|
||||
|
|
@ -621,10 +585,6 @@ impl PresenceTracker {
|
|||
)
|
||||
}
|
||||
|
||||
pub fn track_iota_connection(&self, iota_id: i64, omikron_id: i64, primary: bool) {
|
||||
self.connect_iota_with_priority(iota_id, omikron_id, primary);
|
||||
}
|
||||
|
||||
pub fn untrack_iota_connection(&self, iota_id: i64, omikron_id: i64) -> bool {
|
||||
let connections_empty = self.disconnect_iota(iota_id, omikron_id);
|
||||
self.remove_sessions_for_iota(iota_id, omikron_id);
|
||||
|
|
@ -671,29 +631,6 @@ impl PresenceTracker {
|
|||
user_routes.into_iter().map(|(_, route)| route).collect()
|
||||
}
|
||||
|
||||
pub fn sessions_for_user(&self, user_id: i64) -> Vec<(i64, ClientSessionRoute)> {
|
||||
let routes = self.routes.read().unwrap();
|
||||
let mut sessions = routes
|
||||
.indices
|
||||
.sessions_by_user
|
||||
.get(&user_id)
|
||||
.into_iter()
|
||||
.flat_map(|session_ids| session_ids.iter())
|
||||
.filter_map(|session_id| {
|
||||
routes
|
||||
.sessions
|
||||
.get(&(user_id, *session_id))
|
||||
.map(|route| (*session_id, route.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
sessions.sort_by_key(|(session_id, route)| (*session_id, route.omikron_id));
|
||||
sessions
|
||||
}
|
||||
|
||||
pub fn route_for_session(&self, user_id: i64, session_id: i64) -> Option<ClientSessionRoute> {
|
||||
self.session_route(user_id, session_id)
|
||||
}
|
||||
|
||||
pub fn user_route(&self, user_id: i64) -> Option<ClientSessionRoute> {
|
||||
self.routes_for_user(user_id).into_iter().next()
|
||||
}
|
||||
|
|
@ -745,10 +682,6 @@ impl PresenceTracker {
|
|||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn remove_omikron_and_offline_users(&self, omikron_id: i64) -> RemovedOmikronState {
|
||||
self.remove_omikron(omikron_id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -756,6 +689,10 @@ mod tests {
|
|||
use super::{ClientSessionRoute, PresenceTracker};
|
||||
use crate::sql::connection_status::UserStatus;
|
||||
|
||||
fn check_index_consistency(tracker: &PresenceTracker) -> Result<(), String> {
|
||||
PresenceTracker::check_indices(&tracker.routes.read().unwrap())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effective_state_is_derived_from_routes_and_preference() {
|
||||
let tracker = PresenceTracker::default();
|
||||
|
|
@ -765,7 +702,7 @@ mod tests {
|
|||
UserStatus::iota_offline
|
||||
);
|
||||
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.connect_iota(11, 42);
|
||||
assert_eq!(
|
||||
tracker.resolve_public_state(7, 11),
|
||||
UserStatus::user_offline
|
||||
|
|
@ -783,7 +720,7 @@ mod tests {
|
|||
#[test]
|
||||
fn session_must_match_assigned_iota_and_live_route() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.track_session(7, 3, 42, 12);
|
||||
assert!(!tracker.has_active_session_for_iota(7, 11));
|
||||
assert_eq!(
|
||||
|
|
@ -807,7 +744,7 @@ mod tests {
|
|||
#[test]
|
||||
fn resolver_covers_all_public_preference_states() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.track_session(7, 3, 42, 11);
|
||||
|
||||
for (preference, expected) in [
|
||||
|
|
@ -826,14 +763,20 @@ mod tests {
|
|||
fn session_routes_replace_by_session_without_replacing_preferences() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.set_preference(7, UserStatus::user_dnd);
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.track_iota(12, 43);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.connect_iota(12, 43);
|
||||
tracker.track_session(7, 3, 42, 11);
|
||||
tracker.track_session(7, 4, 43, 12);
|
||||
|
||||
assert_eq!(tracker.preference(7), UserStatus::user_dnd);
|
||||
assert_eq!(
|
||||
tracker.session_route(7, 3),
|
||||
tracker
|
||||
.routes
|
||||
.read()
|
||||
.unwrap()
|
||||
.sessions
|
||||
.get(&(7, 3))
|
||||
.cloned(),
|
||||
Some(ClientSessionRoute {
|
||||
omikron_id: 42,
|
||||
iota_id: 11,
|
||||
|
|
@ -865,21 +808,21 @@ mod tests {
|
|||
#[test]
|
||||
fn losing_one_iota_route_keeps_iota_online() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.track_iota(11, 43);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.connect_iota(11, 43);
|
||||
assert!(tracker.has_iota_route(11));
|
||||
|
||||
assert!(!tracker.untrack_iota(11, 42));
|
||||
assert!(!tracker.disconnect_iota(11, 42));
|
||||
assert!(tracker.has_iota_route(11));
|
||||
assert!(tracker.untrack_iota(11, 43));
|
||||
assert!(tracker.disconnect_iota(11, 43));
|
||||
assert!(!tracker.has_iota_route(11));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iota_connection_routes_track_primary_and_replacement() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.track_iota_connection(11, 42, true);
|
||||
tracker.track_iota_connection(11, 43, false);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.connect_iota(11, 43);
|
||||
|
||||
assert_eq!(tracker.iota_connections(11), Some(vec![42, 43]));
|
||||
assert_eq!(tracker.primary_iota_route(11), Some(42));
|
||||
|
|
@ -961,8 +904,8 @@ mod tests {
|
|||
#[test]
|
||||
fn active_session_can_use_one_of_several_live_iota_routes() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.track_iota(11, 43);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.connect_iota(11, 43);
|
||||
tracker.track_session(7, 3, 43, 11);
|
||||
|
||||
assert!(tracker.has_active_session_for_iota(7, 11));
|
||||
|
|
@ -973,13 +916,13 @@ mod tests {
|
|||
#[test]
|
||||
fn route_snapshot_replaces_only_one_omikron() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.track_iota(12, 43);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.connect_iota(12, 43);
|
||||
tracker.track_session(7, 3, 42, 11);
|
||||
tracker.track_session(8, 4, 43, 12);
|
||||
tracker.replace_subscription(7, 3, 42, vec![20]);
|
||||
|
||||
tracker.replace_routes_owned_by(42, &[12], &[(9, 5, 12)]);
|
||||
tracker.replace_omikron_snapshot(42, &[12], &[(9, 5, 12)]);
|
||||
|
||||
assert!(!tracker.owns_session(7, 3, 42));
|
||||
assert!(tracker.owns_session(8, 4, 43));
|
||||
|
|
@ -992,8 +935,8 @@ mod tests {
|
|||
fn omikron_cleanup_preserves_other_routes_and_preferences() {
|
||||
let tracker = PresenceTracker::default();
|
||||
tracker.set_preference(7, UserStatus::user_dnd);
|
||||
tracker.track_iota(11, 42);
|
||||
tracker.track_iota(11, 43);
|
||||
tracker.connect_iota(11, 42);
|
||||
tracker.connect_iota(11, 43);
|
||||
tracker.track_session(7, 3, 42, 11);
|
||||
tracker.track_session(7, 4, 43, 11);
|
||||
tracker.replace_subscription(7, 3, 42, vec![20]);
|
||||
|
|
@ -1036,14 +979,14 @@ mod tests {
|
|||
.cloned(),
|
||||
Some([20, 21].into_iter().collect())
|
||||
);
|
||||
assert!(tracker.check_index_consistency().is_ok());
|
||||
assert!(check_index_consistency(&tracker).is_ok());
|
||||
|
||||
tracker.replace_subscription(7, 3, 42, vec![22]);
|
||||
assert!(tracker.subscribers(20).is_empty());
|
||||
assert!(tracker.subscribers(21).is_empty());
|
||||
assert_eq!(tracker.subscribers(22).len(), 1);
|
||||
tracker.remove_session(7, 3, 42);
|
||||
assert!(tracker.check_index_consistency().is_ok());
|
||||
assert!(check_index_consistency(&tracker).is_ok());
|
||||
assert!(
|
||||
tracker
|
||||
.routes
|
||||
|
|
@ -1076,7 +1019,7 @@ mod tests {
|
|||
assert_eq!(removed.iota_ids, vec![11]);
|
||||
assert_eq!(removed.session_user_ids, vec![7]);
|
||||
assert_eq!(removed.subscriber_sessions, vec![(7, 3)]);
|
||||
assert!(tracker.check_index_consistency().is_ok());
|
||||
assert!(check_index_consistency(&tracker).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1093,6 +1036,6 @@ mod tests {
|
|||
.unwrap()
|
||||
.clear();
|
||||
|
||||
assert!(tracker.check_index_consistency().is_err());
|
||||
assert!(check_index_consistency(&tracker).is_err());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
60
src/state.rs
60
src/state.rs
|
|
@ -1,7 +1,10 @@
|
|||
use crate::sql::user_online_tracker::PresenceTracker;
|
||||
use crate::{
|
||||
config::OmegaConfig, identity::OmegaIdentity, sql::user_online_tracker::PresenceTracker,
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AccountChallengeOperation {
|
||||
|
|
@ -11,28 +14,50 @@ pub enum AccountChallengeOperation {
|
|||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AccountChallenge {
|
||||
pub operation: AccountChallengeOperation,
|
||||
pub user_id: i64,
|
||||
pub requester_iota_id: i64,
|
||||
pub nonce: u64,
|
||||
pub created_at: Instant,
|
||||
}
|
||||
|
||||
pub struct OmegaState {
|
||||
pub presence: Arc<PresenceTracker>,
|
||||
pub identity: Arc<OmegaIdentity>,
|
||||
pub(crate) config: Arc<OmegaConfig>,
|
||||
pub(crate) global_handler_limit: Arc<Semaphore>,
|
||||
pub(crate) omikron_handler_concurrency: usize,
|
||||
challenges: DashMap<(AccountChallengeOperation, i64, i64), AccountChallenge>,
|
||||
}
|
||||
|
||||
impl Default for OmegaState {
|
||||
fn default() -> Self {
|
||||
impl OmegaState {
|
||||
fn with_handler_limits(identity: OmegaIdentity, config: OmegaConfig) -> Self {
|
||||
let omikron_handler_concurrency = config.dispatch.omikron_handler_concurrency;
|
||||
let global_handler_concurrency = config.dispatch.global_handler_concurrency;
|
||||
Self {
|
||||
presence: Arc::new(PresenceTracker::default()),
|
||||
identity: Arc::new(identity),
|
||||
config: Arc::new(config),
|
||||
global_handler_limit: Arc::new(Semaphore::new(global_handler_concurrency)),
|
||||
omikron_handler_concurrency,
|
||||
challenges: DashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl OmegaState {
|
||||
pub fn new(identity: OmegaIdentity, config: OmegaConfig) -> Arc<Self> {
|
||||
Arc::new(Self::with_handler_limits(identity, config))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn test_state() -> Arc<Self> {
|
||||
use mtp::crypto::Keyring;
|
||||
|
||||
let mut config = OmegaConfig::default();
|
||||
config.dispatch.omikron_handler_concurrency = 4;
|
||||
config.dispatch.global_handler_concurrency = 8;
|
||||
Arc::new(Self::with_handler_limits(
|
||||
OmegaIdentity::from_keyring(Keyring::generate()),
|
||||
config,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn issue_challenge(
|
||||
&self,
|
||||
operation: AccountChallengeOperation,
|
||||
|
|
@ -43,9 +68,6 @@ impl OmegaState {
|
|||
self.challenges.insert(
|
||||
(operation, user_id, requester_iota_id),
|
||||
AccountChallenge {
|
||||
operation,
|
||||
user_id,
|
||||
requester_iota_id,
|
||||
nonce,
|
||||
created_at: Instant::now(),
|
||||
},
|
||||
|
|
@ -68,12 +90,6 @@ impl OmegaState {
|
|||
}
|
||||
}
|
||||
|
||||
impl OmegaState {
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::OmegaState;
|
||||
|
|
@ -81,10 +97,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn state_instances_have_independent_presence_trackers() {
|
||||
let first = OmegaState::new();
|
||||
let second = OmegaState::new();
|
||||
let first = OmegaState::test_state();
|
||||
let second = OmegaState::test_state();
|
||||
|
||||
first.presence.track_iota_connection(11, 42, true);
|
||||
first.presence.connect_iota(11, 42);
|
||||
|
||||
assert!(first.presence.has_iota_route(11));
|
||||
assert!(!second.presence.has_iota_route(11));
|
||||
|
|
@ -94,10 +110,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn two_session_private_and_public_presence_flow_is_authoritative() {
|
||||
let state = OmegaState::new();
|
||||
let state = OmegaState::test_state();
|
||||
state.presence.set_preference(7, UserStatus::user_online);
|
||||
state.presence.set_preference(8, UserStatus::user_online);
|
||||
state.presence.track_iota_connection(11, 42, true);
|
||||
state.presence.connect_iota(11, 42);
|
||||
state.presence.track_session(7, 100, 42, 11);
|
||||
state.presence.track_session(7, 101, 42, 11);
|
||||
state.presence.replace_subscription(7, 100, 42, vec![8]);
|
||||
|
|
|
|||
|
|
@ -2,24 +2,94 @@ pub(crate) use super::omikron_connection::{OmikronConnection, OmikronResult};
|
|||
|
||||
use mtp::codec::{CommunicationValue, DataValue};
|
||||
|
||||
pub(crate) trait MtpValueCompat {
|
||||
fn get_id(&self) -> u32;
|
||||
fn get_sender(&self) -> u64;
|
||||
fn get_receiver(&self) -> u64;
|
||||
pub(crate) trait RequiredMtpFields {
|
||||
/// Require a nonzero request correlation ID. `None` and `Some(0)` are
|
||||
/// distinct MTP wire states, but both are invalid for Omega requests.
|
||||
fn require_id(&self) -> OmikronResult<u32>;
|
||||
/// Require a nonzero authenticated peer identity.
|
||||
fn require_sender(&self) -> OmikronResult<u64>;
|
||||
/// Require a nonzero application routing target.
|
||||
fn require_receiver(&self) -> OmikronResult<u64>;
|
||||
fn require_sender_i64(&self) -> OmikronResult<i64>;
|
||||
fn require_receiver_i64(&self) -> OmikronResult<i64>;
|
||||
}
|
||||
|
||||
impl MtpValueCompat for CommunicationValue {
|
||||
fn get_id(&self) -> u32 {
|
||||
self.id().unwrap_or_default()
|
||||
impl RequiredMtpFields for CommunicationValue {
|
||||
fn require_id(&self) -> OmikronResult<u32> {
|
||||
self.id().filter(|id| *id != 0).ok_or_else(|| {
|
||||
crate::OmegaError::Validation("MTP message is missing request id".into())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_sender(&self) -> u64 {
|
||||
self.sender().unwrap_or_default()
|
||||
fn require_sender(&self) -> OmikronResult<u64> {
|
||||
self.sender()
|
||||
.filter(|sender| *sender != 0)
|
||||
.ok_or_else(|| crate::OmegaError::Validation("MTP message is missing sender".into()))
|
||||
}
|
||||
|
||||
fn get_receiver(&self) -> u64 {
|
||||
self.receiver().unwrap_or_default()
|
||||
fn require_receiver(&self) -> OmikronResult<u64> {
|
||||
self.receiver()
|
||||
.filter(|receiver| *receiver != 0)
|
||||
.ok_or_else(|| crate::OmegaError::Validation("MTP message is missing receiver".into()))
|
||||
}
|
||||
|
||||
fn require_sender_i64(&self) -> OmikronResult<i64> {
|
||||
positive_i64(self.require_sender()?, "sender")
|
||||
}
|
||||
|
||||
fn require_receiver_i64(&self) -> OmikronResult<i64> {
|
||||
positive_i64(self.require_receiver()?, "receiver")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn positive_i64(value: impl TryInto<i128>, field: &str) -> OmikronResult<i64> {
|
||||
let value = value
|
||||
.try_into()
|
||||
.map_err(|_| crate::OmegaError::Validation(format!("invalid {field}")))?;
|
||||
let value = i64::try_from(value)
|
||||
.map_err(|_| crate::OmegaError::Validation(format!("invalid {field}")))?;
|
||||
if value <= 0 {
|
||||
return Err(crate::OmegaError::Validation(format!("invalid {field}")));
|
||||
}
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_dispatch_fields(value: &CommunicationValue) -> OmikronResult<()> {
|
||||
let Some(message_type) = value.get_comm_type_enum() else {
|
||||
return Err(crate::OmegaError::Validation(
|
||||
"MTP message has an unknown communication type".into(),
|
||||
));
|
||||
};
|
||||
|
||||
if !matches!(
|
||||
message_type,
|
||||
mtp::codec::CommunicationType::ClientChanged
|
||||
| mtp::codec::CommunicationType::PushNotification
|
||||
) {
|
||||
value.require_id()?;
|
||||
}
|
||||
|
||||
if matches!(
|
||||
message_type,
|
||||
mtp::codec::CommunicationType::GetUserData
|
||||
| mtp::codec::CommunicationType::ChangeUserData
|
||||
| mtp::codec::CommunicationType::ChangeIotaData
|
||||
| mtp::codec::CommunicationType::DeleteUser
|
||||
| mtp::codec::CommunicationType::AttachUserBegin
|
||||
| mtp::codec::CommunicationType::AttachUserComplete
|
||||
| mtp::codec::CommunicationType::DeleteUserCredentialBegin
|
||||
| mtp::codec::CommunicationType::DeleteUserCredentialComplete
|
||||
| mtp::codec::CommunicationType::EraseHostedUserDataAck
|
||||
| mtp::codec::CommunicationType::ReleaseUserFromIota
|
||||
| mtp::codec::CommunicationType::DeleteIota
|
||||
| mtp::codec::CommunicationType::GetNotifications
|
||||
| mtp::codec::CommunicationType::ReadNotification
|
||||
| mtp::codec::CommunicationType::StateSubscribe
|
||||
) {
|
||||
value.require_sender()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) trait OptionalDataValueCompat {
|
||||
|
|
@ -46,3 +116,65 @@ impl OptionalDataValueCompat for Option<&DataValue> {
|
|||
self.and_then(|value| value.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{RequiredMtpFields, validate_dispatch_fields};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||
|
||||
#[test]
|
||||
fn missing_request_id_is_rejected_without_a_zero_fallback() {
|
||||
let value = CommunicationValue::new(CommunicationType::GetUserData).without_id();
|
||||
assert!(value.require_id().is_err());
|
||||
assert!(validate_dispatch_fields(&value).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_routing_values_are_rejected_by_omega_contract() {
|
||||
let value = CommunicationValue::new(CommunicationType::GetUserData)
|
||||
.with_id(0)
|
||||
.with_sender(0)
|
||||
.with_receiver(0);
|
||||
|
||||
assert!(value.require_id().is_err());
|
||||
assert!(value.require_sender().is_err());
|
||||
assert!(value.require_receiver().is_err());
|
||||
assert!(validate_dispatch_fields(&value).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn security_sensitive_messages_require_sender() {
|
||||
for message_type in [
|
||||
CommunicationType::GetUserData,
|
||||
CommunicationType::ChangeUserData,
|
||||
CommunicationType::ChangeIotaData,
|
||||
CommunicationType::DeleteUser,
|
||||
CommunicationType::AttachUserBegin,
|
||||
CommunicationType::AttachUserComplete,
|
||||
CommunicationType::DeleteUserCredentialBegin,
|
||||
CommunicationType::DeleteUserCredentialComplete,
|
||||
CommunicationType::EraseHostedUserDataAck,
|
||||
CommunicationType::ReleaseUserFromIota,
|
||||
CommunicationType::DeleteIota,
|
||||
CommunicationType::GetNotifications,
|
||||
CommunicationType::ReadNotification,
|
||||
CommunicationType::StateSubscribe,
|
||||
] {
|
||||
let value = CommunicationValue::new(message_type).with_id(1);
|
||||
assert!(value.require_sender().is_err());
|
||||
assert!(validate_dispatch_fields(&value).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn optional_client_changed_id_remains_optional() {
|
||||
let value = CommunicationValue::new(CommunicationType::ClientChanged).without_id();
|
||||
assert!(validate_dispatch_fields(&value).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_notification_uses_its_logical_sender_field() {
|
||||
let value = CommunicationValue::new(CommunicationType::PushNotification);
|
||||
assert!(validate_dispatch_fields(&value).is_ok());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::super::connection::{
|
||||
MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat,
|
||||
OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields,
|
||||
};
|
||||
use crate::{
|
||||
db::{iota_repo, user_repo},
|
||||
|
|
@ -22,24 +22,30 @@ async fn delete(
|
|||
Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
|
||||
};
|
||||
connection.send(&response.with_id(value.get_id())).await
|
||||
connection
|
||||
.send(&response.with_id(value.require_id()?))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn user(
|
||||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let user_id = UserId::from(value.get_sender() as i64);
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
let user_id = UserId::from(value.require_sender_i64()?);
|
||||
complete_delete(connection, value, user_id).await
|
||||
}
|
||||
pub async fn iota(
|
||||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
delete(
|
||||
connection,
|
||||
value.clone(),
|
||||
iota_repo::delete_iota(IotaId::from(value.get_sender() as i64)),
|
||||
iota_repo::delete_iota(IotaId::from(value.require_sender_i64()?)),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
@ -48,6 +54,8 @@ pub async fn release_from_iota(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
let Some(user_id) = value
|
||||
.get_data(DataType::UserId)
|
||||
.as_signed_number()
|
||||
|
|
@ -55,18 +63,21 @@ pub async fn release_from_iota(
|
|||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId)
|
||||
.await;
|
||||
};
|
||||
let requester = IotaId::from(value.get_sender() as i64);
|
||||
let requester = IotaId::from(value.require_sender_i64()?);
|
||||
let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
};
|
||||
if user.iota_id != Some(requester) {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorNotAuthenticated,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let previous_iota = user.iota_id;
|
||||
|
|
@ -76,14 +87,17 @@ pub async fn release_from_iota(
|
|||
crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await;
|
||||
}
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::Success)
|
||||
.with_id(value.require_id()?),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(error) => {
|
||||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
|
||||
)
|
||||
.await
|
||||
|
|
@ -104,6 +118,8 @@ pub async fn attach_begin(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
let Some(user_id) = value
|
||||
.get_data(DataType::UserId)
|
||||
.as_signed_number()
|
||||
|
|
@ -111,7 +127,7 @@ pub async fn attach_begin(
|
|||
.filter(|v| *v > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId)
|
||||
.await;
|
||||
};
|
||||
if user_repo::get_by_user_id(UserId::from(user_id))
|
||||
|
|
@ -119,10 +135,10 @@ pub async fn attach_begin(
|
|||
.is_err()
|
||||
{
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
}
|
||||
let requester = value.get_sender() as i64;
|
||||
let requester = value.require_sender_i64()?;
|
||||
let nonce =
|
||||
connection
|
||||
.state()
|
||||
|
|
@ -130,7 +146,7 @@ pub async fn attach_begin(
|
|||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::AttachUserChallenge)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
|
||||
.add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())),
|
||||
)
|
||||
|
|
@ -141,6 +157,8 @@ pub async fn attach_complete(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
let Some(user_id) = value
|
||||
.get_data(DataType::UserId)
|
||||
.as_signed_number()
|
||||
|
|
@ -148,24 +166,30 @@ pub async fn attach_complete(
|
|||
.filter(|v| *v > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId)
|
||||
.await;
|
||||
};
|
||||
let requester = value.get_sender() as i64;
|
||||
let requester = value.require_sender_i64()?;
|
||||
let Some(nonce) = value
|
||||
.get_data(DataType::ServerNonce)
|
||||
.as_signed_number()
|
||||
.and_then(|v| u64::try_from(v).ok())
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidChallenge,
|
||||
)
|
||||
.await;
|
||||
};
|
||||
let signature = value.get_data(DataType::Signature).as_bytes();
|
||||
let pq_signature = value.get_data(DataType::PqSignature).as_bytes();
|
||||
let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidChallenge,
|
||||
)
|
||||
.await;
|
||||
};
|
||||
if !connection.state().consume_challenge(
|
||||
|
|
@ -175,12 +199,15 @@ pub async fn attach_complete(
|
|||
nonce,
|
||||
) {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidChallenge,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
};
|
||||
let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce);
|
||||
|
|
@ -188,7 +215,10 @@ pub async fn attach_complete(
|
|||
|| verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err()
|
||||
{
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorNotAuthenticated,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let previous_iota = user.iota_id;
|
||||
|
|
@ -199,12 +229,15 @@ pub async fn attach_complete(
|
|||
}
|
||||
crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await;
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::Success)
|
||||
.with_id(value.require_id()?),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(_) => {
|
||||
connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInternal)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInternal)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
|
@ -225,7 +258,7 @@ async fn complete_delete(
|
|||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::Success)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(
|
||||
DataType::CleanupPending,
|
||||
DataValue::Bool(cleanup_pending),
|
||||
|
|
@ -235,14 +268,14 @@ async fn complete_delete(
|
|||
}
|
||||
Err(crate::error::OmegaError::NotFound) => {
|
||||
connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNotFound)
|
||||
.await
|
||||
}
|
||||
Err(error) => {
|
||||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
|
||||
)
|
||||
.await
|
||||
|
|
@ -254,6 +287,8 @@ pub async fn delete_credential_begin(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
let Some(user_id) = value
|
||||
.get_data(DataType::UserId)
|
||||
.as_signed_number()
|
||||
|
|
@ -261,7 +296,7 @@ pub async fn delete_credential_begin(
|
|||
.filter(|v| *v > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId)
|
||||
.await;
|
||||
};
|
||||
if user_repo::get_by_user_id(UserId::from(user_id))
|
||||
|
|
@ -269,10 +304,10 @@ pub async fn delete_credential_begin(
|
|||
.is_err()
|
||||
{
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
}
|
||||
let requester = value.get_sender() as i64;
|
||||
let requester = value.require_sender_i64()?;
|
||||
let nonce =
|
||||
connection
|
||||
.state()
|
||||
|
|
@ -280,7 +315,7 @@ pub async fn delete_credential_begin(
|
|||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
|
||||
.add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into())),
|
||||
)
|
||||
|
|
@ -291,6 +326,8 @@ pub async fn delete_credential_complete(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
let Some(user_id) = value
|
||||
.get_data(DataType::UserId)
|
||||
.as_signed_number()
|
||||
|
|
@ -298,17 +335,20 @@ pub async fn delete_credential_complete(
|
|||
.filter(|v| *v > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId)
|
||||
.await;
|
||||
};
|
||||
let requester = value.get_sender() as i64;
|
||||
let requester = value.require_sender_i64()?;
|
||||
let Some(nonce) = value
|
||||
.get_data(DataType::ServerNonce)
|
||||
.as_signed_number()
|
||||
.and_then(|v| u64::try_from(v).ok())
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidChallenge,
|
||||
)
|
||||
.await;
|
||||
};
|
||||
let (Some(signature), Some(pq_signature)) = (
|
||||
|
|
@ -316,7 +356,10 @@ pub async fn delete_credential_complete(
|
|||
value.get_data(DataType::PqSignature).as_bytes(),
|
||||
) else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidChallenge,
|
||||
)
|
||||
.await;
|
||||
};
|
||||
if !connection.state().consume_challenge(
|
||||
|
|
@ -326,12 +369,15 @@ pub async fn delete_credential_complete(
|
|||
nonce,
|
||||
) {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidChallenge,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
};
|
||||
let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce);
|
||||
|
|
@ -339,7 +385,10 @@ pub async fn delete_credential_complete(
|
|||
|| verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err()
|
||||
{
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorNotAuthenticated,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
complete_delete(connection, value, user.id).await
|
||||
|
|
@ -349,6 +398,8 @@ pub async fn erase_hosted_user_data_ack(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
value.require_id()?;
|
||||
value.require_sender_i64()?;
|
||||
let Some(user_id) = value
|
||||
.get_data(DataType::UserId)
|
||||
.as_signed_number()
|
||||
|
|
@ -356,45 +407,31 @@ pub async fn erase_hosted_user_data_ack(
|
|||
.filter(|v| *v > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidUserId)
|
||||
.await;
|
||||
};
|
||||
let iota_id = IotaId::from(value.get_sender() as i64);
|
||||
let iota_id = IotaId::from(value.require_sender_i64()?);
|
||||
match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await {
|
||||
Ok(true) => {
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::Success)
|
||||
.with_id(value.require_id()?),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Ok(false) => {
|
||||
connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated)
|
||||
.send_error_response(
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorNotAuthenticated,
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(_) => {
|
||||
connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInternal)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInternal)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// New lifecycle operation names are intentionally fail-closed until their
|
||||
/// proof and durable-erasure handlers are enabled. This explicit dispatch
|
||||
/// prevents either a bare Iota request or the legacy DeleteUser path from
|
||||
/// acquiring account-deletion authority during a staged rollout.
|
||||
pub async fn lifecycle_unavailable(
|
||||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::ErrorNotAuthenticated)
|
||||
.with_id(value.get_id())
|
||||
.add_typed_default(
|
||||
DataType::ErrorType,
|
||||
DataValue::Str("user lifecycle proof handler is not enabled".into()),
|
||||
),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::super::connection::{
|
||||
MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat,
|
||||
OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields,
|
||||
};
|
||||
use crate::server::short_link::add_short_link;
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
|
|
@ -17,7 +17,7 @@ pub async fn shorten(
|
|||
.await
|
||||
.map_err(|_| crate::error::OmegaError::Transport("short link error".to_string()))?;
|
||||
let response = CommunicationValue::new(CommunicationType::ShortenLink)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::Link, DataValue::Str(short));
|
||||
connection.send(&response).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::super::connection::{
|
||||
MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat,
|
||||
OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields,
|
||||
};
|
||||
use crate::{db::notification_repo, log, models::UserId};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
|
|
@ -10,35 +10,36 @@ pub async fn get(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let notifications =
|
||||
match notification_repo::get_notifications(UserId::from(value.get_sender() as i64)).await {
|
||||
Ok(items) => items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let tm = TypeMap::latest();
|
||||
let Some(sender) = DataType::SenderId.try_to_id(&tm) else {
|
||||
return DataValue::Container(Vec::new());
|
||||
};
|
||||
let Some(amount) = DataType::Amount.try_to_id(&tm) else {
|
||||
return DataValue::Container(Vec::new());
|
||||
};
|
||||
DataValue::Container(vec![
|
||||
(sender, DataValue::SignedNumber(item.sender_id.0.into())),
|
||||
(amount, DataValue::SignedNumber(item.amount.into())),
|
||||
])
|
||||
})
|
||||
.collect(),
|
||||
Err(error) => {
|
||||
log!(
|
||||
crate::util::logger::PrintType::General,
|
||||
"SQL get_notifications error: {}",
|
||||
error
|
||||
);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
let request_id = value.require_id()?;
|
||||
let sender = value.require_sender_i64()?;
|
||||
let notifications = match notification_repo::get_notifications(UserId::from(sender)).await {
|
||||
Ok(items) => items
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let tm = TypeMap::latest();
|
||||
let Some(sender) = DataType::SenderId.try_to_id(&tm) else {
|
||||
return DataValue::Container(Vec::new());
|
||||
};
|
||||
let Some(amount) = DataType::Amount.try_to_id(&tm) else {
|
||||
return DataValue::Container(Vec::new());
|
||||
};
|
||||
DataValue::Container(vec![
|
||||
(sender, DataValue::SignedNumber(item.sender_id.0.into())),
|
||||
(amount, DataValue::SignedNumber(item.amount.into())),
|
||||
])
|
||||
})
|
||||
.collect(),
|
||||
Err(error) => {
|
||||
log!(
|
||||
crate::util::logger::PrintType::General,
|
||||
"SQL get_notifications error: {}",
|
||||
error
|
||||
);
|
||||
Vec::new()
|
||||
}
|
||||
};
|
||||
let response = CommunicationValue::new(CommunicationType::GetNotifications)
|
||||
.with_id(value.get_id())
|
||||
.with_id(request_id)
|
||||
.add_typed_default(DataType::Notifications, DataValue::Array(notifications));
|
||||
connection.send(&response).await
|
||||
}
|
||||
|
|
@ -47,19 +48,17 @@ pub async fn read(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let receiver = match value.get_sender() {
|
||||
sender if sender > 0 => sender as i64,
|
||||
_ => match value.get_data(DataType::ReceiverId).as_number() {
|
||||
Some(id) => id as i64,
|
||||
None => return Ok(()),
|
||||
},
|
||||
};
|
||||
let request_id = value.require_id()?;
|
||||
let receiver = value.require_sender_i64()?;
|
||||
let Some(other) = value
|
||||
.get_data(DataType::SenderId)
|
||||
.as_number()
|
||||
.map(|id| id as i64)
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return Ok(());
|
||||
return connection
|
||||
.send_error_response(request_id, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
if let Err(error) =
|
||||
notification_repo::read_notification(UserId::from(receiver), UserId::from(other)).await
|
||||
|
|
@ -71,7 +70,7 @@ pub async fn read(
|
|||
);
|
||||
} else {
|
||||
let response =
|
||||
CommunicationValue::new(CommunicationType::ReadNotification).with_id(value.get_id());
|
||||
CommunicationValue::new(CommunicationType::ReadNotification).with_id(request_id);
|
||||
let _ = connection.send(&response).await;
|
||||
let sync = CommunicationValue::new(CommunicationType::ReadNotification)
|
||||
.with_receiver(receiver as u64)
|
||||
|
|
@ -85,18 +84,54 @@ pub async fn push(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let receiver = match value.get_receiver() {
|
||||
receiver if receiver > 0 => receiver as i64,
|
||||
_ => match value.get_data(DataType::ReceiverId).as_number() {
|
||||
Some(id) => id as i64,
|
||||
None => return Ok(()),
|
||||
},
|
||||
let request_id = value.id().filter(|id| *id != 0);
|
||||
let receiver = value.require_receiver_i64().ok().or_else(|| {
|
||||
value
|
||||
.get_data(DataType::ReceiverId)
|
||||
.as_number()
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| *id > 0)
|
||||
});
|
||||
let Some(receiver) = receiver else {
|
||||
if let Some(request_id) = request_id {
|
||||
return connection
|
||||
.send_error_response(request_id, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
let sender = value
|
||||
let Some(sender) = value
|
||||
.get_data(DataType::SenderId)
|
||||
.as_number()
|
||||
.map(|id| id as i64)
|
||||
.unwrap_or(value.get_sender() as i64);
|
||||
.and_then(|sender| i64::try_from(sender).ok())
|
||||
.filter(|sender| *sender > 0)
|
||||
else {
|
||||
if let Some(request_id) = request_id {
|
||||
return connection
|
||||
.send_error_response(request_id, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
let source_omikron = connection
|
||||
.clone()
|
||||
.get_omikron_id()
|
||||
.await
|
||||
.ok_or(crate::OmegaError::NotConnected)?;
|
||||
if !connection
|
||||
.state()
|
||||
.presence
|
||||
.routes_for_user(sender)
|
||||
.iter()
|
||||
.any(|route| route.omikron_id == source_omikron)
|
||||
{
|
||||
if let Some(request_id) = request_id {
|
||||
return connection
|
||||
.send_error_response(request_id, CommunicationType::ErrorNotAuthenticated)
|
||||
.await;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
if let Err(error) =
|
||||
notification_repo::add_notification(UserId::from(receiver), UserId::from(sender)).await
|
||||
{
|
||||
|
|
@ -106,9 +141,11 @@ pub async fn push(
|
|||
error
|
||||
);
|
||||
} else {
|
||||
let response =
|
||||
CommunicationValue::new(CommunicationType::PushNotification).with_id(value.get_id());
|
||||
let _ = connection.send(&response).await;
|
||||
if let Some(request_id) = request_id {
|
||||
let response =
|
||||
CommunicationValue::new(CommunicationType::PushNotification).with_id(request_id);
|
||||
let _ = connection.send(&response).await;
|
||||
}
|
||||
let push = CommunicationValue::new(CommunicationType::PushNotification)
|
||||
.with_receiver(receiver as u64)
|
||||
.add_typed_default(DataType::SenderId, DataValue::SignedNumber(sender.into()));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::super::connection::{
|
||||
MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat,
|
||||
OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields,
|
||||
};
|
||||
use crate::{
|
||||
db::user_repo, log_in, models::IotaId, sql::connection_status::UserStatus, state::OmegaState,
|
||||
|
|
@ -11,7 +11,10 @@ use std::{
|
|||
};
|
||||
|
||||
fn parse_subscription(value: &CommunicationValue) -> Result<(i64, i64, Vec<i64>), &'static str> {
|
||||
let user_id = i64::try_from(value.get_sender())
|
||||
let Some(sender) = value.sender() else {
|
||||
return Err("user_id");
|
||||
};
|
||||
let user_id = i64::try_from(sender)
|
||||
.ok()
|
||||
.filter(|id| *id > 0)
|
||||
.ok_or("user_id")?;
|
||||
|
|
@ -51,13 +54,11 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa
|
|||
users
|
||||
.iter()
|
||||
.map(|user| {
|
||||
(
|
||||
user.id.0,
|
||||
state.presence.resolve_public_state(
|
||||
user.id.0,
|
||||
user.iota_id.map(|id| id.0).unwrap_or_default(),
|
||||
),
|
||||
)
|
||||
let status = user
|
||||
.iota_id
|
||||
.map(|iota_id| state.presence.resolve_public_state(user.id.0, iota_id.0))
|
||||
.unwrap_or(UserStatus::user_offline);
|
||||
(user.id.0, status)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -70,9 +71,10 @@ fn changed_states(
|
|||
let mut changes = users
|
||||
.iter()
|
||||
.filter_map(|user| {
|
||||
let after = state
|
||||
.presence
|
||||
.resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default());
|
||||
let after = user
|
||||
.iota_id
|
||||
.map(|iota_id| state.presence.resolve_public_state(user.id.0, iota_id.0))
|
||||
.unwrap_or(UserStatus::user_offline);
|
||||
(before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after))
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
|
@ -96,21 +98,6 @@ fn state_notification(
|
|||
.add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string()))
|
||||
}
|
||||
|
||||
fn private_state_notification(
|
||||
user_id: i64,
|
||||
session_id: i64,
|
||||
user_state: &UserStatus,
|
||||
) -> CommunicationValue {
|
||||
CommunicationValue::new(CommunicationType::ClientChanged)
|
||||
.with_receiver(user_id as u64)
|
||||
.add_typed_default(
|
||||
DataType::SessionId,
|
||||
DataValue::SignedNumber(session_id.into()),
|
||||
)
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
|
||||
.add_typed_default(DataType::UserState, DataValue::Str(user_state.to_string()))
|
||||
}
|
||||
|
||||
async fn publish_state_changes(state: &OmegaState, changes: &[(i64, UserStatus)]) {
|
||||
let mut grouped = BTreeMap::<i64, Vec<CommunicationValue>>::new();
|
||||
for (user_id, user_state) in changes {
|
||||
|
|
@ -143,28 +130,6 @@ async fn publish_changed_states(
|
|||
publish_state_changes(state, &changed_states(state, before, users)).await;
|
||||
}
|
||||
|
||||
async fn publish_private_state(state: &OmegaState, user_id: i64, user_state: &UserStatus) {
|
||||
let mut grouped = BTreeMap::<i64, Vec<CommunicationValue>>::new();
|
||||
for (session_id, route) in state.presence.sessions_for_user(user_id) {
|
||||
grouped
|
||||
.entry(route.omikron_id)
|
||||
.or_default()
|
||||
.push(private_state_notification(user_id, session_id, user_state));
|
||||
}
|
||||
for (omikron_id, notifications) in grouped {
|
||||
if let Err(error) =
|
||||
crate::transport::omikron_manager::send_state_batch(omikron_id, notifications).await
|
||||
{
|
||||
log_in!(
|
||||
crate::util::logger::PrintType::General,
|
||||
"Failed to deliver private presence state batch to Omikron {}: {}",
|
||||
omikron_id,
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn state_subscribe(
|
||||
state: Arc<OmegaState>,
|
||||
connection: Arc<OmikronConnection>,
|
||||
|
|
@ -175,13 +140,13 @@ pub async fn state_subscribe(
|
|||
Ok(subscription) => subscription,
|
||||
Err("user_id") => {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoUserId)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNoUserId)
|
||||
.await;
|
||||
}
|
||||
Err(detail) => {
|
||||
return connection
|
||||
.send_error_response_with_detail(
|
||||
value.get_id(),
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidData,
|
||||
detail,
|
||||
)
|
||||
|
|
@ -190,14 +155,14 @@ pub async fn state_subscribe(
|
|||
};
|
||||
if !state.presence.owns_session(user_id, session_id, omikron_id) {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoIota)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNoIota)
|
||||
.await;
|
||||
}
|
||||
state
|
||||
.presence
|
||||
.replace_subscription(user_id, session_id, omikron_id, user_ids);
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -242,7 +207,7 @@ pub async fn user_connected(
|
|||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let Some(session_id) = value
|
||||
|
|
@ -252,7 +217,7 @@ pub async fn user_connected(
|
|||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let Some(iota_id) = value
|
||||
|
|
@ -262,25 +227,25 @@ pub async fn user_connected(
|
|||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let user = match user_repo::get_by_user_id(user_id.into()).await {
|
||||
Ok(user) => user,
|
||||
Err(crate::error::OmegaError::NotFound) => {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
}
|
||||
Err(error) => return Err(error.into()),
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
let preferences = match user_repo::get_presence_preferences(&[user_id]).await {
|
||||
Ok(preferences) => preferences,
|
||||
Err(error) => return Err(error.into()),
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
if user.iota_id.map(|id| id.0) != Some(iota_id) || !state.presence.has_iota_route(iota_id) {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoIota)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorNoIota)
|
||||
.await;
|
||||
}
|
||||
apply_preferences(&state, preferences);
|
||||
|
|
@ -291,7 +256,7 @@ pub async fn user_connected(
|
|||
.track_session(user_id, session_id, omikron_id, iota_id);
|
||||
publish_changed_states(&state, &before, &users).await;
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -309,7 +274,7 @@ pub async fn user_disconnected(
|
|||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let Some(session_id) = value
|
||||
|
|
@ -319,7 +284,7 @@ pub async fn user_disconnected(
|
|||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
if let Ok(user) = user_repo::get_by_user_id(user_id.into()).await {
|
||||
|
|
@ -337,137 +302,7 @@ pub async fn user_disconnected(
|
|||
.remove_session(user_id, session_id, omikron_id);
|
||||
}
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn set_user_state(
|
||||
state: Arc<OmegaState>,
|
||||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
omikron_id: i64,
|
||||
) -> OmikronResult<()> {
|
||||
let Some(user_id) = i64::try_from(value.get_sender()).ok().filter(|id| *id > 0) else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoUserId)
|
||||
.await;
|
||||
};
|
||||
if let Some(requested_user) = value.get_data(DataType::UserId) {
|
||||
let Some(requested_user_id) = requested_user
|
||||
.as_number()
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
else {
|
||||
return connection
|
||||
.send_error_response_with_detail(
|
||||
value.get_id(),
|
||||
CommunicationType::ErrorInvalidData,
|
||||
"user_id",
|
||||
)
|
||||
.await;
|
||||
};
|
||||
if requested_user_id != user_id {
|
||||
return connection
|
||||
.send_error_response_with_detail(
|
||||
value.get_id(),
|
||||
CommunicationType::ErrorInvalidData,
|
||||
"user_id",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
let Some(iota_id) = value
|
||||
.get_data(DataType::IotaId)
|
||||
.as_number()
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
else {
|
||||
return connection
|
||||
.send_error_response_with_detail(
|
||||
value.get_id(),
|
||||
CommunicationType::ErrorInvalidData,
|
||||
"iota_id",
|
||||
)
|
||||
.await;
|
||||
};
|
||||
let Some(requested_state) = value
|
||||
.get_data(DataType::UserState)
|
||||
.as_str()
|
||||
.and_then(UserStatus::from_client_preference)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response_with_detail(
|
||||
value.get_id(),
|
||||
CommunicationType::ErrorInvalidData,
|
||||
"user_state",
|
||||
)
|
||||
.await;
|
||||
};
|
||||
if !state.presence.has_iota_route(iota_id) {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoIota)
|
||||
.await;
|
||||
}
|
||||
let Some(session_id) = value
|
||||
.get_data(DataType::SessionId)
|
||||
.as_number()
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response_with_detail(
|
||||
value.get_id(),
|
||||
CommunicationType::ErrorInvalidData,
|
||||
"session_id",
|
||||
)
|
||||
.await;
|
||||
};
|
||||
let Some(route) = state.presence.session_route(user_id, session_id) else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoIota)
|
||||
.await;
|
||||
};
|
||||
if route.omikron_id != omikron_id || route.iota_id != iota_id {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
}
|
||||
if !state.presence.has_active_session_for_iota(user_id, iota_id) {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoIota)
|
||||
.await;
|
||||
}
|
||||
let previous_preference = state.presence.preference(user_id);
|
||||
let previous_state = state.presence.resolve_public_state(user_id, iota_id);
|
||||
if let Err(error) =
|
||||
user_repo::change_presence_preference(user_id.into(), requested_state.to_string()).await
|
||||
{
|
||||
log_in!(
|
||||
crate::util::logger::PrintType::General,
|
||||
"Failed to persist presence preference: {}",
|
||||
error
|
||||
);
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInternal)
|
||||
.await;
|
||||
}
|
||||
state
|
||||
.presence
|
||||
.set_preference(user_id, requested_state.clone());
|
||||
let new_state = state.presence.resolve_public_state(user_id, iota_id);
|
||||
if requested_state != previous_preference {
|
||||
publish_private_state(&state, user_id, &requested_state).await;
|
||||
}
|
||||
if requested_state != previous_preference && new_state != previous_state {
|
||||
publish_state_changes(&state, &[(user_id, new_state)]).await;
|
||||
}
|
||||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::Success)
|
||||
.with_id(value.get_id())
|
||||
.add_typed_default(
|
||||
DataType::UserState,
|
||||
DataValue::Str(requested_state.to_string()),
|
||||
),
|
||||
)
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -481,10 +316,11 @@ pub async fn iota_connected(
|
|||
let Some(iota_id) = value
|
||||
.get_data(DataType::IotaId)
|
||||
.as_number()
|
||||
.map(|id| id as i64)
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?;
|
||||
|
|
@ -503,7 +339,7 @@ pub async fn iota_connected(
|
|||
crate::transport::omikron_manager::deliver_pending_erasures(iota_id).await;
|
||||
publish_changed_states(&state, &before, &users).await;
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -517,10 +353,11 @@ pub async fn iota_disconnected(
|
|||
let Some(iota_id) = value
|
||||
.get_data(DataType::IotaId)
|
||||
.as_number()
|
||||
.map(|id| id as i64)
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| *id > 0)
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let users = user_repo::get_users_by_iota_id(IotaId::from(iota_id)).await?;
|
||||
|
|
@ -530,7 +367,7 @@ pub async fn iota_disconnected(
|
|||
state.presence.untrack_iota_connection(iota_id, omikron_id);
|
||||
publish_changed_states(&state, &before, &users).await;
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.require_id()?))
|
||||
.await
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +377,7 @@ pub async fn sync_status(
|
|||
value: CommunicationValue,
|
||||
omikron_id: i64,
|
||||
) -> OmikronResult<()> {
|
||||
let request_id = value.get_id();
|
||||
let request_id = value.require_id()?;
|
||||
let Some(DataValue::Array(iota_values)) = value.get_data(DataType::IotaIds) else {
|
||||
return connection
|
||||
.send_error_response(request_id, CommunicationType::ErrorInvalidData)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::super::connection::{
|
||||
MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat,
|
||||
OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields,
|
||||
};
|
||||
use crate::{
|
||||
db::{iota_repo, user_repo},
|
||||
|
|
@ -22,13 +22,13 @@ pub async fn get_register(
|
|||
.filter(|id| user_repo::valid_protocol_id(*id));
|
||||
let Some(iota_id) = iota_id else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let (register_id, registration_token) =
|
||||
user_repo::allocate_registration(IotaId::from(iota_id), value.get_id()).await?;
|
||||
user_repo::allocate_registration(IotaId::from(iota_id), value.require_id()?).await?;
|
||||
let response = CommunicationValue::new(CommunicationType::GetRegister)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(
|
||||
DataType::UserId,
|
||||
DataValue::SignedNumber(register_id.0.into()),
|
||||
|
|
@ -47,7 +47,7 @@ pub async fn complete_iota(
|
|||
.and_then(|key| PublicKeyBundle::from_base64(key).ok());
|
||||
let Some(public_key) = public_key else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
match iota_repo::create_new_iota(public_key).await {
|
||||
|
|
@ -55,7 +55,7 @@ pub async fn complete_iota(
|
|||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::CompleteRegisterIota)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.0.into())),
|
||||
)
|
||||
.await
|
||||
|
|
@ -64,7 +64,7 @@ pub async fn complete_iota(
|
|||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
|
||||
)
|
||||
.await
|
||||
|
|
@ -108,7 +108,7 @@ pub async fn complete_user(
|
|||
})
|
||||
else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
// Omikron supplies the authenticated Iota ID in the payload. The lease
|
||||
|
|
@ -120,7 +120,7 @@ pub async fn complete_user(
|
|||
.filter(|id| user_repo::valid_protocol_id(*id));
|
||||
let Some(iota_id) = iota_id else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
|
||||
|
|
@ -136,14 +136,17 @@ pub async fn complete_user(
|
|||
{
|
||||
Ok(()) => {
|
||||
connection
|
||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::Success)
|
||||
.with_id(value.require_id()?),
|
||||
)
|
||||
.await
|
||||
}
|
||||
Err(error) => {
|
||||
connection
|
||||
.send(
|
||||
&CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
|
||||
)
|
||||
.await
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::super::connection::{
|
||||
MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat,
|
||||
OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields,
|
||||
};
|
||||
use crate::db::user_repo;
|
||||
use mtp::{
|
||||
|
|
@ -34,7 +34,7 @@ pub async fn get(
|
|||
let Some(DataValue::Array(ids)) = value.get_data(DataType::UserIds) else {
|
||||
return send_error(
|
||||
connection,
|
||||
value.get_id(),
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidData,
|
||||
None,
|
||||
)
|
||||
|
|
@ -47,7 +47,7 @@ pub async fn get(
|
|||
if session_id.is_none() && !legacy_peer {
|
||||
return send_error(
|
||||
connection,
|
||||
value.get_id(),
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidData,
|
||||
None,
|
||||
)
|
||||
|
|
@ -60,7 +60,7 @@ pub async fn get(
|
|||
let DataValue::SignedNumber(id) = id else {
|
||||
return send_error(
|
||||
connection,
|
||||
value.get_id(),
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidData,
|
||||
session_id,
|
||||
)
|
||||
|
|
@ -69,7 +69,7 @@ pub async fn get(
|
|||
let Ok(user_id) = i64::try_from(*id) else {
|
||||
return send_error(
|
||||
connection,
|
||||
value.get_id(),
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidData,
|
||||
session_id,
|
||||
)
|
||||
|
|
@ -78,7 +78,7 @@ pub async fn get(
|
|||
if user_id <= 0 {
|
||||
return send_error(
|
||||
connection,
|
||||
value.get_id(),
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInvalidData,
|
||||
session_id,
|
||||
)
|
||||
|
|
@ -95,7 +95,7 @@ pub async fn get(
|
|||
Err(_) => {
|
||||
return send_error(
|
||||
connection,
|
||||
value.get_id(),
|
||||
value.require_id()?,
|
||||
CommunicationType::ErrorInternal,
|
||||
session_id,
|
||||
)
|
||||
|
|
@ -110,9 +110,10 @@ pub async fn get(
|
|||
missing_user_ids.push(user_id);
|
||||
continue;
|
||||
};
|
||||
let status = state
|
||||
.presence
|
||||
.resolve_public_state(user_id, user.iota_id.map(|id| id.0).unwrap_or_default())
|
||||
let status = user
|
||||
.iota_id
|
||||
.map(|iota_id| state.presence.resolve_public_state(user_id, iota_id.0))
|
||||
.unwrap_or(crate::sql::connection_status::UserStatus::user_offline)
|
||||
.to_string();
|
||||
let mut map = Vec::new();
|
||||
if let Some(kind) = DataType::UserId.try_to_id(&tm) {
|
||||
|
|
@ -124,7 +125,7 @@ pub async fn get(
|
|||
states.push(DataValue::Container(map));
|
||||
}
|
||||
let response = CommunicationValue::new(CommunicationType::GetStates)
|
||||
.with_id(value.get_id())
|
||||
.with_id(value.require_id()?)
|
||||
.add_typed_default(DataType::UserStates, DataValue::Array(states));
|
||||
let response = if let Some(session_id) = session_id {
|
||||
response.add_typed_default(DataType::SessionId, DataValue::SignedNumber(session_id))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use super::super::connection::{
|
||||
MtpValueCompat, OmikronConnection, OmikronResult, OptionalDataValueCompat,
|
||||
OmikronConnection, OmikronResult, OptionalDataValueCompat, RequiredMtpFields,
|
||||
};
|
||||
use crate::{
|
||||
db::{iota_repo, user_repo},
|
||||
|
|
@ -29,11 +29,16 @@ pub async fn get_user(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let request_id = value.require_id()?;
|
||||
let sender = value.require_sender_i64()?;
|
||||
let state = connection.state();
|
||||
let user = if let Some(id) = value.get_data(DataType::UserId).as_number() {
|
||||
user_repo::get_by_user_id(UserId::from(id as i64))
|
||||
.await
|
||||
.ok()
|
||||
let user = if let Some(id) = value
|
||||
.get_data(DataType::UserId)
|
||||
.as_number()
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| *id > 0)
|
||||
{
|
||||
user_repo::get_by_user_id(UserId::from(id)).await.ok()
|
||||
} else if let Some(name) = value.get_data(DataType::Username).as_str() {
|
||||
user_repo::get_by_username(name).await.ok()
|
||||
} else {
|
||||
|
|
@ -41,7 +46,7 @@ pub async fn get_user(
|
|||
};
|
||||
let Some(user) = user else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(request_id, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
};
|
||||
let id = user.id.0;
|
||||
|
|
@ -52,11 +57,11 @@ pub async fn get_user(
|
|||
.filter(|name| !name.is_empty())
|
||||
.unwrap_or_else(|| username.clone());
|
||||
let mut response = CommunicationValue::new(CommunicationType::GetUserData)
|
||||
.with_id(value.get_id())
|
||||
.with_id(request_id)
|
||||
.add_typed_default(DataType::Username, DataValue::Str(username))
|
||||
.add_typed_default(
|
||||
DataType::PublicKey,
|
||||
DataValue::Str(user.public_key.to_base64()),
|
||||
DataValue::Str(user.public_key.try_to_base64()?),
|
||||
)
|
||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into()))
|
||||
.add_typed_default(DataType::Display, DataValue::Str(display))
|
||||
|
|
@ -79,7 +84,7 @@ pub async fn get_user(
|
|||
response.add_typed_default(DataType::Avatar, DataValue::Str(STANDARD.encode(avatar)));
|
||||
}
|
||||
let route = state.presence.user_route(id);
|
||||
let private_request = value.get_sender() as i64 == id;
|
||||
let private_request = sender == id;
|
||||
let resolved_status = if private_request {
|
||||
if !state
|
||||
.presence
|
||||
|
|
@ -125,13 +130,21 @@ pub async fn get_iota(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let found = if let Some(id) = value.get_data(DataType::IotaId).as_number() {
|
||||
iota_repo::get_iota_by_id(IotaId::from(id as i64))
|
||||
let request_id = value.require_id()?;
|
||||
let found = if let Some(id) = value
|
||||
.get_data(DataType::IotaId)
|
||||
.as_number()
|
||||
.and_then(|id| i64::try_from(id).ok())
|
||||
.filter(|id| *id > 0)
|
||||
{
|
||||
iota_repo::get_iota_by_id(IotaId::from(id))
|
||||
.await
|
||||
.ok()
|
||||
.map(|iota| (iota.id.0, iota.public_key, None, None))
|
||||
} else if let Some(id) = value.get_data(DataType::UserId).as_number() {
|
||||
if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await {
|
||||
if let Some(id) = i64::try_from(id).ok().filter(|id| *id > 0)
|
||||
&& let Ok(user) = user_repo::get_by_user_id(UserId::from(id)).await
|
||||
{
|
||||
match user.iota_id {
|
||||
Some(iota_id) => iota_repo::get_iota_by_id(iota_id)
|
||||
.await
|
||||
|
|
@ -163,12 +176,12 @@ pub async fn get_iota(
|
|||
};
|
||||
let Some((id, key, user_id, username)) = found else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(request_id, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
};
|
||||
let mut response = CommunicationValue::new(CommunicationType::GetIotaData)
|
||||
.with_id(value.get_id())
|
||||
.add_typed_default(DataType::PublicKey, DataValue::Str(key.to_base64()))
|
||||
.with_id(request_id)
|
||||
.add_typed_default(DataType::PublicKey, DataValue::Str(key.try_to_base64()?))
|
||||
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(id.into()))
|
||||
.add_typed_default(DataType::OmikronConnections, connections(&connection, id));
|
||||
if let Some(user_id) = user_id {
|
||||
|
|
@ -185,7 +198,8 @@ async fn update_user(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let id = UserId::from(value.get_sender() as i64);
|
||||
let request_id = value.require_id()?;
|
||||
let id = UserId::from(value.require_sender_i64()?);
|
||||
let mut error = None;
|
||||
if let Some(name) = value.get_data(DataType::Username).as_str() {
|
||||
error = user_repo::change_username(id, name.to_owned())
|
||||
|
|
@ -193,56 +207,55 @@ async fn update_user(
|
|||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none() {
|
||||
if let Some(name) = value.get_data(DataType::Display).as_str() {
|
||||
error = user_repo::change_display_name(id, name.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none()
|
||||
&& let Some(name) = value.get_data(DataType::Display).as_str()
|
||||
{
|
||||
error = user_repo::change_display_name(id, name.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none() {
|
||||
if let Some(avatar) = value.get_data(DataType::Avatar).as_str() {
|
||||
error = user_repo::change_avatar(id, avatar.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none()
|
||||
&& let Some(avatar) = value.get_data(DataType::Avatar).as_str()
|
||||
{
|
||||
error = user_repo::change_avatar(id, avatar.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none() {
|
||||
if let Some(about) = value.get_data(DataType::About).as_str() {
|
||||
error = user_repo::change_about(id, about.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none()
|
||||
&& let Some(about) = value.get_data(DataType::About).as_str()
|
||||
{
|
||||
error = user_repo::change_about(id, about.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none() {
|
||||
if let Some(status) = value.get_data(DataType::Status).as_str() {
|
||||
error = user_repo::change_status(id, status.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none()
|
||||
&& let Some(status) = value.get_data(DataType::Status).as_str()
|
||||
{
|
||||
error = user_repo::change_status(id, status.to_owned())
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
if error.is_none() {
|
||||
if let Some(key) = value
|
||||
if error.is_none()
|
||||
&& let Some(key) = value
|
||||
.get_data(DataType::PublicKey)
|
||||
.as_str()
|
||||
.and_then(|key| PublicKeyBundle::from_base64(key).ok())
|
||||
{
|
||||
error = user_repo::change_keys(id, key)
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
{
|
||||
error = user_repo::change_keys(id, key)
|
||||
.await
|
||||
.err()
|
||||
.map(|e| e.to_string());
|
||||
}
|
||||
let response = match error {
|
||||
None => CommunicationValue::new(CommunicationType::Success),
|
||||
Some(error) => CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error)),
|
||||
};
|
||||
connection.send(&response.with_id(value.get_id())).await
|
||||
connection.send(&response.with_id(request_id)).await
|
||||
}
|
||||
|
||||
pub async fn change_user(
|
||||
|
|
@ -256,34 +269,35 @@ pub async fn change_iota(
|
|||
connection: Arc<OmikronConnection>,
|
||||
value: CommunicationValue,
|
||||
) -> OmikronResult<()> {
|
||||
let request_id = value.require_id()?;
|
||||
let reset_data = value.get_data(DataType::ResetToken);
|
||||
let Some(reset) = reset_data.as_str() else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let new_token_data = value.get_data(DataType::NewToken);
|
||||
let Some(new_token) = new_token_data.as_str() else {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidData)
|
||||
.send_error_response(value.require_id()?, CommunicationType::ErrorInvalidData)
|
||||
.await;
|
||||
};
|
||||
let user_id = UserId::from(value.get_sender() as i64);
|
||||
let user_id = UserId::from(value.require_sender_i64()?);
|
||||
let user = match user_repo::get_by_user_id(user_id).await {
|
||||
Ok(user) => user,
|
||||
Err(_) => {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||
.send_error_response(request_id, CommunicationType::ErrorNotFound)
|
||||
.await;
|
||||
}
|
||||
};
|
||||
if user.token != reset {
|
||||
return connection
|
||||
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge)
|
||||
.send_error_response(request_id, CommunicationType::ErrorInvalidChallenge)
|
||||
.await;
|
||||
}
|
||||
let result =
|
||||
match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64)))
|
||||
match user_repo::change_iota_id(user_id, Some(IotaId::from(value.require_sender_i64()?)))
|
||||
.await
|
||||
{
|
||||
Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await,
|
||||
|
|
@ -294,5 +308,5 @@ pub async fn change_iota(
|
|||
Err(error) => CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())),
|
||||
};
|
||||
connection.send(&response.with_id(value.get_id())).await
|
||||
connection.send(&response.with_id(request_id)).await
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
use super::capabilities::{OmegaCapabilities, PeerCapabilities};
|
||||
use crate::models::OmikronId;
|
||||
use crate::{
|
||||
load_keyring, log, log_cv_in, log_cv_out, log_err, log_in, server,
|
||||
log, log_cv_in, log_cv_out, log_err, log_in, server,
|
||||
state::OmegaState,
|
||||
transport::connection::{RequiredMtpFields, validate_dispatch_fields},
|
||||
transport::omikron_manager,
|
||||
util::{file_util::load_file_vec, logger::PrintType},
|
||||
};
|
||||
use dashmap::DashMap;
|
||||
use dashmap::{DashMap, mapref::entry::Entry};
|
||||
use mtp::{
|
||||
codec::{CommunicationType, CommunicationValue},
|
||||
crypto::PublicKeyBundle,
|
||||
|
|
@ -16,19 +17,21 @@ use mtp::{
|
|||
use std::{
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
sync::{
|
||||
Arc,
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc, Mutex as StdMutex,
|
||||
atomic::{AtomicU32, AtomicUsize, Ordering},
|
||||
},
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
use tokio::{
|
||||
sync::{Mutex, mpsc},
|
||||
sync::{Mutex, Semaphore, mpsc, oneshot},
|
||||
time::interval,
|
||||
};
|
||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||
|
||||
const CLEANUP_INTERVAL: Duration = Duration::from_secs(30);
|
||||
const MAX_WAITING_AGE: Duration = Duration::from_secs(60);
|
||||
static ACTIVE_CONNECTIONS: AtomicUsize = AtomicUsize::new(0);
|
||||
static NEXT_CORRELATION_ID: AtomicU32 = AtomicU32::new(1);
|
||||
static ACTIVE_CONNECTIONS_BY_IP: once_cell::sync::Lazy<DashMap<IpAddr, usize>> =
|
||||
once_cell::sync::Lazy::new(DashMap::new);
|
||||
|
||||
|
|
@ -47,16 +50,27 @@ impl Drop for ConnectionLimitGuard {
|
|||
|
||||
pub type OmikronResult<T> = crate::error::Result<T>;
|
||||
pub struct WaitingTask {
|
||||
pub task: Box<dyn Fn(Arc<OmikronConnection>, CommunicationValue) -> bool + Send + Sync>,
|
||||
pub sender: oneshot::Sender<CommunicationValue>,
|
||||
pub inserted_at: Instant,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum DispatchClass {
|
||||
Concurrent,
|
||||
Ordered,
|
||||
}
|
||||
|
||||
pub struct OmikronConnection {
|
||||
id: u64,
|
||||
state: Arc<OmegaState>,
|
||||
sender: Mutex<Option<WebMtpSender>>,
|
||||
waiting_tasks: DashMap<u32, WaitingTask>,
|
||||
cleanup_handle: std::sync::Mutex<Option<tokio::task::JoinHandle<()>>>,
|
||||
handler_tasks: TaskTracker,
|
||||
handler_cancel: CancellationToken,
|
||||
handler_limit: Arc<Semaphore>,
|
||||
ordered_sender: mpsc::Sender<CommunicationValue>,
|
||||
ordered_receiver: StdMutex<Option<mpsc::Receiver<CommunicationValue>>>,
|
||||
peer_capabilities: PeerCapabilities,
|
||||
}
|
||||
impl Drop for OmikronConnection {
|
||||
|
|
@ -64,6 +78,9 @@ impl Drop for OmikronConnection {
|
|||
if let Some(handle) = self.cleanup_handle.lock().unwrap().take() {
|
||||
handle.abort();
|
||||
}
|
||||
self.handler_cancel.cancel();
|
||||
self.handler_tasks.close();
|
||||
self.waiting_tasks.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,17 +92,24 @@ impl OmikronConnection {
|
|||
state: Arc<OmegaState>,
|
||||
authenticated: bool,
|
||||
) -> Option<Arc<Self>> {
|
||||
if !authenticated {
|
||||
if !authenticated || id == 0 || i64::try_from(id).is_err() {
|
||||
return None;
|
||||
}
|
||||
let peer_capabilities =
|
||||
PeerCapabilities::from_identification_description(description).ok()?;
|
||||
let handler_concurrency = state.omikron_handler_concurrency;
|
||||
let (ordered_sender, ordered_receiver) = mpsc::channel(handler_concurrency);
|
||||
Some(Arc::new(Self {
|
||||
id,
|
||||
state,
|
||||
sender: Mutex::new(Some(sender)),
|
||||
waiting_tasks: DashMap::new(),
|
||||
cleanup_handle: std::sync::Mutex::new(None),
|
||||
handler_tasks: TaskTracker::new(),
|
||||
handler_cancel: CancellationToken::new(),
|
||||
handler_limit: Arc::new(Semaphore::new(handler_concurrency)),
|
||||
ordered_sender,
|
||||
ordered_receiver: StdMutex::new(Some(ordered_receiver)),
|
||||
peer_capabilities,
|
||||
}))
|
||||
}
|
||||
|
|
@ -94,6 +118,115 @@ impl OmikronConnection {
|
|||
&self.peer_capabilities
|
||||
}
|
||||
|
||||
fn track_handler<F>(&self, task: F)
|
||||
where
|
||||
F: std::future::Future<Output = ()> + Send + 'static,
|
||||
{
|
||||
let handle = self.handler_tasks.spawn(task);
|
||||
let id = self.id as i64;
|
||||
tokio::spawn(async move {
|
||||
if let Err(error) = handle.await {
|
||||
log_err!(
|
||||
id,
|
||||
PrintType::Omega,
|
||||
"Omikron handler task failed: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn start_ordered_worker(self: &Arc<Self>) {
|
||||
let Some(mut receiver) = self.ordered_receiver.lock().unwrap().take() else {
|
||||
return;
|
||||
};
|
||||
let connection = self.clone();
|
||||
let cancellation = self.handler_cancel.clone();
|
||||
self.track_handler(async move {
|
||||
loop {
|
||||
let value = tokio::select! {
|
||||
_ = cancellation.cancelled() => break,
|
||||
value = receiver.recv() => match value {
|
||||
Some(value) => value,
|
||||
None => break,
|
||||
},
|
||||
};
|
||||
let global_permit = tokio::select! {
|
||||
_ = cancellation.cancelled() => break,
|
||||
permit = connection.state.global_handler_limit.clone().acquire_owned() => {
|
||||
match permit {
|
||||
Ok(permit) => permit,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
};
|
||||
let permit = tokio::select! {
|
||||
_ = cancellation.cancelled() => break,
|
||||
permit = connection.handler_limit.clone().acquire_owned() => {
|
||||
match permit {
|
||||
Ok(permit) => permit,
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
};
|
||||
let result = tokio::select! {
|
||||
_ = cancellation.cancelled() => break,
|
||||
result = connection.clone().dispatch(value) => result,
|
||||
};
|
||||
drop(permit);
|
||||
drop(global_permit);
|
||||
if let Err(error) = result {
|
||||
log_err!(
|
||||
connection.id as i64,
|
||||
PrintType::Omega,
|
||||
"Error processing ordered Omikron message: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn stop_handlers(&self) {
|
||||
self.handler_cancel.cancel();
|
||||
self.handler_tasks.close();
|
||||
let _ = tokio::time::timeout(Duration::from_secs(1), self.handler_tasks.wait()).await;
|
||||
self.waiting_tasks.clear();
|
||||
}
|
||||
|
||||
fn correlation_response(value: &CommunicationValue) -> bool {
|
||||
matches!(
|
||||
value.get_comm_type_enum(),
|
||||
Some(CommunicationType::Success)
|
||||
| Some(CommunicationType::ErrorProtocol)
|
||||
| Some(CommunicationType::ErrorAnonymous)
|
||||
| Some(CommunicationType::ErrorInternal)
|
||||
| Some(CommunicationType::ErrorInvalidData)
|
||||
| Some(CommunicationType::ErrorInvalidUserId)
|
||||
| Some(CommunicationType::ErrorInvalidOmikronId)
|
||||
| Some(CommunicationType::ErrorNotFound)
|
||||
| Some(CommunicationType::ErrorNotAuthenticated)
|
||||
| Some(CommunicationType::ErrorNoIota)
|
||||
| Some(CommunicationType::ErrorInvalidChallenge)
|
||||
| Some(CommunicationType::ErrorInvalidSecret)
|
||||
| Some(CommunicationType::ErrorInvalidPrivateKey)
|
||||
| Some(CommunicationType::ErrorInvalidPublicKey)
|
||||
| Some(CommunicationType::ErrorNoUserId)
|
||||
| Some(CommunicationType::ErrorNoCallId)
|
||||
| Some(CommunicationType::ErrorInvalidCallId)
|
||||
)
|
||||
}
|
||||
|
||||
fn next_correlation_id(&self) -> Option<u32> {
|
||||
for _ in 0..u32::MAX {
|
||||
let id = NEXT_CORRELATION_ID.fetch_add(1, Ordering::Relaxed);
|
||||
if id != 0 && !self.waiting_tasks.contains_key(&id) {
|
||||
return Some(id);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn handle(self: Arc<Self>, receiver: &mut WebMtpReceiver) {
|
||||
log_in!(
|
||||
self.id as i64,
|
||||
|
|
@ -117,12 +250,16 @@ impl OmikronConnection {
|
|||
self.clone().cleanup().await;
|
||||
return;
|
||||
}
|
||||
let cleanup_conn = self.clone();
|
||||
self.start_ordered_worker();
|
||||
let cleanup_conn = Arc::downgrade(&self);
|
||||
*self.cleanup_handle.lock().unwrap() = Some(tokio::spawn(async move {
|
||||
let mut ticker = interval(CLEANUP_INTERVAL);
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
cleanup_conn
|
||||
let Some(connection) = cleanup_conn.upgrade() else {
|
||||
break;
|
||||
};
|
||||
connection
|
||||
.waiting_tasks
|
||||
.retain(|_, task| task.inserted_at.elapsed() < MAX_WAITING_AGE);
|
||||
}
|
||||
|
|
@ -164,16 +301,81 @@ impl OmikronConnection {
|
|||
|
||||
async fn process_message(self: Arc<Self>, value: CommunicationValue) -> OmikronResult<()> {
|
||||
log_cv_in!(PrintType::Omikron, &value);
|
||||
if value.is_type(CommunicationType::Relay) {
|
||||
return self.dispatch(value).await;
|
||||
}
|
||||
if let Some(message_id) = value.id()
|
||||
if Self::correlation_response(&value)
|
||||
&& let Some(message_id) = value.id()
|
||||
&& let Some((_, task)) = self.waiting_tasks.remove(&message_id)
|
||||
{
|
||||
let _ = (task.task)(self.clone(), value);
|
||||
let _ = task.sender.send(value);
|
||||
return Ok(());
|
||||
}
|
||||
self.dispatch(value).await
|
||||
|
||||
if !value.is_type(CommunicationType::Relay) {
|
||||
validate_dispatch_fields(&value)?;
|
||||
}
|
||||
|
||||
let dispatch_class = Self::dispatch_class(&value);
|
||||
if dispatch_class == DispatchClass::Ordered {
|
||||
tokio::select! {
|
||||
_ = self.handler_cancel.cancelled() => {
|
||||
return Err(crate::error::OmegaError::NotConnected);
|
||||
}
|
||||
result = self.ordered_sender.send(value) => {
|
||||
result.map_err(|_| crate::error::OmegaError::NotConnected)?;
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let global_permit = tokio::select! {
|
||||
_ = self.handler_cancel.cancelled() => {
|
||||
return Err(crate::error::OmegaError::NotConnected);
|
||||
}
|
||||
permit = self.state.global_handler_limit.clone().acquire_owned() => {
|
||||
permit.map_err(|_| crate::error::OmegaError::NotConnected)?
|
||||
}
|
||||
};
|
||||
let permit = tokio::select! {
|
||||
_ = self.handler_cancel.cancelled() => {
|
||||
drop(global_permit);
|
||||
return Err(crate::error::OmegaError::NotConnected);
|
||||
}
|
||||
permit = self.handler_limit.clone().acquire_owned() => {
|
||||
permit.map_err(|_| crate::error::OmegaError::NotConnected)?
|
||||
}
|
||||
};
|
||||
let connection = self.clone();
|
||||
let cancellation = self.handler_cancel.clone();
|
||||
self.track_handler(async move {
|
||||
tokio::select! {
|
||||
_ = cancellation.cancelled() => {}
|
||||
result = connection.clone().dispatch(value) => {
|
||||
if let Err(error) = result {
|
||||
log_err!(
|
||||
connection.id as i64,
|
||||
PrintType::Omega,
|
||||
"Error processing Omikron message: {}",
|
||||
error
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(permit);
|
||||
drop(global_permit);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn dispatch_class(value: &CommunicationValue) -> DispatchClass {
|
||||
match value.get_comm_type_enum() {
|
||||
Some(CommunicationType::UserConnected)
|
||||
| Some(CommunicationType::UserDisconnected)
|
||||
| Some(CommunicationType::IotaConnected)
|
||||
| Some(CommunicationType::IotaDisconnected)
|
||||
| Some(CommunicationType::SyncClientIotaStatus)
|
||||
| Some(CommunicationType::StateSubscribe)
|
||||
| Some(CommunicationType::ClientChanged) => DispatchClass::Ordered,
|
||||
_ => DispatchClass::Concurrent,
|
||||
}
|
||||
}
|
||||
|
||||
async fn dispatch(self: Arc<Self>, value: CommunicationValue) -> OmikronResult<()> {
|
||||
|
|
@ -341,33 +543,39 @@ impl OmikronConnection {
|
|||
value: &CommunicationValue,
|
||||
timeout: Duration,
|
||||
) -> OmikronResult<CommunicationValue> {
|
||||
let (tx, mut rx) = mpsc::channel(1);
|
||||
let message_id = value.id().unwrap_or_default();
|
||||
let task_tx = tx.clone();
|
||||
self.waiting_tasks.insert(
|
||||
message_id,
|
||||
WaitingTask {
|
||||
task: Box::new(move |_, response| {
|
||||
let task_tx = task_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let _ = task_tx.send(response).await;
|
||||
});
|
||||
true
|
||||
}),
|
||||
inserted_at: Instant::now(),
|
||||
},
|
||||
);
|
||||
value.require_id()?;
|
||||
let (tx, rx) = oneshot::channel();
|
||||
let message_id = self.next_correlation_id().ok_or_else(|| {
|
||||
crate::OmegaError::Transport("no relay correlation id available".into())
|
||||
})?;
|
||||
match self.waiting_tasks.entry(message_id) {
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(WaitingTask {
|
||||
sender: tx,
|
||||
inserted_at: Instant::now(),
|
||||
});
|
||||
}
|
||||
Entry::Occupied(_) => {
|
||||
return Err(crate::OmegaError::Transport(
|
||||
"relay correlation id collision".into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
let outbound = value.clone().with_id(message_id);
|
||||
|
||||
if let Err(error) = self.clone().send(value).await {
|
||||
if let Err(error) = self.clone().send(&outbound).await {
|
||||
self.waiting_tasks.remove(&message_id);
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
match tokio::time::timeout(timeout, rx.recv()).await {
|
||||
Ok(Some(response)) => Ok(response),
|
||||
Ok(None) => Err(crate::error::OmegaError::Transport(
|
||||
"Relay response channel closed".into(),
|
||||
)),
|
||||
match tokio::time::timeout(timeout, rx).await {
|
||||
Ok(Ok(response)) => Ok(response),
|
||||
Ok(Err(_)) => {
|
||||
self.waiting_tasks.remove(&message_id);
|
||||
Err(crate::error::OmegaError::Transport(
|
||||
"Relay response channel closed".into(),
|
||||
))
|
||||
}
|
||||
Err(_) => {
|
||||
self.waiting_tasks.remove(&message_id);
|
||||
Err(crate::error::OmegaError::Transport(
|
||||
|
|
@ -411,6 +619,7 @@ impl OmikronConnection {
|
|||
}
|
||||
}
|
||||
async fn cleanup(self: Arc<Self>) {
|
||||
self.stop_handlers().await;
|
||||
if self.id != 0 {
|
||||
log_in!(self.id as i64, PrintType::Omega, "Omikron disconnected");
|
||||
if omikron_manager::remove_omikron(self.id as i64, &self).await {
|
||||
|
|
@ -441,7 +650,8 @@ pub async fn get_by_omikron_id(
|
|||
description: Option<String>,
|
||||
) -> Option<PublicKeyBundle> {
|
||||
PeerCapabilities::from_identification_description(description.as_deref()).ok()?;
|
||||
crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id as i64))
|
||||
let omikron_id = i64::try_from(omikron_id).ok().filter(|id| *id > 0)?;
|
||||
crate::db::omikron_repo::get_omikron_by_id(OmikronId::from(omikron_id))
|
||||
.await
|
||||
.ok()
|
||||
.map(|omikron| omikron.public_key)
|
||||
|
|
@ -453,7 +663,7 @@ pub async fn complete_register(_: PublicKeyBundle, _: Option<String>) -> u64 {
|
|||
pub async fn start(port: u16, state: Arc<OmegaState>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cert_pem = load_file_vec("certs", "cert.pem")?;
|
||||
let key_pem = load_file_vec("certs", "key.pem")?;
|
||||
let web_config = server::server::build_web_config()?
|
||||
let web_config = server::web::build_web_config(state.identity.clone())?
|
||||
.serve_tcp_https(true)
|
||||
.max_tcp_connections(256);
|
||||
let ip = IpAddr::from(Ipv4Addr::new(0, 0, 0, 0));
|
||||
|
|
@ -478,7 +688,7 @@ pub async fn start(port: u16, state: Arc<OmegaState>) -> Result<(), Box<dyn std:
|
|||
max_frames_per_stream: None,
|
||||
})
|
||||
.with_authentication(
|
||||
load_keyring(),
|
||||
state.identity.clone_keyring()?,
|
||||
Box::new(|id, description| Box::pin(get_by_omikron_id(id, description))),
|
||||
Box::new(|key, description| Box::pin(complete_register(key, description))),
|
||||
)
|
||||
|
|
@ -499,7 +709,7 @@ pub async fn start(port: u16, state: Arc<OmegaState>) -> Result<(), Box<dyn std:
|
|||
continue;
|
||||
}
|
||||
};
|
||||
let config = crate::config::RateLimitConfig::from_env();
|
||||
let config = &state.config.rate_limits;
|
||||
let peer_ip = conn.remote_addr.map(|address| address.ip());
|
||||
let active = ACTIVE_CONNECTIONS.fetch_add(1, Ordering::AcqRel) + 1;
|
||||
let peer_active = peer_ip.map(|ip| {
|
||||
|
|
@ -507,10 +717,11 @@ pub async fn start(port: u16, state: Arc<OmegaState>) -> Result<(), Box<dyn std:
|
|||
*count += 1;
|
||||
*count
|
||||
});
|
||||
let connection_limit_guard = ConnectionLimitGuard(peer_ip);
|
||||
if active > config.transport_connections
|
||||
|| peer_active.is_some_and(|count| count > config.transport_connections_per_ip)
|
||||
{
|
||||
drop(ConnectionLimitGuard(peer_ip));
|
||||
drop(connection_limit_guard);
|
||||
log_err!(
|
||||
0,
|
||||
PrintType::Omega,
|
||||
|
|
@ -526,6 +737,7 @@ pub async fn start(port: u16, state: Arc<OmegaState>) -> Result<(), Box<dyn std:
|
|||
state.clone(),
|
||||
authenticated,
|
||||
) else {
|
||||
drop(connection_limit_guard);
|
||||
log_err!(
|
||||
0,
|
||||
PrintType::Omega,
|
||||
|
|
@ -534,10 +746,56 @@ pub async fn start(port: u16, state: Arc<OmegaState>) -> Result<(), Box<dyn std:
|
|||
continue;
|
||||
};
|
||||
tokio::spawn(async move {
|
||||
let _guard = ConnectionLimitGuard(peer_ip);
|
||||
let _guard = connection_limit_guard;
|
||||
omikron_manager::add_omikron(connection.clone()).await;
|
||||
connection.handle(&mut conn.receiver).await;
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DispatchClass, OmikronConnection};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue};
|
||||
|
||||
#[test]
|
||||
fn relay_dispatch_is_not_on_the_ordered_state_lane() {
|
||||
let value = CommunicationValue::new(CommunicationType::Relay);
|
||||
assert_eq!(
|
||||
OmikronConnection::dispatch_class(&value),
|
||||
DispatchClass::Concurrent
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn presence_lifecycle_dispatch_is_ordered() {
|
||||
let value = CommunicationValue::new(CommunicationType::UserConnected);
|
||||
assert_eq!(
|
||||
OmikronConnection::dispatch_class(&value),
|
||||
DispatchClass::Ordered
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn client_presence_changes_are_ordered() {
|
||||
let value = CommunicationValue::new(CommunicationType::ClientChanged);
|
||||
assert_eq!(
|
||||
OmikronConnection::dispatch_class(&value),
|
||||
DispatchClass::Ordered
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_protocol_responses_match_waiting_tasks() {
|
||||
assert!(OmikronConnection::correlation_response(
|
||||
&CommunicationValue::new(CommunicationType::Success).with_id(1)
|
||||
));
|
||||
assert!(OmikronConnection::correlation_response(
|
||||
&CommunicationValue::new(CommunicationType::ErrorInvalidData).with_id(1)
|
||||
));
|
||||
assert!(!OmikronConnection::correlation_response(
|
||||
&CommunicationValue::new(CommunicationType::GetUserData).with_id(1)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use rand::prelude::IteratorRandom;
|
|||
use std::sync::Arc;
|
||||
|
||||
pub static OMIKRON_CONNECTIONS: Lazy<DashMap<i64, Arc<OmikronConnection>>> =
|
||||
Lazy::new(|| DashMap::new());
|
||||
Lazy::new(DashMap::new);
|
||||
|
||||
pub async fn add_omikron(conn: Arc<OmikronConnection>) {
|
||||
let id = match conn.clone().get_omikron_id().await {
|
||||
|
|
@ -64,12 +64,11 @@ pub async fn get_all_connections()
|
|||
.map_err(|_| ())?;
|
||||
for user in users {
|
||||
for route in state.presence.routes_for_user(user.id.0) {
|
||||
if let Some(iotas) = result.get_mut(&route.omikron_id) {
|
||||
if let Some(iota_id) = user.iota_id
|
||||
&& let Some(users) = iotas.get_mut(&iota_id.0)
|
||||
{
|
||||
users.push(user.id.0);
|
||||
}
|
||||
if let Some(iotas) = result.get_mut(&route.omikron_id)
|
||||
&& let Some(iota_id) = user.iota_id
|
||||
&& let Some(users) = iotas.get_mut(&iota_id.0)
|
||||
{
|
||||
users.push(user.id.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -97,10 +96,10 @@ pub async fn send_state_batch(
|
|||
pub async fn get_random_omikron() -> Result<Arc<OmikronConnection>, ()> {
|
||||
let keys: Vec<_> = OMIKRON_CONNECTIONS.iter().map(|e| *e.key()).collect();
|
||||
|
||||
if let Some(key) = keys.into_iter().choose(&mut rand::rng()) {
|
||||
if let Some(connection) = get_connected_omikron(key) {
|
||||
return Ok(connection);
|
||||
}
|
||||
if let Some(key) = keys.into_iter().choose(&mut rand::rng())
|
||||
&& let Some(connection) = get_connected_omikron(key)
|
||||
{
|
||||
return Ok(connection);
|
||||
}
|
||||
|
||||
Err(())
|
||||
|
|
|
|||
|
|
@ -21,14 +21,6 @@ pub enum RouteTarget {
|
|||
}
|
||||
|
||||
impl RouteTarget {
|
||||
pub fn wire_id(self) -> Option<u64> {
|
||||
let (kind, id) = match self {
|
||||
Self::User(id) => (USER_TARGET_KIND, id),
|
||||
Self::Iota(id) => (IOTA_TARGET_KIND, id),
|
||||
};
|
||||
(id > 0 && id <= TARGET_ID_MASK).then_some(kind | id)
|
||||
}
|
||||
|
||||
pub fn from_wire_id(value: u64) -> Option<Self> {
|
||||
let id = value & TARGET_ID_MASK;
|
||||
if id == 0 || value & !(TARGET_KIND_MASK | TARGET_ID_MASK) != 0 {
|
||||
|
|
@ -40,12 +32,6 @@ impl RouteTarget {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn id(self) -> u64 {
|
||||
match self {
|
||||
Self::User(id) | Self::Iota(id) => id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
@ -183,10 +169,11 @@ mod tests {
|
|||
use mtp::codec::DataValue;
|
||||
|
||||
fn wire(target: RouteTarget) -> u64 {
|
||||
let Some(value) = target.wire_id() else {
|
||||
panic!("valid route target was rejected");
|
||||
let (kind, id) = match target {
|
||||
RouteTarget::User(id) => (USER_TARGET_KIND, id),
|
||||
RouteTarget::Iota(id) => (IOTA_TARGET_KIND, id),
|
||||
};
|
||||
value
|
||||
kind | id
|
||||
}
|
||||
|
||||
fn relay_frame() -> CommunicationValue {
|
||||
|
|
|
|||
|
|
@ -1,293 +1,15 @@
|
|||
use once_cell::sync::Lazy;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{self, BufReader, Read};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use uuid::Uuid;
|
||||
use zip::ZipArchive;
|
||||
|
||||
use crate::log;
|
||||
use std::path::PathBuf;
|
||||
|
||||
static WORKING_DIR: Lazy<PathBuf> =
|
||||
Lazy::new(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
|
||||
|
||||
pub fn delete_file(path: &str, name: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file = dir.join(name);
|
||||
if !file.exists() {
|
||||
return false;
|
||||
}
|
||||
fs::remove_file(file).is_ok()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_directory(path: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
delete_dir_recursive(&dir)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn delete_dir_recursive(directory: &Path) -> bool {
|
||||
if !directory.exists() {
|
||||
return false;
|
||||
}
|
||||
if let Err(e) = fs::remove_dir_all(directory) {
|
||||
log!(
|
||||
"[IMPORTANT] Couldn't delete directory {}: {}",
|
||||
directory.display(),
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn delete_user_directory(user_id: Uuid) {
|
||||
let user_dir = Path::new(&get_directory())
|
||||
.join("users")
|
||||
.join(user_id.to_string());
|
||||
let _ = delete_dir_recursive(&user_dir);
|
||||
}
|
||||
|
||||
pub fn load_file_buf(path: &str, name: &str) -> io::Result<BufReader<File>> {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
// Ensure the directory exists, create if necessary
|
||||
if !dir.exists() {
|
||||
if let Err(_) = fs::create_dir_all(&dir) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Directory creation failed",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Create the file if it doesn't exist
|
||||
if !file_path.exists() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"File creation failed",
|
||||
));
|
||||
}
|
||||
|
||||
// Open the file and return a BufReader for efficient reading
|
||||
let file = File::open(&file_path)?;
|
||||
Ok(BufReader::new(file))
|
||||
}
|
||||
pub fn has_file(path: &str, name: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
if !dir.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if !file_path.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
pub fn has_dir(path: &str) -> bool {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
|
||||
if !dir.exists() {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn load_file(path: &str, name: &str) -> String {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
log!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return String::new();
|
||||
}
|
||||
return String::new();
|
||||
}
|
||||
|
||||
if !file_path.exists() {
|
||||
if let Err(e) = File::create(&file_path) {
|
||||
log!("[IMPORTANT] Couldn't create file: {}", e);
|
||||
}
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut content = String::new();
|
||||
if let Ok(mut f) = File::open(&file_path) {
|
||||
let _ = f.read_to_string(&mut content);
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
pub fn load_file_vec(path: &str, name: &str) -> Result<Vec<u8>, std::io::Error> {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let dir = PathBuf::from(get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
std::fs::read(file_path)
|
||||
}
|
||||
|
||||
pub fn save_file(path: &str, name: &str, value: &str) {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let file_path = dir.join(name);
|
||||
|
||||
if !dir.exists() {
|
||||
if let Err(e) = fs::create_dir_all(&dir) {
|
||||
log!("[IMPORTANT] Couldn't create directories: {}", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(e) = fs::write(&file_path, value) {
|
||||
log!(
|
||||
"[IMPORTANT] Couldn't write file {}: {}",
|
||||
file_path.display(),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_children(path: &str) -> Vec<String> {
|
||||
let dir = Path::new(&get_directory()).join(path);
|
||||
let mut children = Vec::new();
|
||||
if let Ok(entries) = fs::read_dir(&dir) {
|
||||
for entry in entries {
|
||||
if let Ok(entry) = entry {
|
||||
children.push(entry.file_name().to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
children
|
||||
}
|
||||
|
||||
pub fn get_directory() -> String {
|
||||
WORKING_DIR.to_string_lossy().to_string()
|
||||
}
|
||||
|
||||
pub fn working_dir() -> &'static Path {
|
||||
&WORKING_DIR
|
||||
}
|
||||
|
||||
// Helper to download the zip file content to a file on disk
|
||||
#[allow(dead_code)]
|
||||
async fn download_zip(url: &str, as_name: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let response = reqwest::get(url).await?;
|
||||
|
||||
// Check for successful response status
|
||||
if !response.status().is_success() {
|
||||
return Err(format!("Failed to download file: Status {}", response.status()).into());
|
||||
}
|
||||
|
||||
let mut zip_file = tokio::fs::File::create(as_name).await?;
|
||||
let body = response.bytes().await?;
|
||||
zip_file.write_all(&body).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code, deprecated)]
|
||||
fn extract_zip_contents_to_folder(
|
||||
zip_path: &Path,
|
||||
target_dir: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file = File::open(zip_path)?;
|
||||
let mut archive = ZipArchive::new(file)?;
|
||||
|
||||
let staging_dir = target_dir.with_extension("staging");
|
||||
|
||||
let _ = fs::remove_dir_all(&staging_dir);
|
||||
fs::create_dir_all(&staging_dir)?;
|
||||
|
||||
let mut first_item_name: Option<PathBuf> = None;
|
||||
|
||||
for i in 0..archive.len() {
|
||||
let mut file = archive.by_index(i)?;
|
||||
let entry_path = staging_dir.join(file.sanitized_name());
|
||||
|
||||
if i == 0 {
|
||||
if file.name().ends_with('/') || file.sanitized_name().components().count() == 1 {
|
||||
first_item_name = Some(file.sanitized_name());
|
||||
}
|
||||
}
|
||||
|
||||
if file.name().ends_with('/') {
|
||||
fs::create_dir_all(&entry_path)?;
|
||||
} else {
|
||||
if let Some(parent) = entry_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let mut out_file = File::create(entry_path)?;
|
||||
io::copy(&mut file, &mut out_file)?;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(root_path) = first_item_name {
|
||||
let root_dir = staging_dir.join(&root_path);
|
||||
|
||||
if root_dir.is_dir() {
|
||||
let root_contents_count = fs::read_dir(&staging_dir)?.count();
|
||||
|
||||
if root_contents_count == 1
|
||||
|| (root_contents_count > 1 && fs::metadata(&root_dir).is_ok())
|
||||
{
|
||||
let _ = fs::remove_dir_all(target_dir);
|
||||
fs::create_dir_all(target_dir)?;
|
||||
|
||||
for entry in fs::read_dir(root_dir)? {
|
||||
let entry = entry?;
|
||||
let src = entry.path();
|
||||
let dest = target_dir.join(entry.file_name());
|
||||
|
||||
if let Err(_) = fs::rename(&src, &dest) {
|
||||
if src.is_file() {
|
||||
fs::copy(&src, &dest)?;
|
||||
} else {
|
||||
if entry.path().is_dir() {
|
||||
fs::rename(&src, &dest)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = fs::remove_dir_all(&staging_dir);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log!("Extracting directly (no single root folder detected).");
|
||||
let _ = fs::remove_dir_all(target_dir);
|
||||
fs::rename(&staging_dir, target_dir)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub async fn download_and_extract_zip(url: &str, as_name: &str) {
|
||||
let base_dir = PathBuf::from(get_directory());
|
||||
let zip_filename = format!("{}.zip", Uuid::new_v4());
|
||||
let zip_path = base_dir.join(&zip_filename);
|
||||
let target_dir = base_dir.join(as_name);
|
||||
|
||||
if let Err(e) = download_zip(url, &zip_path).await {
|
||||
log!("Error downloading file: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let zip_path_clone = zip_path.clone();
|
||||
let target_dir_clone = target_dir.clone();
|
||||
let extract_result = extract_zip_contents_to_folder(&zip_path_clone, &target_dir_clone);
|
||||
if let Err(e) = extract_result {
|
||||
log!("Panic during ZIP extraction: {}", e);
|
||||
}
|
||||
|
||||
if let Err(e) = tokio::fs::remove_file(&zip_path).await {
|
||||
log!("Error cleaning up ZIP file {}: {}", zip_path.display(), e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use std::{
|
|||
use ansi_term::Color;
|
||||
use mtp::codec::{CommunicationValue, DataTypeId, DataValue, Version};
|
||||
|
||||
use crate::transport::connection::MtpValueCompat;
|
||||
use crate::util::file_util::get_directory;
|
||||
|
||||
static LOGGER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
|
||||
|
|
@ -223,7 +222,7 @@ pub fn log_cv_internal(
|
|||
let formatted = format_cv(cv);
|
||||
|
||||
log_internal(
|
||||
Some(cv.get_sender() as i64),
|
||||
cv.sender().and_then(|sender| i64::try_from(sender).ok()),
|
||||
print_type.unwrap_or(PrintType::General),
|
||||
prefix,
|
||||
false,
|
||||
|
|
@ -234,14 +233,14 @@ pub fn log_cv_internal(
|
|||
pub fn format_cv(cv: &CommunicationValue) -> String {
|
||||
let mut parts = Vec::new();
|
||||
|
||||
let sender = cv.get_sender();
|
||||
let receiver = cv.get_receiver();
|
||||
let sender = cv.sender().filter(|sender| *sender != 0);
|
||||
let receiver = cv.receiver().filter(|receiver| *receiver != 0);
|
||||
|
||||
if sender > 0 && receiver > 0 {
|
||||
if let (Some(sender), Some(receiver)) = (sender, receiver) {
|
||||
parts.push(format!("{} > {}", sender, receiver));
|
||||
} else if sender > 0 {
|
||||
} else if let Some(sender) = sender {
|
||||
parts.push(format!("{}", sender));
|
||||
} else if receiver > 0 {
|
||||
} else if let Some(receiver) = receiver {
|
||||
parts.push(format!("> {}", receiver));
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +248,11 @@ pub fn format_cv(cv: &CommunicationValue) -> String {
|
|||
.get_comm_type_enum()
|
||||
.map(|kind| kind.to_string())
|
||||
.unwrap_or_else(|| cv.get_type().to_string());
|
||||
parts.push(format!("{} (id={})", comm_type, cv.get_id()));
|
||||
let id = cv
|
||||
.id()
|
||||
.map(|id| id.to_string())
|
||||
.unwrap_or_else(|| "missing".to_string());
|
||||
parts.push(format!("{} (id={})", comm_type, id));
|
||||
|
||||
let data = cv.data().unwrap_or(&[]);
|
||||
|
||||
|
|
@ -258,7 +261,7 @@ pub fn format_cv(cv: &CommunicationValue) -> String {
|
|||
Version(3, 0),
|
||||
);
|
||||
|
||||
parts.push(format!("{}", formated_data));
|
||||
parts.push(formated_data.to_string());
|
||||
|
||||
parts.join(": ")
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue