[Updt] Mtp 0.3.0
Some checks failed
Dependency builds / Build web (pull_request) Has been skipped
Dependency builds / Build desktop (pull_request) Has been skipped
Dependency builds / Test native MTP (pull_request) Has been skipped
Dependency builds / Build mobile (pull_request) Has been skipped
/ build-web (push) Failing after 3m32s
/ build-desktop (linux) (push) Failing after 2m47s
/ build-mobile (push) Failing after 5m29s
/ release (push) Has been skipped
Some checks failed
Dependency builds / Build web (pull_request) Has been skipped
Dependency builds / Build desktop (pull_request) Has been skipped
Dependency builds / Test native MTP (pull_request) Has been skipped
Dependency builds / Build mobile (pull_request) Has been skipped
/ build-web (push) Failing after 3m32s
/ build-desktop (linux) (push) Failing after 2m47s
/ build-mobile (push) Failing after 5m29s
/ release (push) Has been skipped
This commit is contained in:
parent
57c7ceb27a
commit
2a55c87df1
33 changed files with 1825 additions and 785 deletions
296
apps/tauri/src-tauri/Cargo.lock
generated
296
apps/tauri/src-tauri/Cargo.lock
generated
|
|
@ -674,12 +674,6 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.9.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
|
||||
|
||||
[[package]]
|
||||
name = "const-oid"
|
||||
version = "0.10.2"
|
||||
|
|
@ -889,7 +883,6 @@ dependencies = [
|
|||
"cfg-if",
|
||||
"cpufeatures 0.2.17",
|
||||
"curve25519-dalek-derive",
|
||||
"digest 0.10.7",
|
||||
"fiat-crypto 0.2.9",
|
||||
"rustc_version",
|
||||
"subtle",
|
||||
|
|
@ -974,25 +967,14 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||
dependencies = [
|
||||
"const-oid 0.9.6",
|
||||
"pem-rfc7468 0.7.0",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
|
||||
dependencies = [
|
||||
"const-oid 0.10.2",
|
||||
"pem-rfc7468 1.0.0",
|
||||
"const-oid",
|
||||
"pem-rfc7468",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
|
|
@ -1057,7 +1039,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2"
|
||||
dependencies = [
|
||||
"block-buffer 0.12.1",
|
||||
"const-oid 0.10.2",
|
||||
"const-oid",
|
||||
"crypto-common 0.2.2",
|
||||
"ctutils",
|
||||
]
|
||||
|
|
@ -1204,38 +1186,14 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "2.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
||||
dependencies = [
|
||||
"pkcs8 0.10.2",
|
||||
"signature 2.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
|
||||
dependencies = [
|
||||
"pkcs8 0.11.0",
|
||||
"signature 3.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ed25519-dalek"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
||||
dependencies = [
|
||||
"curve25519-dalek 4.1.3",
|
||||
"ed25519 2.2.3",
|
||||
"serde",
|
||||
"sha2 0.10.9",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
"pkcs8",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1245,10 +1203,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
|
||||
dependencies = [
|
||||
"curve25519-dalek 5.0.0",
|
||||
"ed25519 3.0.0",
|
||||
"ed25519",
|
||||
"serde",
|
||||
"sha2 0.11.0",
|
||||
"signature 3.0.0",
|
||||
"signature",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -2737,14 +2695,14 @@ version = "0.1.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d"
|
||||
dependencies = [
|
||||
"const-oid 0.10.2",
|
||||
"const-oid",
|
||||
"crypto-common 0.2.2",
|
||||
"ctutils",
|
||||
"hybrid-array",
|
||||
"module-lattice",
|
||||
"pkcs8 0.11.0",
|
||||
"pkcs8",
|
||||
"shake",
|
||||
"signature 3.0.0",
|
||||
"signature",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2786,72 +2744,50 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mtp"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"mtp-client",
|
||||
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"mtp-host",
|
||||
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-type-map 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-transport",
|
||||
"mtp-type-map",
|
||||
"mtp-webserver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-client"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"mtp-transport",
|
||||
"rand 0.10.2",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-codec"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"byteorder",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-type-map 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"mtp-type-map",
|
||||
"rand 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-codec"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"byteorder",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||
"mtp-type-map 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||
"rand 0.10.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
dependencies = [
|
||||
"quinn",
|
||||
"rustls",
|
||||
"thiserror 2.0.19",
|
||||
"wtransport",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-common"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"quinn",
|
||||
"rustls",
|
||||
|
|
@ -2861,18 +2797,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mtp-crypto"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"base64 0.22.1",
|
||||
"chacha20poly1305",
|
||||
"ed25519-dalek 2.2.0",
|
||||
"ed25519-dalek",
|
||||
"getrandom 0.4.3",
|
||||
"hkdf",
|
||||
"ml-dsa",
|
||||
"mlkem-tls",
|
||||
"rand 0.10.2",
|
||||
"rand_core 0.10.1",
|
||||
"rand_core 0.6.4",
|
||||
"rustls",
|
||||
"serde",
|
||||
"sha2 0.11.0",
|
||||
|
|
@ -2882,89 +2818,45 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-crypto"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
name = "mtp-host"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"base64 0.23.1",
|
||||
"chacha20poly1305",
|
||||
"ed25519-dalek 3.0.0",
|
||||
"getrandom 0.4.3",
|
||||
"hkdf",
|
||||
"ml-dsa",
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"mtp-transport",
|
||||
"rand 0.10.2",
|
||||
"rand_core 0.10.1",
|
||||
"rustls",
|
||||
"sha2 0.11.0",
|
||||
"thiserror 2.0.19",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wtransport",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-transport"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"rand 0.10.2",
|
||||
"rcgen",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"sha2 0.11.0",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wtransport",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-host"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
dependencies = [
|
||||
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"rand 0.10.2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wtransport",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-transport"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"rcgen",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"sha2 0.11.0",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wtransport",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-transport"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||
"rcgen",
|
||||
"rustls",
|
||||
"rustls-native-certs",
|
||||
"sha2 0.11.0",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"wtransport",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-type-map"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mtp-type-map"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_yaml",
|
||||
|
|
@ -2972,8 +2864,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "mtp-webserver"
|
||||
version = "0.2.0"
|
||||
source = "git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22#7182272e3edb0079d7b74b45b2f62a298d2fee22"
|
||||
version = "0.3.0"
|
||||
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=a5c8d4f0c898c78351e9d54124886c86e789a22a#a5c8d4f0c898c78351e9d54124886c86e789a22a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
|
|
@ -2984,13 +2876,12 @@ dependencies = [
|
|||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"mtp-codec 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-common 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-crypto 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-codec",
|
||||
"mtp-common",
|
||||
"mtp-crypto",
|
||||
"mtp-host",
|
||||
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=7182272e3edb0079d7b74b45b2f62a298d2fee22)",
|
||||
"mtp-transport",
|
||||
"quinn",
|
||||
"rand 0.10.2",
|
||||
"rustls",
|
||||
"thiserror 2.0.19",
|
||||
"tokio",
|
||||
|
|
@ -3492,15 +3383,6 @@ dependencies = [
|
|||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pem-rfc7468"
|
||||
version = "1.0.0"
|
||||
|
|
@ -3586,24 +3468,14 @@ dependencies = [
|
|||
"futures-io",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
|
||||
dependencies = [
|
||||
"der 0.7.10",
|
||||
"spki 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7"
|
||||
dependencies = [
|
||||
"der 0.8.1",
|
||||
"spki 0.8.0",
|
||||
"der",
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -4592,15 +4464,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "3.0.0"
|
||||
|
|
@ -4709,16 +4572,6 @@ dependencies = [
|
|||
"system-deps",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der 0.7.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
version = "0.8.0"
|
||||
|
|
@ -4726,7 +4579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"der 0.8.1",
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -5289,7 +5142,6 @@ dependencies = [
|
|||
"base64 0.22.1",
|
||||
"jni 0.22.4",
|
||||
"mtp",
|
||||
"mtp-transport 0.2.0 (git+https://git.methanium.net/methanium/mtp.git?rev=b067614a684eb1856bc5db7b3fd82148c036ce6b)",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ serde_json = "1"
|
|||
base64 = "0.22"
|
||||
reqwest = { version = "0.13", default-features = false, features = ["json", "rustls"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "sync", "time"] }
|
||||
mtp = { git = "https://git.methanium.net/methanium/mtp.git", rev = "7182272e3edb0079d7b74b45b2f62a298d2fee22", features = ["client", "crypto"] }
|
||||
mtp-transport = { git = "https://git.methanium.net/methanium/mtp.git", rev = "b067614a684eb1856bc5db7b3fd82148c036ce6b" }
|
||||
mtp = { git = "https://git.methanium.net/Methanium/mtp.git", rev = "a5c8d4f0c898c78351e9d54124886c86e789a22a", features = ["client", "crypto"] }
|
||||
webpki-root-certs = "1"
|
||||
tauri-plugin-deep-link = "2"
|
||||
tauri-plugin-notification = "2"
|
||||
|
|
@ -36,6 +35,11 @@ version = "2"
|
|||
features = []
|
||||
default-features = true
|
||||
|
||||
[target.'cfg(not(target_os = "android"))'.dependencies.tauri]
|
||||
version = "2"
|
||||
features = []
|
||||
default-features = true
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
jni = "0.22"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
use lib::log;
|
||||
|
||||
fn main() {
|
||||
mobile_lib::run();
|
||||
log("Test test 123")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use std::sync::{
|
|||
atomic::{AtomicBool, AtomicU64, Ordering},
|
||||
Arc, Mutex, OnceLock, RwLock,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
|
||||
use base64::{
|
||||
engine::general_purpose::{STANDARD, STANDARD_NO_PAD},
|
||||
|
|
@ -16,6 +16,7 @@ use mtp::crypto::{
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Map, Value};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
const EVENT_NAME: &str = "mtp://event";
|
||||
const DISCONNECTED: u8 = 0;
|
||||
|
|
@ -24,6 +25,9 @@ const CONNECTED: u8 = 2;
|
|||
const CHAT_SECRET_SALT: &[u8] = b"tensamin-chat-secret-v1";
|
||||
const CHAT_MESSAGE_SALT: &[u8] = b"tensamin-chat-message-v1";
|
||||
const CHAT_SECRET_SCHEME: &str = "mtp-chat-secret-kem-chacha20poly1305-hkdf-sha256-v1";
|
||||
const INITIAL_SYNC_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const MAX_BUFFERED_INITIAL_FRAMES: usize = 1_000;
|
||||
const NOTIFICATION_QUEUE_CAPACITY: usize = 32;
|
||||
#[cfg(target_os = "android")]
|
||||
const ROOT_YE_PEM: &[u8] = b"-----BEGIN CERTIFICATE-----\n\
|
||||
MIIB2TCCAWCgAwIBAgIRAKQCa6LvbHwg1AR+XmWmk4AwCgYIKoZIzj0EAwMwLjEL\n\
|
||||
|
|
@ -95,7 +99,7 @@ enum MtpEvent {
|
|||
pub struct MtpManager {
|
||||
runtime: tokio::runtime::Runtime,
|
||||
config: RwLock<Option<MtpConfig>>,
|
||||
connection: RwLock<Option<Arc<MTPConnection>>>,
|
||||
connection: RwLock<Option<Arc<ManagedConnection>>>,
|
||||
snapshot: RwLock<MtpSnapshot>,
|
||||
generation: AtomicU64,
|
||||
enabled: AtomicBool,
|
||||
|
|
@ -104,6 +108,46 @@ pub struct MtpManager {
|
|||
start_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
struct RequestIdAllocator {
|
||||
next: AtomicU64,
|
||||
}
|
||||
|
||||
impl RequestIdAllocator {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
next: AtomicU64::new(1),
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&self) -> Result<u32, String> {
|
||||
let value = self.next.fetch_add(1, Ordering::Relaxed);
|
||||
u32::try_from(value)
|
||||
.map_err(|_| "MTP request ID space exhausted for this connection".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
struct ManagedConnection {
|
||||
mtp: Arc<MTPConnection>,
|
||||
request_ids: RequestIdAllocator,
|
||||
}
|
||||
|
||||
impl ManagedConnection {
|
||||
async fn next_request_id(&self) -> Result<u32, String> {
|
||||
let id = self.request_ids.next();
|
||||
if id.is_err() {
|
||||
self.mtp.sender.close().await;
|
||||
}
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
struct PreparedConnection {
|
||||
connection: MTPConnection,
|
||||
request_ids: RequestIdAllocator,
|
||||
initial_state: Value,
|
||||
buffered_frames: Vec<CommunicationValue>,
|
||||
}
|
||||
|
||||
static MANAGER: OnceLock<MtpManager> = OnceLock::new();
|
||||
|
||||
pub fn manager() -> &'static MtpManager {
|
||||
|
|
@ -149,7 +193,7 @@ impl MtpManager {
|
|||
.take()
|
||||
{
|
||||
self.runtime
|
||||
.spawn(async move { connection.sender.close().await });
|
||||
.spawn(async move { connection.mtp.sender.close().await });
|
||||
}
|
||||
self.set_snapshot(MtpSnapshot {
|
||||
generation,
|
||||
|
|
@ -171,7 +215,7 @@ impl MtpManager {
|
|||
.take()
|
||||
{
|
||||
self.runtime
|
||||
.spawn(async move { connection.sender.close().await });
|
||||
.spawn(async move { connection.mtp.sender.close().await });
|
||||
}
|
||||
self.set_snapshot(MtpSnapshot {
|
||||
generation: self.generation.load(Ordering::SeqCst),
|
||||
|
|
@ -231,20 +275,16 @@ impl MtpManager {
|
|||
self.enabled.load(Ordering::SeqCst) && self.generation.load(Ordering::SeqCst) == generation
|
||||
}
|
||||
|
||||
async fn request(
|
||||
&self,
|
||||
type_name: &str,
|
||||
data: Value,
|
||||
id: Option<u32>,
|
||||
) -> Result<Value, String> {
|
||||
async fn request(&self, type_name: &str, data: Value) -> Result<Value, String> {
|
||||
let connection = self
|
||||
.connection
|
||||
.read()
|
||||
.map_err(|_| "MTP connection lock is unavailable")?
|
||||
.clone()
|
||||
.ok_or_else(|| "MTP is not connected".to_string())?;
|
||||
let request = json_to_frame(type_name, data, id)?;
|
||||
let request = json_to_frame(type_name, data, connection.next_request_id().await?)?;
|
||||
let response = connection
|
||||
.mtp
|
||||
.request(&request, None)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
|
@ -261,9 +301,12 @@ async fn supervise(generation: u64) {
|
|||
manager.log(2, "Starting native MTP connection", None);
|
||||
android_status("Connecting");
|
||||
match connect(&config).await {
|
||||
Ok((connection, state)) => {
|
||||
Ok(prepared) => {
|
||||
delay = Duration::from_secs(1);
|
||||
let connection = Arc::new(connection);
|
||||
let connection = Arc::new(ManagedConnection {
|
||||
mtp: Arc::new(prepared.connection),
|
||||
request_ids: prepared.request_ids,
|
||||
});
|
||||
let stale = {
|
||||
let _guard = manager.start_lock.lock().expect("start lock poisoned");
|
||||
let mut current = manager
|
||||
|
|
@ -279,22 +322,47 @@ async fn supervise(generation: u64) {
|
|||
generation,
|
||||
ready_state: CONNECTED,
|
||||
identified: true,
|
||||
state: Some(state),
|
||||
state: Some(prepared.initial_state),
|
||||
error: None,
|
||||
});
|
||||
false
|
||||
}
|
||||
};
|
||||
if stale {
|
||||
connection.sender.close().await;
|
||||
connection.mtp.sender.close().await;
|
||||
break;
|
||||
}
|
||||
android_status("Connected");
|
||||
manager.log(2, "Native MTP connection established", None);
|
||||
|
||||
let (notification_tx, mut notification_rx) =
|
||||
mpsc::channel(NOTIFICATION_QUEUE_CAPACITY);
|
||||
let notification_connection = connection.clone();
|
||||
let notification_config = config.clone();
|
||||
let notification_worker = tokio::spawn(async move {
|
||||
while let Some(frame) = notification_rx.recv().await {
|
||||
if !manager().is_current(generation) {
|
||||
break;
|
||||
}
|
||||
if let Err(error) = notify_message(
|
||||
¬ification_config,
|
||||
notification_connection.clone(),
|
||||
&frame,
|
||||
)
|
||||
.await
|
||||
{
|
||||
eprintln!("failed to create background message notification: {error}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for frame in prepared.buffered_frames {
|
||||
handle_push(generation, ¬ification_tx, frame).await;
|
||||
}
|
||||
|
||||
while manager.is_current(generation) {
|
||||
match connection.receive().await {
|
||||
Ok(frame) => handle_push(generation, connection.clone(), frame).await,
|
||||
match connection.mtp.receive().await {
|
||||
Ok(frame) => handle_push(generation, ¬ification_tx, frame).await,
|
||||
Err(error) => {
|
||||
let _guard = manager.start_lock.lock().expect("start lock poisoned");
|
||||
if manager.is_current(generation) {
|
||||
|
|
@ -310,6 +378,13 @@ async fn supervise(generation: u64) {
|
|||
}
|
||||
}
|
||||
}
|
||||
drop(notification_tx);
|
||||
notification_worker.abort();
|
||||
if let Err(error) = notification_worker.await {
|
||||
if !error.is_cancelled() {
|
||||
eprintln!("background notification worker failed: {error}");
|
||||
}
|
||||
}
|
||||
let mut current = manager
|
||||
.connection
|
||||
.write()
|
||||
|
|
@ -339,12 +414,21 @@ async fn supervise(generation: u64) {
|
|||
break;
|
||||
}
|
||||
android_status("Reconnecting");
|
||||
tokio::time::sleep(delay).await;
|
||||
tokio::time::sleep(jittered_retry_delay(delay)).await;
|
||||
delay = (delay * 2).min(Duration::from_secs(60));
|
||||
}
|
||||
}
|
||||
|
||||
async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
|
||||
fn jittered_retry_delay(delay: Duration) -> Duration {
|
||||
let entropy = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|duration| duration.subsec_nanos())
|
||||
.unwrap_or_default();
|
||||
let percent = 80 + entropy % 41;
|
||||
delay.mul_f64(percent as f64 / 100.0)
|
||||
}
|
||||
|
||||
async fn connect(config: &MtpConfig) -> Result<PreparedConnection, String> {
|
||||
let (url, public_key) = resolve_endpoint(config)
|
||||
.await
|
||||
.map_err(|error| format!("endpoint discovery failed: {error}"))?;
|
||||
|
|
@ -381,39 +465,10 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
|
|||
.map_err(|error| format!("transport authentication failed: {error}"))?;
|
||||
manager().log(2, "Native MTP authentication completed", None);
|
||||
|
||||
let connected = CommunicationValue::new(CommunicationType::ClientConnected)
|
||||
.add_typed_default(
|
||||
DataType::SessionId,
|
||||
DataValue::UnsignedNumber(current_millis() as u128),
|
||||
)
|
||||
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0))
|
||||
.add_typed_default(DataType::CacheValid, DataValue::BoolFalse)
|
||||
.add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0));
|
||||
let state = tokio::time::timeout(
|
||||
Duration::from_secs(30),
|
||||
connection.request(&connected, None),
|
||||
)
|
||||
.await
|
||||
.map_err(|_| "initial state synchronization timed out".to_string())?
|
||||
.map_err(|error| format!("initial state synchronization failed: {error}"))?;
|
||||
if !state.is_type(CommunicationType::ClientStateSync) {
|
||||
return Err(format!(
|
||||
"expected ClientStateSync, received {}",
|
||||
state.get_type_name().unwrap_or("unknown")
|
||||
));
|
||||
}
|
||||
let session_id = state
|
||||
.get_data(DataType::SessionId)
|
||||
.as_number()
|
||||
.ok_or("ClientStateSync omitted SessionId")?;
|
||||
let version = state
|
||||
.get_data(DataType::VersionNumber)
|
||||
.as_number()
|
||||
.ok_or("ClientStateSync omitted VersionNumber")?;
|
||||
let ack = CommunicationValue::new(CommunicationType::ClientStateAck)
|
||||
.add_typed_default(DataType::SessionId, number_to_data(session_id))
|
||||
.add_typed_default(DataType::VersionNumber, number_to_data(version));
|
||||
let response = tokio::time::timeout(Duration::from_secs(30), connection.request(&ack, None))
|
||||
let (state, buffered_frames) = await_initial_state(&connection).await?;
|
||||
let request_ids = RequestIdAllocator::new();
|
||||
let (initial_state, ack) = prepare_initial_state_ack(&state, &request_ids)?;
|
||||
let response = tokio::time::timeout(INITIAL_SYNC_TIMEOUT, connection.request(&ack, None))
|
||||
.await
|
||||
.map_err(|_| "state acknowledgement timed out".to_string())?
|
||||
.map_err(|error| format!("state acknowledgement failed: {error}"))?;
|
||||
|
|
@ -423,7 +478,182 @@ async fn connect(config: &MtpConfig) -> Result<(MTPConnection, Value), String> {
|
|||
{
|
||||
return Err(format!("ClientStateAck failed: {response}"));
|
||||
}
|
||||
Ok((connection, frame_data_to_json(&state)?))
|
||||
Ok(PreparedConnection {
|
||||
connection,
|
||||
request_ids,
|
||||
initial_state,
|
||||
buffered_frames,
|
||||
})
|
||||
}
|
||||
|
||||
fn prepare_initial_state_ack(
|
||||
state: &CommunicationValue,
|
||||
request_ids: &RequestIdAllocator,
|
||||
) -> Result<(Value, CommunicationValue), String> {
|
||||
let initial_state = frame_data_to_json(state)?;
|
||||
validate_client_state_sync(&initial_state)?;
|
||||
let data = initial_state
|
||||
.as_object()
|
||||
.ok_or("ClientStateSync payload is not an object")?;
|
||||
let session_id = required_integer(data, "SessionId")?;
|
||||
let version = required_integer(data, "VersionNumber")?;
|
||||
let ack = CommunicationValue::new(CommunicationType::ClientStateAck)
|
||||
.with_id(request_ids.next()?)
|
||||
.add_typed_default(DataType::SessionId, number_to_data(session_id))
|
||||
.add_typed_default(DataType::VersionNumber, number_to_data(version));
|
||||
Ok((initial_state, ack))
|
||||
}
|
||||
|
||||
fn validate_client_state_sync(state: &Value) -> Result<(), String> {
|
||||
let data = state
|
||||
.as_object()
|
||||
.ok_or("ClientStateSync payload is not an object")?;
|
||||
if required_integer(data, "SessionId")? <= 0 {
|
||||
return Err("ClientStateSync SessionId must be a positive integer".into());
|
||||
}
|
||||
for field in ["VersionNumber", "CacheSchemaVersion"] {
|
||||
if required_integer(data, field)? < 0 {
|
||||
return Err(format!("ClientStateSync {field} must be nonnegative"));
|
||||
}
|
||||
}
|
||||
match data.get("SyncMode").and_then(Value::as_str) {
|
||||
Some("full" | "delta") => {}
|
||||
_ => return Err("ClientStateSync SyncMode must be 'full' or 'delta'".into()),
|
||||
}
|
||||
for field in ["Contacts", "Communities", "Calls", "Messages"] {
|
||||
if !data.get(field).is_some_and(Value::is_array) {
|
||||
return Err(format!("ClientStateSync {field} must be an array"));
|
||||
}
|
||||
}
|
||||
for field in ["DeletedMessageIds", "DeletedContactIds"] {
|
||||
if let Some(value) = data.get(field) {
|
||||
let values = value
|
||||
.as_array()
|
||||
.ok_or_else(|| format!("ClientStateSync {field} must be an array"))?;
|
||||
if values.iter().any(|value| !value.is_number()) {
|
||||
return Err(format!("ClientStateSync {field} must contain numbers"));
|
||||
}
|
||||
}
|
||||
}
|
||||
validate_object_array(data, "Communities", |_| Ok(()))?;
|
||||
validate_object_array(data, "Contacts", validate_contact)?;
|
||||
validate_object_array(data, "Calls", validate_call)?;
|
||||
validate_object_array(data, "Messages", validate_message)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_object_array(
|
||||
data: &Map<String, Value>,
|
||||
field: &str,
|
||||
validate: impl Fn(&Map<String, Value>) -> Result<(), String>,
|
||||
) -> Result<(), String> {
|
||||
let values = data
|
||||
.get(field)
|
||||
.and_then(Value::as_array)
|
||||
.ok_or_else(|| format!("ClientStateSync {field} must be an array"))?;
|
||||
for value in values {
|
||||
let object = value
|
||||
.as_object()
|
||||
.ok_or_else(|| format!("ClientStateSync {field} entries must be objects"))?;
|
||||
validate(object)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_contact(contact: &Map<String, Value>) -> Result<(), String> {
|
||||
if !contact.get("UserId").is_some_and(Value::is_number) {
|
||||
return Err("ClientStateSync contact omitted numeric UserId".into());
|
||||
}
|
||||
if let Some(messages) = contact.get("Messages") {
|
||||
let messages = messages
|
||||
.as_array()
|
||||
.ok_or("ClientStateSync contact Messages must be an array")?;
|
||||
for message in messages {
|
||||
validate_message(
|
||||
message
|
||||
.as_object()
|
||||
.ok_or("ClientStateSync contact message must be an object")?,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_call(call: &Map<String, Value>) -> Result<(), String> {
|
||||
if !call.get("CallId").is_some_and(Value::is_string) {
|
||||
return Err("ClientStateSync call omitted string CallId".into());
|
||||
}
|
||||
let members = call
|
||||
.get("CallMembers")
|
||||
.and_then(Value::as_array)
|
||||
.ok_or("ClientStateSync call omitted CallMembers array")?;
|
||||
if members.iter().any(|member| !member.is_number()) {
|
||||
return Err("ClientStateSync CallMembers must contain numbers".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn validate_message(message: &Map<String, Value>) -> Result<(), String> {
|
||||
for field in ["SenderId", "SendTime"] {
|
||||
if !message.get(field).is_some_and(Value::is_number) {
|
||||
return Err(format!("ClientStateSync message omitted numeric {field}"));
|
||||
}
|
||||
}
|
||||
let content = message
|
||||
.get("Content")
|
||||
.and_then(Value::as_str)
|
||||
.ok_or("ClientStateSync message omitted string Content")?;
|
||||
STANDARD
|
||||
.decode(content)
|
||||
.or_else(|_| STANDARD_NO_PAD.decode(content))
|
||||
.map_err(|_| "ClientStateSync message Content must be base64".to_string())?;
|
||||
if let Some(state) = message.get("MessageState") {
|
||||
match state.as_str() {
|
||||
Some("read" | "received" | "sent" | "sending" | "awaiting") => {}
|
||||
_ => return Err("ClientStateSync message has invalid MessageState".into()),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn required_integer(data: &Map<String, Value>, field: &str) -> Result<i128, String> {
|
||||
let value = data
|
||||
.get(field)
|
||||
.ok_or_else(|| format!("ClientStateSync omitted {field}"))?;
|
||||
if let Some(value) = value.as_i64() {
|
||||
return Ok(value as i128);
|
||||
}
|
||||
value
|
||||
.as_u64()
|
||||
.map(|value| value as i128)
|
||||
.ok_or_else(|| format!("ClientStateSync {field} must be an integer"))
|
||||
}
|
||||
|
||||
async fn await_initial_state(
|
||||
connection: &MTPConnection,
|
||||
) -> Result<(CommunicationValue, Vec<CommunicationValue>), String> {
|
||||
let mut buffered = Vec::new();
|
||||
let deadline = tokio::time::Instant::now() + INITIAL_SYNC_TIMEOUT;
|
||||
|
||||
loop {
|
||||
let frame = tokio::time::timeout_at(deadline, connection.receive())
|
||||
.await
|
||||
.map_err(|_| "initial state synchronization timed out".to_string())?
|
||||
.map_err(|error| format!("initial state synchronization failed: {error}"))?;
|
||||
|
||||
if frame.is_type(CommunicationType::ErrorNoIota) {
|
||||
return Err("No Iota is currently connected".into());
|
||||
}
|
||||
|
||||
if frame.is_type(CommunicationType::ClientStateSync) {
|
||||
return Ok((frame, buffered));
|
||||
}
|
||||
|
||||
if buffered.len() == MAX_BUFFERED_INITIAL_FRAMES {
|
||||
return Err("initial state synchronization buffered too many frames".into());
|
||||
}
|
||||
buffered.push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
async fn resolve_endpoint(config: &MtpConfig) -> Result<(String, String), String> {
|
||||
|
|
@ -465,7 +695,11 @@ async fn resolve_endpoint(config: &MtpConfig) -> Result<(String, String), String
|
|||
))
|
||||
}
|
||||
|
||||
async fn handle_push(generation: u64, connection: Arc<MTPConnection>, frame: CommunicationValue) {
|
||||
async fn handle_push(
|
||||
generation: u64,
|
||||
notification_tx: &mpsc::Sender<CommunicationValue>,
|
||||
frame: CommunicationValue,
|
||||
) {
|
||||
let manager = manager();
|
||||
if let Ok(message) = frame_to_json(&frame) {
|
||||
manager.emit(MtpEvent::Message {
|
||||
|
|
@ -478,7 +712,7 @@ async fn handle_push(generation: u64, connection: Arc<MTPConnection>, frame: Com
|
|||
{
|
||||
if let Some(partner_id) = frame
|
||||
.get_data(DataType::ChatPartnerId)
|
||||
.as_number()
|
||||
.and_then(DataValue::as_number)
|
||||
.and_then(|value| u64::try_from(value).ok())
|
||||
{
|
||||
if let Err(error) = android_cancel_notification(partner_id) {
|
||||
|
|
@ -487,40 +721,39 @@ async fn handle_push(generation: u64, connection: Arc<MTPConnection>, frame: Com
|
|||
}
|
||||
}
|
||||
if frame.is_type(CommunicationType::MessageLive) && !manager.ui_visible.load(Ordering::SeqCst) {
|
||||
if let Err(error) = notify_message(connection, &frame).await {
|
||||
eprintln!("failed to create background message notification: {error}");
|
||||
if notification_tx.try_send(frame).is_err() {
|
||||
eprintln!("background message notification queue is full");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn notify_message(
|
||||
connection: Arc<MTPConnection>,
|
||||
config: &MtpConfig,
|
||||
connection: Arc<ManagedConnection>,
|
||||
frame: &CommunicationValue,
|
||||
) -> Result<(), String> {
|
||||
let sender_id = frame
|
||||
.get_data(DataType::SenderId)
|
||||
.as_number()
|
||||
.and_then(DataValue::as_number)
|
||||
.and_then(|value| u64::try_from(value).ok())
|
||||
.ok_or("MessageLive omitted SenderId")?;
|
||||
let message = frame.get_data(DataType::Message);
|
||||
let content = container_value(message, DataType::Content)
|
||||
let message = frame
|
||||
.get_data(DataType::Message)
|
||||
.ok_or("MessageLive omitted Message")?;
|
||||
let content = container_value(message, DataType::AppContent)
|
||||
.and_then(DataValue::as_str)
|
||||
.ok_or("MessageLive omitted Content")?;
|
||||
let config = manager()
|
||||
.config
|
||||
.read()
|
||||
.expect("config lock poisoned")
|
||||
.clone()
|
||||
.ok_or("missing config")?;
|
||||
let keyring_bytes = decode_browser_base64(&config.keyring)?;
|
||||
let keyring = Keyring::from_bytes(&keyring_bytes).map_err(|error| error.to_string())?;
|
||||
let chat_id = derive_chat_id(config.user_id, sender_id);
|
||||
let secret_id = format!("chat:{chat_id}:main");
|
||||
let secret_request = CommunicationValue::new(CommunicationType::GetChatSecret)
|
||||
.with_id(connection.next_request_id().await?)
|
||||
.add_typed_default(DataType::UserId, DataValue::Str(config.user_id.to_string()))
|
||||
.add_typed_default(DataType::ChatId, DataValue::Str(chat_id.clone()))
|
||||
.add_typed_default(DataType::SecretId, DataValue::Str(secret_id.clone()));
|
||||
let secret = connection
|
||||
.mtp
|
||||
.request(&secret_request, None)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
|
@ -532,7 +765,7 @@ async fn notify_message(
|
|||
}
|
||||
let version = secret
|
||||
.get_data(DataType::VersionNumber)
|
||||
.as_number()
|
||||
.and_then(DataValue::as_number)
|
||||
.ok_or("missing secret version")?;
|
||||
let encrypted_secret = secret
|
||||
.get_bytes(DataType::EncryptedSecret)
|
||||
|
|
@ -560,11 +793,14 @@ async fn notify_message(
|
|||
.decrypt(&ciphertext, b"")
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
||||
let user_request = CommunicationValue::new(CommunicationType::GetUserData).add_typed_default(
|
||||
DataType::UserId,
|
||||
DataValue::UnsignedNumber(sender_id as u128),
|
||||
);
|
||||
let user_request = CommunicationValue::new(CommunicationType::GetUserData)
|
||||
.with_id(connection.next_request_id().await?)
|
||||
.add_typed_default(
|
||||
DataType::UserId,
|
||||
DataValue::UnsignedNumber(sender_id as u128),
|
||||
);
|
||||
let user = connection
|
||||
.mtp
|
||||
.request(&user_request, None)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
|
|
@ -652,24 +888,42 @@ fn container_value(value: &DataValue, field: DataType) -> Option<&DataValue> {
|
|||
value.get_field(id)
|
||||
}
|
||||
|
||||
fn json_to_frame(
|
||||
type_name: &str,
|
||||
data: Value,
|
||||
id: Option<u32>,
|
||||
) -> Result<CommunicationValue, String> {
|
||||
fn wire_field_name(name: &str) -> &str {
|
||||
match name {
|
||||
"Content" => "AppContent",
|
||||
"CreatedAt" => "AppCreatedAt",
|
||||
"MessageId" => "AppMessageId",
|
||||
_ => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn application_field_name(name: &str) -> &str {
|
||||
match name {
|
||||
"AppContent" => "Content",
|
||||
"AppCreatedAt" => "CreatedAt",
|
||||
"AppMessageId" => "MessageId",
|
||||
_ => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn json_to_frame(type_name: &str, data: Value, id: u32) -> Result<CommunicationValue, String> {
|
||||
let comm_type = CommunicationType::from_name(type_name)
|
||||
.ok_or_else(|| format!("unknown communication type: {type_name}"))?;
|
||||
let mut frame = CommunicationValue::new(comm_type);
|
||||
if let Some(id) = id {
|
||||
frame = frame.with_id(id);
|
||||
}
|
||||
let mut frame = CommunicationValue::new(comm_type).with_id(id);
|
||||
let Value::Object(fields) = data else {
|
||||
return Err("MTP request data must be an object".into());
|
||||
};
|
||||
let mut translated_fields = std::collections::HashSet::<String>::with_capacity(fields.len());
|
||||
for (name, value) in fields {
|
||||
let wire_name = wire_field_name(&name).to_owned();
|
||||
if !translated_fields.insert(wire_name.clone()) {
|
||||
return Err(format!(
|
||||
"duplicate MTP field after translation: {wire_name}"
|
||||
));
|
||||
}
|
||||
let data_type =
|
||||
DataType::from_name(&name).ok_or_else(|| format!("unknown data type: {name}"))?;
|
||||
frame = frame.add_typed_default(data_type, json_to_data(&name, value)?);
|
||||
DataType::from_name(&wire_name).ok_or_else(|| format!("unknown data type: {name}"))?;
|
||||
frame = frame.add_typed_default(data_type, json_to_data(&wire_name, value)?);
|
||||
}
|
||||
Ok(frame)
|
||||
}
|
||||
|
|
@ -707,13 +961,21 @@ fn json_to_data(field: &str, value: Value) -> Result<DataValue, String> {
|
|||
),
|
||||
Value::Object(fields) => {
|
||||
let mut entries = Vec::with_capacity(fields.len());
|
||||
let mut translated_fields =
|
||||
std::collections::HashSet::<String>::with_capacity(fields.len());
|
||||
for (name, value) in fields {
|
||||
let data_type = DataType::from_name(&name)
|
||||
let wire_name = wire_field_name(&name).to_owned();
|
||||
if !translated_fields.insert(wire_name.clone()) {
|
||||
return Err(format!(
|
||||
"duplicate MTP field after translation: {wire_name}"
|
||||
));
|
||||
}
|
||||
let data_type = DataType::from_name(&wire_name)
|
||||
.ok_or_else(|| format!("unknown nested data type: {name}"))?;
|
||||
let id = data_type
|
||||
.try_to_id(&TypeMap::latest())
|
||||
.ok_or_else(|| format!("unmapped data type: {name}"))?;
|
||||
entries.push((id, json_to_data(&name, value)?));
|
||||
entries.push((id, json_to_data(&wire_name, value)?));
|
||||
}
|
||||
DataValue::Container(entries)
|
||||
}
|
||||
|
|
@ -737,8 +999,8 @@ fn is_bytes_field(field: &str) -> bool {
|
|||
|
||||
fn frame_to_json(frame: &CommunicationValue) -> Result<Value, String> {
|
||||
let mut result = Map::new();
|
||||
if frame.get_id() != 0 {
|
||||
result.insert("id".into(), Value::from(frame.get_id()));
|
||||
if let Some(id) = frame.id() {
|
||||
result.insert("id".into(), Value::from(id));
|
||||
}
|
||||
result.insert(
|
||||
"type".into(),
|
||||
|
|
@ -751,11 +1013,20 @@ fn frame_to_json(frame: &CommunicationValue) -> Result<Value, String> {
|
|||
fn frame_data_to_json(frame: &CommunicationValue) -> Result<Value, String> {
|
||||
let map = frame.type_map().cloned().unwrap_or_else(TypeMap::latest);
|
||||
let mut result = Map::new();
|
||||
for (id, value) in frame.data() {
|
||||
let entries = frame
|
||||
.data()
|
||||
.ok_or("MTP frame payload is not a data container")?;
|
||||
for (id, value) in entries {
|
||||
let name = map
|
||||
.data_type_name(id.0)
|
||||
.ok_or_else(|| format!("unknown data type id: {}", id.0))?;
|
||||
result.insert(name.to_owned(), data_to_json(value, &map)?);
|
||||
let application_name = application_field_name(name);
|
||||
if result.contains_key(application_name) {
|
||||
return Err(format!(
|
||||
"duplicate MTP field after translation: {application_name}"
|
||||
));
|
||||
}
|
||||
result.insert(application_name.to_owned(), data_to_json(value, &map)?);
|
||||
}
|
||||
Ok(Value::Object(result))
|
||||
}
|
||||
|
|
@ -786,7 +1057,13 @@ fn data_to_json(value: &DataValue, map: &TypeMap) -> Result<Value, String> {
|
|||
let name = map
|
||||
.data_type_name(id.0)
|
||||
.ok_or_else(|| format!("unknown nested data type id: {}", id.0))?;
|
||||
object.insert(name.to_owned(), data_to_json(value, map)?);
|
||||
let application_name = application_field_name(name);
|
||||
if object.contains_key(application_name) {
|
||||
return Err(format!(
|
||||
"duplicate MTP field after translation: {application_name}"
|
||||
));
|
||||
}
|
||||
object.insert(application_name.to_owned(), data_to_json(value, map)?);
|
||||
}
|
||||
Value::Object(object)
|
||||
}
|
||||
|
|
@ -801,16 +1078,15 @@ fn number_to_json(value: i128) -> Result<Value, String> {
|
|||
.map_err(|_| "number exceeds JSON range".into())
|
||||
}
|
||||
|
||||
fn current_millis() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as u64
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{decode_browser_base64, decode_sdk_bytes};
|
||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||
use serde_json::json;
|
||||
|
||||
use super::{
|
||||
container_value, decode_browser_base64, decode_sdk_bytes, frame_to_json,
|
||||
jittered_retry_delay, json_to_frame, prepare_initial_state_ack, RequestIdAllocator,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn browser_base64_accepts_file_whitespace_and_missing_padding() {
|
||||
|
|
@ -823,11 +1099,148 @@ mod tests {
|
|||
assert_eq!(decode_sdk_bytes("0x01:02-ff").unwrap(), [1, 2, 255]);
|
||||
assert_eq!(decode_sdk_bytes("AQI=").unwrap(), [1, 2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_ids_are_nonzero_and_monotonic() {
|
||||
let ids = RequestIdAllocator::new();
|
||||
|
||||
assert_eq!(ids.next().unwrap(), 1);
|
||||
assert_eq!(ids.next().unwrap(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn retry_jitter_stays_within_policy_bounds() {
|
||||
let delay = jittered_retry_delay(std::time::Duration::from_secs(10));
|
||||
|
||||
assert!(delay >= std::time::Duration::from_secs(8));
|
||||
assert!(delay <= std::time::Duration::from_secs(12));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn json_content_uses_app_content_wire_type() {
|
||||
let frame = json_to_frame(
|
||||
"MessageEdit",
|
||||
json!({
|
||||
"Content": "ciphertext",
|
||||
"ChatPartnerId": 42,
|
||||
"SendTime": 10,
|
||||
}),
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
frame
|
||||
.get_data(DataType::AppContent)
|
||||
.and_then(DataValue::as_str),
|
||||
Some("ciphertext")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_json_content_uses_app_content_wire_type() {
|
||||
let frame = json_to_frame(
|
||||
"MessageEdit",
|
||||
json!({
|
||||
"Message": { "Content": "ciphertext" },
|
||||
}),
|
||||
1,
|
||||
)
|
||||
.unwrap();
|
||||
let message = frame.get_data(DataType::Message).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
container_value(message, DataType::AppContent).and_then(DataValue::as_str),
|
||||
Some("ciphertext")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_content_is_exposed_as_content_to_frontend() {
|
||||
let frame = CommunicationValue::new(CommunicationType::MessageEditLive)
|
||||
.with_id(1)
|
||||
.add_typed_default(DataType::AppContent, DataValue::Str("ciphertext".into()));
|
||||
|
||||
let json = frame_to_json(&frame).unwrap();
|
||||
|
||||
assert_eq!(json["data"]["Content"], "ciphertext");
|
||||
assert!(json["data"].get("AppContent").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn translated_field_collisions_are_rejected() {
|
||||
assert!(json_to_frame(
|
||||
"MessageEdit",
|
||||
json!({ "Content": "a", "AppContent": "b" }),
|
||||
1,
|
||||
)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
fn valid_initial_state() -> CommunicationValue {
|
||||
CommunicationValue::new(CommunicationType::ClientStateSync)
|
||||
.add_typed_default(DataType::SessionId, DataValue::UnsignedNumber(1))
|
||||
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0))
|
||||
.add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0))
|
||||
.add_typed_default(DataType::SyncMode, DataValue::Str("full".into()))
|
||||
.add_typed_default(DataType::Contacts, DataValue::Array(vec![]))
|
||||
.add_typed_default(DataType::Communities, DataValue::Array(vec![]))
|
||||
.add_typed_default(DataType::Calls, DataValue::Array(vec![]))
|
||||
.add_typed_default(DataType::Messages, DataValue::Array(vec![]))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_initial_state_is_prepared_before_ack() {
|
||||
let ids = RequestIdAllocator::new();
|
||||
let (state, ack) = prepare_initial_state_ack(&valid_initial_state(), &ids).unwrap();
|
||||
|
||||
assert_eq!(state["SyncMode"], "full");
|
||||
assert!(ack.is_type(CommunicationType::ClientStateAck));
|
||||
assert_eq!(ack.id(), Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_initial_state_does_not_prepare_ack() {
|
||||
let ids = RequestIdAllocator::new();
|
||||
let malformed = valid_initial_state()
|
||||
.add_typed_default(DataType::SyncMode, DataValue::Str("invalid".into()));
|
||||
|
||||
assert!(prepare_initial_state_ack(&malformed, &ids).is_err());
|
||||
assert_eq!(
|
||||
ids.next().unwrap(),
|
||||
1,
|
||||
"no acknowledgement ID was allocated"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn malformed_nested_initial_state_does_not_prepare_ack() {
|
||||
let ids = RequestIdAllocator::new();
|
||||
let malformed = CommunicationValue::new(CommunicationType::ClientStateSync)
|
||||
.add_typed_default(DataType::SessionId, DataValue::UnsignedNumber(1))
|
||||
.add_typed_default(DataType::VersionNumber, DataValue::UnsignedNumber(0))
|
||||
.add_typed_default(DataType::CacheSchemaVersion, DataValue::UnsignedNumber(0))
|
||||
.add_typed_default(DataType::SyncMode, DataValue::Str("full".into()))
|
||||
.add_typed_default(
|
||||
DataType::Contacts,
|
||||
DataValue::Array(vec![DataValue::Container(vec![])]),
|
||||
)
|
||||
.add_typed_default(DataType::Communities, DataValue::Array(vec![]))
|
||||
.add_typed_default(DataType::Calls, DataValue::Array(vec![]))
|
||||
.add_typed_default(DataType::Messages, DataValue::Array(vec![]));
|
||||
|
||||
assert!(prepare_initial_state_ack(&malformed, &ids).is_err());
|
||||
assert_eq!(
|
||||
ids.next().unwrap(),
|
||||
1,
|
||||
"no acknowledgement ID was allocated"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn mtp_request(type_name: String, data: Value, id: Option<u32>) -> Result<Value, String> {
|
||||
manager().request(&type_name, data, id).await
|
||||
pub async fn mtp_request(type_name: String, data: Value) -> Result<Value, String> {
|
||||
manager().request(&type_name, data).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"esbuild": "^0.28.1",
|
||||
"eslint": "^10.8.0",
|
||||
"globals": "^17.9.0",
|
||||
"mtp": "*",
|
||||
"typescript": "~6.0.3",
|
||||
"typescript-eslint": "^8.66.0",
|
||||
"vite": "^8.2.1"
|
||||
|
|
|
|||
Loading…
Reference in a new issue