[Add] Mesage util

This commit is contained in:
Alex Emmet 2026-09-20 21:11:57 +02:00
commit 2b578693aa
No known key found for this signature in database
5 changed files with 671 additions and 293 deletions

191
Cargo.lock generated
View file

@ -21,9 +21,9 @@ dependencies = [
[[package]] [[package]]
name = "actix-http" name = "actix-http"
version = "3.13.5" version = "3.13.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86d62d1a48894ec9450bcde7ef1e3681205771ff6ea9c61cc5ae031145192787" checksum = "3c4f07a021937af76abef4c458a0d6b25f63f82eec5394354deeb7de0551f523"
dependencies = [ dependencies = [
"actix-codec", "actix-codec",
"actix-service", "actix-service",
@ -49,7 +49,7 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rand 0.10.2", "rand 0.10.3",
"sha1 0.11.0", "sha1 0.11.0",
"smallvec", "smallvec",
"tokio", "tokio",
@ -60,12 +60,13 @@ dependencies = [
[[package]] [[package]]
name = "actix-macros" name = "actix-macros"
version = "0.2.4" version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" checksum = "367f814ad4afbac74f07df5001214da65f65e185c90ef56c4dd8df23f8695b9b"
dependencies = [ dependencies = [
"proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -140,9 +141,9 @@ dependencies = [
[[package]] [[package]]
name = "actix-utils" name = "actix-utils"
version = "3.0.1" version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" checksum = "0128396dd7313f697ad05b21b1a7be7d4cbb81888704f55996e4a27db196bb4d"
dependencies = [ dependencies = [
"local-waker", "local-waker",
"pin-project-lite", "pin-project-lite",
@ -194,14 +195,14 @@ dependencies = [
[[package]] [[package]]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "4.3.0" version = "4.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f591380e2e68490b5dfaf1dd1aa0ebe78d84ba7067078512b4ea6e4492d622b8" checksum = "b96b09c4878563f8ab4a5fd0c59f9f0d6e0e9f60eb9b748526a0b9604fd89c50"
dependencies = [ dependencies = [
"actix-router", "actix-router",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -390,7 +391,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
"synstructure", "synstructure 0.13.2",
] ]
[[package]] [[package]]
@ -412,7 +413,7 @@ checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -621,9 +622,9 @@ dependencies = [
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.4.6" version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver", "jobserver",
@ -633,9 +634,9 @@ dependencies = [
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
[[package]] [[package]]
name = "cfg_aliases" name = "cfg_aliases"
@ -704,9 +705,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.6.6" version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "473c7e07f409a8d772161724aa8db6a765a2532a70f9667eeb7b49d3d02fbdca" checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -714,9 +715,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.6.6" version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b48fea5a88e9ae728a2dcbedbfc0e730f7d60da42e1cb049a83c9fb8b789889" checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -726,21 +727,21 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.6.4" version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "1.1.0" version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486"
[[package]] [[package]]
name = "client" name = "client"
@ -1064,7 +1065,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"strsim", "strsim",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -1075,7 +1076,7 @@ checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -1216,7 +1217,7 @@ checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -1406,15 +1407,15 @@ dependencies = [
[[package]] [[package]]
name = "find-msvc-tools" name = "find-msvc-tools"
version = "0.1.12" version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
[[package]] [[package]]
name = "finl_unicode" name = "finl_unicode"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9844ddc3a6e533d62bba727eb6c28b5d360921d5175e9ff0f1e621a5c590a4d5" checksum = "80bb028c8b4148c9ee0cca68fcd9add6044e81d3619f48577ddf13a263d047a2"
[[package]] [[package]]
name = "fixedbitset" name = "fixedbitset"
@ -1526,7 +1527,7 @@ checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -1879,9 +1880,9 @@ dependencies = [
[[package]] [[package]]
name = "hyper-rustls" name = "hyper-rustls"
version = "0.27.9" version = "0.27.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53"
dependencies = [ dependencies = [
"http 1.5.0", "http 1.5.0",
"hyper", "hyper",
@ -2063,9 +2064,9 @@ dependencies = [
[[package]] [[package]]
name = "impl-more" name = "impl-more"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edaff2ce006342d4d0e00fae676f7082dada44a203560629ba18ea50a19277bb" checksum = "30c0cddce6b7505483307994f60d97c4b156020e1504d0cf1f05a21b1b325e64"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
@ -2105,7 +2106,7 @@ dependencies = [
"indoc", "indoc",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -2698,9 +2699,9 @@ dependencies = [
[[package]] [[package]]
name = "lru-slab" name = "lru-slab"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f"
[[package]] [[package]]
name = "lzma-rust2" name = "lzma-rust2"
@ -2856,7 +2857,7 @@ dependencies = [
"mtp-common", "mtp-common",
"mtp-crypto", "mtp-crypto",
"mtp-native", "mtp-native",
"rand 0.10.2", "rand 0.10.3",
"tokio", "tokio",
] ]
@ -2870,7 +2871,7 @@ dependencies = [
"mtp-common", "mtp-common",
"mtp-crypto", "mtp-crypto",
"mtp-type-map", "mtp-type-map",
"rand 0.10.2", "rand 0.10.3",
"thiserror 2.0.20", "thiserror 2.0.20",
] ]
@ -2879,7 +2880,7 @@ name = "mtp-common"
version = "0.3.0" version = "0.3.0"
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=bb0f682b735de5ebb36bb41dc699260578341828#bb0f682b735de5ebb36bb41dc699260578341828" source = "git+https://git.methanium.net/Methanium/mtp.git?rev=bb0f682b735de5ebb36bb41dc699260578341828#bb0f682b735de5ebb36bb41dc699260578341828"
dependencies = [ dependencies = [
"rand 0.10.2", "rand 0.10.3",
"thiserror 2.0.20", "thiserror 2.0.20",
] ]
@ -2908,7 +2909,7 @@ dependencies = [
"hkdf", "hkdf",
"ml-dsa", "ml-dsa",
"mlkem-tls", "mlkem-tls",
"rand 0.10.2", "rand 0.10.3",
"rand_core 0.6.4", "rand_core 0.6.4",
"rustls", "rustls",
"serde", "serde",
@ -2924,7 +2925,7 @@ version = "0.3.0"
source = "git+https://git.methanium.net/Methanium/mtp.git?rev=bb0f682b735de5ebb36bb41dc699260578341828#bb0f682b735de5ebb36bb41dc699260578341828" source = "git+https://git.methanium.net/Methanium/mtp.git?rev=bb0f682b735de5ebb36bb41dc699260578341828#bb0f682b735de5ebb36bb41dc699260578341828"
dependencies = [ dependencies = [
"mtp-crypto", "mtp-crypto",
"rand 0.10.2", "rand 0.10.3",
"thiserror 2.0.20", "thiserror 2.0.20",
"zeroize", "zeroize",
] ]
@ -2956,7 +2957,7 @@ dependencies = [
"mtp-core", "mtp-core",
"mtp-crypto", "mtp-crypto",
"mtp-native", "mtp-native",
"rand 0.10.2", "rand 0.10.3",
"thiserror 2.0.20", "thiserror 2.0.20",
"tokio", "tokio",
"tracing", "tracing",
@ -2973,7 +2974,7 @@ dependencies = [
"mtp-common", "mtp-common",
"mtp-core", "mtp-core",
"mtp-crypto", "mtp-crypto",
"rand 0.10.2", "rand 0.10.3",
"rcgen", "rcgen",
"rustls", "rustls",
"rustls-native-certs", "rustls-native-certs",
@ -3041,7 +3042,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"rustversion", "rustversion",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -3630,9 +3631,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
[[package]] [[package]]
name = "ppmd-rust" name = "ppmd-rust"
version = "1.4.1" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e9219bcb9d7aca6b2f63c83cf100cf78bcd619ac46e6ecbd0dd90869a39345d" checksum = "196a7c80b9a7652aba7cc070827516c2abe4ccdf53d128e1944003cf5726cff1"
[[package]] [[package]]
name = "ppv-lite86" name = "ppv-lite86"
@ -3654,9 +3655,9 @@ dependencies = [
[[package]] [[package]]
name = "quinn" name = "quinn"
version = "0.11.11" version = "0.11.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11"
dependencies = [ dependencies = [
"bytes", "bytes",
"cfg_aliases", "cfg_aliases",
@ -3675,16 +3676,16 @@ dependencies = [
[[package]] [[package]]
name = "quinn-proto" name = "quinn-proto"
version = "0.11.17" version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04759210543be93709136e28212294a659ef5001836ff4eab4d663e4529bba83" checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"bytes", "bytes",
"fastbloom", "fastbloom",
"getrandom 0.4.3", "getrandom 0.4.3",
"lru-slab", "lru-slab",
"rand 0.10.2", "rand 0.10.3",
"rand_pcg", "rand_pcg",
"ring", "ring",
"rustc-hash", "rustc-hash",
@ -3757,9 +3758,9 @@ dependencies = [
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.10.2" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" checksum = "65c9fb96cbc91e3478eaae79a69fcd3f1ae4ad052e471fe6732fff548984b4af"
dependencies = [ dependencies = [
"chacha20 0.10.2", "chacha20 0.10.2",
"getrandom 0.4.3", "getrandom 0.4.3",
@ -3803,7 +3804,7 @@ dependencies = [
[[package]] [[package]]
name = "ratatool" name = "ratatool"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.methanium.net/methanium/ratatool.git#f94b8a25ffc5c196db2852ff576593df5ad96938" source = "git+https://git.methanium.net/methanium/ratatool.git#ddd0ddea7235cfc1ca27f79cf166f40d3d1fdabe"
dependencies = [ dependencies = [
"crossterm", "crossterm",
"futures-util", "futures-util",
@ -4084,9 +4085,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "1.1.4" version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [ dependencies = [
"bitflags 2.13.2", "bitflags 2.13.2",
"errno", "errno",
@ -4097,9 +4098,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.23.44" version = "0.23.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634"
dependencies = [ dependencies = [
"aws-lc-rs", "aws-lc-rs",
"log", "log",
@ -4282,7 +4283,7 @@ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -4601,9 +4602,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "3.0.5" version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4630,6 +4631,17 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "synstructure"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02"
dependencies = [
"proc-macro2",
"quote",
"syn 3.0.6",
]
[[package]] [[package]]
name = "sysinfo" name = "sysinfo"
version = "0.38.4" version = "0.38.4"
@ -4672,7 +4684,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [ dependencies = [
"fastrand", "fastrand",
"getrandom 0.3.4", "getrandom 0.4.3",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.61.2", "windows-sys 0.61.2",
@ -4791,7 +4803,7 @@ checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -4838,18 +4850,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.13.2" version = "1.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4cf0ded5c4e56918d8f8a339e1bb67d038d3bc6d144ac407904015ba2e4cde9b" checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
@ -4876,7 +4879,7 @@ checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -5064,9 +5067,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@ -5267,7 +5270,7 @@ dependencies = [
"bumpalo", "bumpalo",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -5757,14 +5760,14 @@ dependencies = [
[[package]] [[package]]
name = "yoke-derive" name = "yoke-derive"
version = "0.8.2" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.6",
"synstructure", "synstructure 0.14.0",
] ]
[[package]] [[package]]
@ -5798,14 +5801,14 @@ dependencies = [
[[package]] [[package]]
name = "zerofrom-derive" name = "zerofrom-derive"
version = "0.1.7" version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 3.0.6",
"synstructure", "synstructure 0.14.0",
] ]
[[package]] [[package]]
@ -5858,7 +5861,7 @@ checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 3.0.5", "syn 3.0.6",
] ]
[[package]] [[package]]
@ -5890,9 +5893,9 @@ dependencies = [
[[package]] [[package]]
name = "zlib-rs" name = "zlib-rs"
version = "0.6.7" version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112"
[[package]] [[package]]
name = "zmij" name = "zmij"

View file

@ -31,6 +31,32 @@ pub struct MessageMutation {
pub send_time: i64, pub send_time: i64,
} }
#[derive(Clone, Debug)]
pub enum AppliedMessageMutation {
State {
send_time: i64,
state: MessageState,
},
Edit {
send_time: i64,
content: String,
version: i64,
},
Reaction {
send_time: i64,
reaction: String,
accepted: bool,
},
Delete {
send_time: i64,
},
}
#[derive(Clone, Debug, Default)]
pub struct RelayApplicationResult {
pub mutation: Option<AppliedMessageMutation>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct SettingMutation { pub struct SettingMutation {
pub response: CommunicationValue, pub response: CommunicationValue,
@ -189,7 +215,7 @@ pub fn apply_verified_relay_content(
accepted_at: i64, accepted_at: i64,
storage_owner: i64, storage_owner: i64,
sent_by_self: bool, sent_by_self: bool,
) -> Result<(), String> { ) -> Result<RelayApplicationResult, String> {
validate_relay_identity(context, &content.content)?; validate_relay_identity(context, &content.content)?;
let sender_id = i64::try_from(context.signer_id) let sender_id = i64::try_from(context.signer_id)
.map_err(|_| "Relay signer ID exceeds the local storage range".to_string())?; .map_err(|_| "Relay signer ID exceeds the local storage range".to_string())?;
@ -200,7 +226,7 @@ pub fn apply_verified_relay_content(
match content.message_type { match content.message_type {
CommunicationType::MessageState => { CommunicationType::MessageState => {
let partner_id = let _partner_id =
relay_number(&content.content, DataType::ChatPartnerId, &context.type_map) relay_number(&content.content, DataType::ChatPartnerId, &context.type_map)
.and_then(|value| i64::try_from(value).ok()) .and_then(|value| i64::try_from(value).ok())
.filter(|id| *id == recipient_id) .filter(|id| *id == recipient_id)
@ -218,31 +244,36 @@ pub fn apply_verified_relay_content(
.map(MessageState::from_str) .map(MessageState::from_str)
.filter(|state| matches!(state, MessageState::Received | MessageState::Read)) .filter(|state| matches!(state, MessageState::Received | MessageState::Read))
.ok_or_else(|| "Relay MessageState has an invalid state".to_string())?; .ok_or_else(|| "Relay MessageState has an invalid state".to_string())?;
if sent_by_self { let update = if sent_by_self {
return chat_files::change_message_state_by_relay_id( chat_files::change_message_state_by_relay_id(
storage_owner, storage_owner,
recipient_id, recipient_id,
recipient_principal, recipient_principal,
relay_message_id, relay_message_id,
state, state,
) )
.map_err(|error| error.to_string()); .map_err(|error| error.to_string())?
} } else {
chat_files::record_message_receipt( chat_files::record_message_receipt(
storage_owner, storage_owner,
recipient_id, recipient_id,
recipient_principal, recipient_principal,
relay_message_id, relay_message_id,
sender_id, sender_id,
signer_principal, signer_principal,
&context.message_id, &context.message_id,
state, state,
event_at, event_at,
now_millis_i64(), now_millis_i64(),
) )
.map_err(|error| error.to_string())?; .map_err(|error| error.to_string())?
let _ = partner_id; };
Ok(()) Ok(RelayApplicationResult {
mutation: update.map(|update| AppliedMessageMutation::State {
send_time: update.send_time,
state: update.state,
}),
})
} }
CommunicationType::MessageSend => { CommunicationType::MessageSend => {
let message = relay_string(&content.content, DataType::Content, &context.type_map) let message = relay_string(&content.content, DataType::Content, &context.type_map)
@ -328,7 +359,7 @@ pub fn apply_verified_relay_content(
}, },
}) })
.map_err(|error| error.to_string())?; .map_err(|error| error.to_string())?;
Ok(()) Ok(RelayApplicationResult::default())
} }
CommunicationType::MessageEdit => { CommunicationType::MessageEdit => {
let message = relay_string(&content.content, DataType::Content, &context.type_map) let message = relay_string(&content.content, DataType::Content, &context.type_map)
@ -336,6 +367,11 @@ pub fn apply_verified_relay_content(
let send_time = relay_number(&content.content, DataType::SendTime, &context.type_map) let send_time = relay_number(&content.content, DataType::SendTime, &context.type_map)
.and_then(|value| i64::try_from(value).ok()) .and_then(|value| i64::try_from(value).ok())
.ok_or_else(|| "Relay MessageEdit is missing SendTime".to_string())?; .ok_or_else(|| "Relay MessageEdit is missing SendTime".to_string())?;
let version =
relay_number(&content.content, DataType::VersionNumber, &context.type_map)
.and_then(|value| i64::try_from(value).ok())
.filter(|value| *value > 0)
.ok_or_else(|| "Relay MessageEdit has an invalid VersionNumber".to_string())?;
let external_user = if sent_by_self { let external_user = if sent_by_self {
recipient_id recipient_id
} else { } else {
@ -365,7 +401,14 @@ pub fn apply_verified_relay_content(
message, message,
) )
}; };
result.map_err(|error| error.to_string()) result.map_err(|error| error.to_string())?;
Ok(RelayApplicationResult {
mutation: Some(AppliedMessageMutation::Edit {
send_time,
content: message.to_string(),
version,
}),
})
} }
CommunicationType::MessageReactionAdd | CommunicationType::MessageReactionRemove => { CommunicationType::MessageReactionAdd | CommunicationType::MessageReactionRemove => {
let reaction = relay_string(&content.content, DataType::Reaction, &context.type_map) let reaction = relay_string(&content.content, DataType::Reaction, &context.type_map)
@ -405,7 +448,14 @@ pub fn apply_verified_relay_content(
reaction, reaction,
) )
}; };
result.map_err(|error| error.to_string()) result.map_err(|error| error.to_string())?;
Ok(RelayApplicationResult {
mutation: Some(AppliedMessageMutation::Reaction {
send_time,
reaction: reaction.to_string(),
accepted: content.message_type == CommunicationType::MessageReactionAdd,
}),
})
} }
CommunicationType::MessageDelete | CommunicationType::MessageDeleteLive => { CommunicationType::MessageDelete | CommunicationType::MessageDeleteLive => {
let send_time = relay_number(&content.content, DataType::SendTime, &context.type_map) let send_time = relay_number(&content.content, DataType::SendTime, &context.type_map)
@ -437,7 +487,10 @@ pub fn apply_verified_relay_content(
sender_id, sender_id,
) )
}; };
result.map_err(|error| error.to_string()) result.map_err(|error| error.to_string())?;
Ok(RelayApplicationResult {
mutation: Some(AppliedMessageMutation::Delete { send_time }),
})
} }
CommunicationType::SetChatSecret => { CommunicationType::SetChatSecret => {
let frame = CommunicationValue::new(CommunicationType::SetChatSecret) let frame = CommunicationValue::new(CommunicationType::SetChatSecret)
@ -526,7 +579,8 @@ pub fn apply_verified_relay_content(
created_at, created_at,
updated_at: now_millis_i64(), updated_at: now_millis_i64(),
}) })
.map_err(|error| error.to_string()) .map_err(|error| error.to_string())?;
Ok(RelayApplicationResult::default())
} }
CommunicationType::AddConversation => { CommunicationType::AddConversation => {
let other_id = let other_id =
@ -560,9 +614,10 @@ pub fn apply_verified_relay_content(
&context.type_map, &context.type_map,
), ),
) )
.map_err(|error| format!("AddConversation persistence failed: {error}")) .map_err(|error| format!("AddConversation persistence failed: {error}"))?;
Ok(RelayApplicationResult::default())
} }
_ => Ok(()), _ => Ok(RelayApplicationResult::default()),
} }
} }
@ -3003,7 +3058,7 @@ pub fn apply_relay_application_content(
context: &VerifiedRelayContext, context: &VerifiedRelayContext,
content: &VerifiedRelayContent, content: &VerifiedRelayContent,
accepted_at: i64, accepted_at: i64,
) -> Result<(), String> { ) -> Result<RelayApplicationResult, String> {
if application.signer_principal.user_id != context.signer_id if application.signer_principal.user_id != context.signer_id
|| application.recipient_principal.user_id != context.final_recipient_id || application.recipient_principal.user_id != context.final_recipient_id
{ {

View file

@ -457,6 +457,7 @@ impl RelayService {
let signer_principal = normalized.signer; let signer_principal = normalized.signer;
let recipient_principal = normalized.recipient; let recipient_principal = normalized.recipient;
let accepted_at = crate::message_common::now_millis_i64(); let accepted_at = crate::message_common::now_millis_i64();
let mut source_mutation = None;
let signer_id = match i64::try_from(verified.context.signer_id) { let signer_id = match i64::try_from(verified.context.signer_id) {
Ok(id) => id, Ok(id) => id,
Err(_) => { Err(_) => {
@ -653,7 +654,7 @@ impl RelayService {
return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData); return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData);
} }
}; };
if let Err(error) = message_handlers::apply_verified_relay_content( let application = match message_handlers::apply_verified_relay_content(
&verified.context, &verified.context,
&content, &content,
signer_principal.handle, signer_principal.handle,
@ -662,13 +663,17 @@ impl RelayService {
owner, owner,
true, true,
) { ) {
log!("Relay shared-Iota origin application failed: {}", error); Ok(application) => application,
let _ = relay_replay::mark_rejected( Err(error) => {
signer_principal.handle, log!("Relay shared-Iota origin application failed: {}", error);
&verified.context.message_id, let _ = relay_replay::mark_rejected(
); signer_principal.handle,
return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData); &verified.context.message_id,
} );
return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData);
}
};
source_mutation = application.mutation;
if let Err(error) = chat_files::record_destination_iota_received( if let Err(error) = chat_files::record_destination_iota_received(
owner, owner,
signer_principal.handle, signer_principal.handle,
@ -703,22 +708,29 @@ impl RelayService {
); );
} }
}; };
if let Err(error) = message_handlers::apply_verified_relay_content( let application = match message_handlers::apply_verified_relay_content(
&verified.context, &verified.context,
&content, &content,
signer_principal.handle, signer_principal.handle,
recipient_principal.handle, recipient_principal.handle,
accepted_at, accepted_at,
i64::try_from(verified.context.signer_id).unwrap_or_default(), signer_id,
true, true,
) { ) {
log!("Relay origin application failed: {}", error); Ok(application) => application,
let _ = relay_replay::mark_rejected( Err(error) => {
signer_principal.handle, log!("Relay origin application failed: {}", error);
&verified.context.message_id, let _ = relay_replay::mark_rejected(
); signer_principal.handle,
return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData); &verified.context.message_id,
} );
return Self::relay_response(
frame.id(),
CommunicationType::ErrorInvalidData,
);
}
};
source_mutation = application.mutation;
} }
let Some(route) = route_destination(&recipient_principal.home) else { let Some(route) = route_destination(&recipient_principal.home) else {
log!("Relay recipient has no Iota route"); log!("Relay recipient has no Iota route");
@ -894,7 +906,18 @@ impl RelayService {
.with_id(frame_id); .with_id(frame_id);
return RelayOutcome::Accepted { return RelayOutcome::Accepted {
ingress_response: response, ingress_response: response,
local_deliveries: Vec::new(), local_deliveries: source_mutation
.as_ref()
.map(|mutation| {
vec![mutation_delivery(
mutation,
verified.context.signer_id,
verified.context.signer_id,
verified.context.final_recipient_id,
signer_principal.handle,
)]
})
.unwrap_or_default(),
}; };
} }
Ok(response) => { Ok(response) => {
@ -914,7 +937,18 @@ impl RelayService {
&verified.context.message_id, &verified.context.message_id,
accepted_at, accepted_at,
true, true,
Vec::new(), source_mutation
.as_ref()
.map(|mutation| {
vec![mutation_delivery(
mutation,
verified.context.signer_id,
verified.context.signer_id,
verified.context.final_recipient_id,
signer_principal.handle,
)]
})
.unwrap_or_default(),
); );
} else if let Ok(signer_id) = i64::try_from(verified.context.signer_id) { } else if let Ok(signer_id) = i64::try_from(verified.context.signer_id) {
if let Err(error) = if let Err(error) =
@ -971,7 +1005,18 @@ impl RelayService {
&verified.context.message_id, &verified.context.message_id,
accepted_at, accepted_at,
true, true,
Vec::new(), source_mutation
.as_ref()
.map(|mutation| {
vec![mutation_delivery(
mutation,
verified.context.signer_id,
verified.context.signer_id,
verified.context.final_recipient_id,
signer_principal.handle,
)]
})
.unwrap_or_default(),
); );
} }
} }
@ -1044,7 +1089,7 @@ impl RelayService {
return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData); return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData);
} }
}; };
if let Err(error) = message_handlers::apply_verified_relay_content( let application = match message_handlers::apply_verified_relay_content(
&verified.context, &verified.context,
&content, &content,
signer_principal.handle, signer_principal.handle,
@ -1062,26 +1107,42 @@ impl RelayService {
}, },
false, false,
) { ) {
log!("Relay application dispatch failed: {}", error); Ok(application) => application,
if let Err(queue_error) = Err(error) => {
relay_queue::remove_for_frame(RouteTarget::User(destination), frame_id) log!("Relay application dispatch failed: {}", error);
{ if let Err(queue_error) =
log!("Relay application queue cleanup failed: {}", queue_error); relay_queue::remove_for_frame(RouteTarget::User(destination), frame_id)
{
log!("Relay application queue cleanup failed: {}", queue_error);
}
let _ = relay_replay::mark_rejected(
signer_principal.handle,
&verified.context.message_id,
);
return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData);
} }
let _ = relay_replay::mark_rejected( };
signer_principal.handle, client_event = application
&verified.context.message_id, .mutation
); .as_ref()
return Self::relay_response(frame.id(), CommunicationType::ErrorInvalidData); .map(|mutation| {
} client_event_from_mutation(
client_event = client_event_from_relay( mutation,
content.message_type, verified.context.signer_id,
&content.content, destination,
verified.context.signer_id, verified.context.signer_id,
destination, )
&verified.context.message_id, })
accepted_at, .or_else(|| {
); client_event_from_relay(
content.message_type,
&content.content,
verified.context.signer_id,
destination,
&verified.context.message_id,
accepted_at,
)
});
if let Err(error) = if let Err(error) =
relay_replay::mark_applied(signer_principal.handle, &verified.context.message_id) relay_replay::mark_applied(signer_principal.handle, &verified.context.message_id)
{ {
@ -1109,19 +1170,29 @@ impl RelayService {
{ {
log!("Relay queue state update failed: {}", error); log!("Relay queue state update failed: {}", error);
} }
let mut local_deliveries = Vec::new();
if let Some(mutation) = source_mutation.as_ref() {
local_deliveries.push(mutation_delivery(
mutation,
verified.context.signer_id,
verified.context.signer_id,
verified.context.final_recipient_id,
signer_principal.handle,
));
}
if let Some(frame) = client_event {
local_deliveries.push(ClientDelivery {
recipient: recipient_principal.handle,
frame,
});
}
Self::relay_success( Self::relay_success(
frame.id(), frame.id(),
local_iota_id, local_iota_id,
&verified.context.message_id, &verified.context.message_id,
accepted_at, accepted_at,
signer_is_local, signer_is_local,
client_event local_deliveries,
.into_iter()
.map(|frame| ClientDelivery {
recipient: recipient_principal.handle,
frame,
})
.collect(),
) )
} }
@ -1239,7 +1310,12 @@ impl RelayService {
return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData); return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData);
} }
relay_replay::RelayReservation::Existing { state, .. } if state == "delivered" => { relay_replay::RelayReservation::Existing { state, .. } if state == "delivered" => {
return federated_success(frame_id, &normalized.message_id, accepted_at); return federated_success(
frame_id,
&normalized.message_id,
accepted_at,
Vec::new(),
);
} }
relay_replay::RelayReservation::Existing { state, .. } if state == "rejected" => { relay_replay::RelayReservation::Existing { state, .. } if state == "rejected" => {
return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData); return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData);
@ -1253,18 +1329,59 @@ impl RelayService {
); );
if let Some(recipient) = recipient_local { if let Some(recipient) = recipient_local {
if !already_applied let mut source_mutation = None;
&& let Err(error) = message_handlers::apply_relay_application_content( let mut destination_mutation = None;
&application, if !already_applied {
if let Some(sender) = signer_local {
let source_application = message_handlers::apply_relay_application_content(
&message_handlers::RelayApplicationContext {
hosted_sender: Some(sender),
hosted_recipient: None,
..application.clone()
},
&context,
&content,
accepted_at,
);
match source_application {
Ok(application) => source_mutation = application.mutation,
Err(error) => {
log!("Relay V2 shared-Iota origin application failed: {error}");
let _ = relay_replay::mark_rejected(
normalized.signer.handle,
&normalized.message_id,
);
return Self::relay_response(
Some(frame_id),
CommunicationType::ErrorInvalidData,
);
}
}
}
let destination_application = message_handlers::apply_relay_application_content(
&message_handlers::RelayApplicationContext {
hosted_sender: None,
hosted_recipient: Some(recipient),
..application.clone()
},
&context, &context,
&content, &content,
accepted_at, accepted_at,
) );
{ match destination_application {
log!("Relay V2 destination application failed: {error}"); Ok(application) => destination_mutation = application.mutation,
let _ = Err(error) => {
relay_replay::mark_rejected(normalized.signer.handle, &normalized.message_id); log!("Relay V2 destination application failed: {error}");
return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData); let _ = relay_replay::mark_rejected(
normalized.signer.handle,
&normalized.message_id,
);
return Self::relay_response(
Some(frame_id),
CommunicationType::ErrorInvalidData,
);
}
}
} }
let relay_identity = relay_queue::RelayIdentity { let relay_identity = relay_queue::RelayIdentity {
signer: normalized.signer.handle, signer: normalized.signer.handle,
@ -1285,45 +1402,86 @@ impl RelayService {
return Self::relay_response(Some(frame_id), CommunicationType::ErrorInternal); return Self::relay_response(Some(frame_id), CommunicationType::ErrorInternal);
} }
let _ = relay_replay::mark_queued(normalized.signer.handle, &normalized.message_id); let _ = relay_replay::mark_queued(normalized.signer.handle, &normalized.message_id);
let delivery = client_event_from_relay( let destination_event = destination_mutation
message_type, .as_ref()
&content.content, .map(|mutation| {
normalized.signer.principal.user_id, client_event_from_mutation(
normalized.recipient.principal.user_id, mutation,
normalized.signer.principal.user_id,
normalized.recipient.principal.user_id,
normalized.signer.principal.user_id,
)
})
.or_else(|| {
client_event_from_relay(
message_type,
&content.content,
normalized.signer.principal.user_id,
normalized.recipient.principal.user_id,
&normalized.message_id,
accepted_at,
)
});
let mut local_deliveries = Vec::new();
if let Some(mutation) = source_mutation.as_ref() {
local_deliveries.push(mutation_delivery(
mutation,
normalized.signer.principal.user_id,
normalized.signer.principal.user_id,
normalized.recipient.principal.user_id,
normalized.signer.handle,
));
}
if let Some(frame) = destination_event {
local_deliveries.push(ClientDelivery {
recipient: normalized.recipient.handle,
frame,
});
}
return federated_success(
frame_id,
&normalized.message_id, &normalized.message_id,
accepted_at, accepted_at,
local_deliveries,
); );
return match federated_success(frame_id, &normalized.message_id, accepted_at) {
RelayOutcome::Accepted {
ingress_response, ..
} => RelayOutcome::Accepted {
ingress_response,
local_deliveries: delivery
.into_iter()
.map(|frame| ClientDelivery {
recipient: normalized.recipient.handle,
frame,
})
.collect(),
},
outcome => outcome,
};
} }
let Some(signer) = signer_local else { let Some(signer) = signer_local else {
return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData); return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData);
}; };
if !already_applied let source_mutation = if already_applied {
&& let Err(error) = message_handlers::apply_relay_application_content( None
} else {
match message_handlers::apply_relay_application_content(
&application, &application,
&context, &context,
&content, &content,
accepted_at, accepted_at,
) ) {
{ Ok(application) => application.mutation,
log!("Relay V2 origin application failed: {error}"); Err(error) => {
return Self::relay_response(Some(frame_id), CommunicationType::ErrorInvalidData); log!("Relay V2 origin application failed: {error}");
} return Self::relay_response(
Some(frame_id),
CommunicationType::ErrorInvalidData,
);
}
}
};
let source_deliveries = || {
source_mutation
.as_ref()
.map(|mutation| {
vec![mutation_delivery(
mutation,
normalized.signer.principal.user_id,
normalized.signer.principal.user_id,
normalized.recipient.principal.user_id,
normalized.signer.handle,
)]
})
.unwrap_or_default()
};
let Some(route) = route_destination(&normalized.recipient.home) else { let Some(route) = route_destination(&normalized.recipient.home) else {
return Self::relay_response(Some(frame_id), CommunicationType::ErrorNoIota); return Self::relay_response(Some(frame_id), CommunicationType::ErrorNoIota);
}; };
@ -1368,7 +1526,12 @@ impl RelayService {
log!("Relay V2 acknowledgement storage failed: {error}"); log!("Relay V2 acknowledgement storage failed: {error}");
return Self::relay_response(Some(frame_id), CommunicationType::ErrorInternal); return Self::relay_response(Some(frame_id), CommunicationType::ErrorInternal);
} }
federated_success(frame_id, &relay_message_id, destination_accepted_at) federated_success(
frame_id,
&relay_message_id,
destination_accepted_at,
source_deliveries(),
)
} }
Ok(RouteOutcome::Accepted { .. }) => { Ok(RouteOutcome::Accepted { .. }) => {
let _ = iota_storage::util::relay_queue::quarantine_for_frame( let _ = iota_storage::util::relay_queue::quarantine_for_frame(
@ -1380,9 +1543,12 @@ impl RelayService {
Ok(RouteOutcome::Rejected { response_type }) => { Ok(RouteOutcome::Rejected { response_type }) => {
Self::relay_response(Some(frame_id), response_type) Self::relay_response(Some(frame_id), response_type)
} }
Ok(RouteOutcome::Retryable { .. }) | Err(_) => { Ok(RouteOutcome::Retryable { .. }) | Err(_) => federated_success(
federated_success(frame_id, &normalized.message_id, accepted_at) frame_id,
} &normalized.message_id,
accepted_at,
source_deliveries(),
),
} }
} }
@ -1495,7 +1661,12 @@ impl RelayService {
} }
} }
fn federated_success(frame_id: u32, message_id: &str, accepted_at: i64) -> RelayOutcome { fn federated_success(
frame_id: u32,
message_id: &str,
accepted_at: i64,
local_deliveries: Vec<ClientDelivery>,
) -> RelayOutcome {
RelayOutcome::Accepted { RelayOutcome::Accepted {
ingress_response: CommunicationValue::new(CommunicationType::Success) ingress_response: CommunicationValue::new(CommunicationType::Success)
.with_id(frame_id) .with_id(frame_id)
@ -1507,7 +1678,7 @@ fn federated_success(frame_id: u32, message_id: &str, accepted_at: i64) -> Relay
DataType::RelayAcceptedAt, DataType::RelayAcceptedAt,
DataValue::SignedNumber(accepted_at.into()), DataValue::SignedNumber(accepted_at.into()),
), ),
local_deliveries: Vec::new(), local_deliveries,
} }
} }
@ -1535,6 +1706,84 @@ fn record_origin_delivery_failure(
} }
} }
fn client_event_from_mutation(
mutation: &message_handlers::AppliedMessageMutation,
actor_id: u64,
account_id: u64,
chat_partner_id: u64,
) -> CommunicationValue {
let event = match mutation {
message_handlers::AppliedMessageMutation::State { send_time, state } => {
CommunicationValue::new(CommunicationType::MessageState)
.add_typed_default(
DataType::SendTime,
DataValue::SignedNumber((*send_time).into()),
)
.add_typed_default(
DataType::MessageState,
DataValue::Str(state.as_str().to_string()),
)
}
message_handlers::AppliedMessageMutation::Edit {
send_time,
content,
version,
} => CommunicationValue::new(CommunicationType::MessageEditLive)
.add_typed_default(DataType::Content, DataValue::Str(content.clone()))
.add_typed_default(
DataType::SendTime,
DataValue::SignedNumber((*send_time).into()),
)
.add_typed_default(
DataType::VersionNumber,
DataValue::SignedNumber((*version).into()),
),
message_handlers::AppliedMessageMutation::Reaction {
send_time,
reaction,
accepted,
} => CommunicationValue::new(CommunicationType::MessageReactionLive)
.add_typed_default(DataType::Reaction, DataValue::Str(reaction.clone()))
.add_typed_default(
DataType::SendTime,
DataValue::SignedNumber((*send_time).into()),
)
.add_typed_default(
DataType::SenderId,
DataValue::UnsignedNumber(actor_id.into()),
)
.add_typed_default(DataType::Accepted, DataValue::Bool(*accepted)),
message_handlers::AppliedMessageMutation::Delete { send_time } => {
CommunicationValue::new(CommunicationType::MessageDeleteLive).add_typed_default(
DataType::SendTime,
DataValue::SignedNumber((*send_time).into()),
)
}
};
event
.with_id(next_client_event_id())
.with_sender(actor_id)
.with_receiver(account_id)
.add_typed_default(
DataType::ChatPartnerId,
DataValue::UnsignedNumber(chat_partner_id.into()),
)
}
fn mutation_delivery(
mutation: &message_handlers::AppliedMessageMutation,
actor_id: u64,
account_id: u64,
chat_partner_id: u64,
recipient: iota_identity::PrincipalHandle,
) -> ClientDelivery {
ClientDelivery {
recipient,
frame: client_event_from_mutation(mutation, actor_id, account_id, chat_partner_id),
}
}
fn client_event_from_relay( fn client_event_from_relay(
message_type: CommunicationType, message_type: CommunicationType,
payload: &DataValue, payload: &DataValue,
@ -1619,67 +1868,6 @@ fn client_event_from_relay(
.add_typed_default(DataType::Payload, DataValue::Str("available".to_string())), .add_typed_default(DataType::Payload, DataValue::Str("available".to_string())),
) )
} }
CommunicationType::MessageEdit => {
let frame = CommunicationValue::new(CommunicationType::MessageEdit)
.with_payload(payload.clone());
Some(
CommunicationValue::new(CommunicationType::MessageEditLive)
.with_id(event_id)
.with_sender(signer_id)
.with_receiver(receiver)
.add_typed_default(
DataType::Content,
frame.get_data(DataType::Content)?.clone(),
)
.add_typed_default(
DataType::SendTime,
frame.get_data(DataType::SendTime)?.clone(),
)
.add_typed_default(
DataType::VersionNumber,
frame.get_data(DataType::VersionNumber)?.clone(),
)
.add_typed_default(DataType::ChatPartnerId, sender.clone()),
)
}
CommunicationType::MessageReactionAdd | CommunicationType::MessageReactionRemove => {
let frame = CommunicationValue::new(message_type).with_payload(payload.clone());
Some(
CommunicationValue::new(CommunicationType::MessageReactionLive)
.with_id(event_id)
.with_sender(signer_id)
.with_receiver(receiver)
.add_typed_default(
DataType::Reaction,
frame.get_data(DataType::Reaction)?.clone(),
)
.add_typed_default(
DataType::SendTime,
frame.get_data(DataType::SendTime)?.clone(),
)
.add_typed_default(DataType::ChatPartnerId, sender.clone())
.add_typed_default(DataType::SenderId, sender)
.add_typed_default(
DataType::Accepted,
DataValue::Bool(message_type == CommunicationType::MessageReactionAdd),
),
)
}
CommunicationType::MessageDelete | CommunicationType::MessageDeleteLive => {
let frame = CommunicationValue::new(CommunicationType::MessageDelete)
.with_payload(payload.clone());
Some(
CommunicationValue::new(CommunicationType::MessageDeleteLive)
.with_id(event_id)
.with_sender(signer_id)
.with_receiver(receiver)
.add_typed_default(
DataType::SendTime,
frame.get_data(DataType::SendTime)?.clone(),
)
.add_typed_default(DataType::ChatPartnerId, sender),
)
}
CommunicationType::AddConversation if recipient_id < signer_id => { CommunicationType::AddConversation if recipient_id < signer_id => {
let chat_id = format!("{recipient_id}:{signer_id}"); let chat_id = format!("{recipient_id}:{signer_id}");
Some( Some(
@ -1872,4 +2060,24 @@ mod tests {
assert!(recipient_block_policy_applies(true, 42, 43)); assert!(recipient_block_policy_applies(true, 42, 43));
assert!(!recipient_block_policy_applies(false, 42, 43)); assert!(!recipient_block_policy_applies(false, 42, 43));
} }
#[test]
fn mutation_events_use_receiving_account_chat_partner() {
let event = client_event_from_mutation(
&message_handlers::AppliedMessageMutation::Reaction {
send_time: 12,
reaction: "thumbsup".into(),
accepted: true,
},
7,
8,
9,
);
assert!(event.is_type(CommunicationType::MessageReactionLive));
assert_eq!(event.receiver(), Some(8));
assert_eq!(event.sender(), Some(7));
assert_eq!(event.get_data(DataType::ChatPartnerId).as_number(), Some(9));
assert_eq!(event.get_data(DataType::SenderId).as_number(), Some(7));
}
} }

View file

@ -14,6 +14,12 @@ pub enum MessageState {
Sending, Sending,
} }
#[derive(Clone, Debug)]
pub struct MessageStateUpdate {
pub send_time: i64,
pub state: MessageState,
}
impl MessageState { impl MessageState {
pub fn as_str(&self) -> &'static str { pub fn as_str(&self) -> &'static str {
match self { match self {
@ -979,23 +985,29 @@ pub fn change_message_state_by_relay_id(
relay_signer_principal: iota_identity::PrincipalHandle, relay_signer_principal: iota_identity::PrincipalHandle,
relay_message_id: &str, relay_message_id: &str,
new_state: MessageState, new_state: MessageState,
) -> Result<(), StorageError> { ) -> Result<Option<MessageStateUpdate>, StorageError> {
db::with_db(|conn| { db::with_db(|conn| {
let tx = conn.unchecked_transaction()?; let tx = conn.unchecked_transaction()?;
let Some((msg_id, current)) = tx let Some((msg_id, current, message_time)) = tx
.query_row( .query_row(
"SELECT id, message_state FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3", "SELECT id, message_state, message_time FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3",
params![storage_owner, relay_signer_principal.0, relay_message_id], params![storage_owner, relay_signer_principal.0, relay_message_id],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, String>(1)?)), |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, String>(1)?,
row.get::<_, i64>(2)?,
))
},
) )
.optional()? .optional()?
else { else {
return Ok(()); return Ok(None);
}; };
let state = MessageState::from_str(&current).upgrade(new_state).as_str(); let final_state = MessageState::from_str(&current).upgrade(new_state);
tx.execute( tx.execute(
"UPDATE messages SET message_state = ?1 WHERE id = ?2", "UPDATE messages SET message_state = ?1 WHERE id = ?2",
params![state, msg_id], params![final_state.as_str(), msg_id],
)?; )?;
sync::record_event( sync::record_event(
&tx, &tx,
@ -1005,7 +1017,10 @@ pub fn change_message_state_by_relay_id(
Operation::Upsert, Operation::Upsert,
)?; )?;
tx.commit()?; tx.commit()?;
Ok(()) Ok(Some(MessageStateUpdate {
send_time: message_time,
state: final_state,
}))
}) })
} }
@ -1020,7 +1035,7 @@ pub fn record_message_receipt(
receipt_type: MessageState, receipt_type: MessageState,
event_at: i64, event_at: i64,
recorded_at: i64, recorded_at: i64,
) -> Result<(), StorageError> { ) -> Result<Option<MessageStateUpdate>, StorageError> {
let receipt_type = match receipt_type { let receipt_type = match receipt_type {
MessageState::Received => "received", MessageState::Received => "received",
MessageState::Read => "read", MessageState::Read => "read",
@ -1028,11 +1043,19 @@ pub fn record_message_receipt(
}; };
db::with_db(|conn| { db::with_db(|conn| {
let tx = conn.unchecked_transaction()?; let tx = conn.unchecked_transaction()?;
let Some((message_id, external_principal, authored_at, history_deleted)) = tx let Some((message_id, message_time, external_principal, authored_at, history_deleted)) = tx
.query_row( .query_row(
"SELECT id, external_principal, authored_at, history_deleted FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3", "SELECT id, message_time, external_principal, authored_at, history_deleted FROM messages WHERE storage_owner = ?1 AND relay_signer_principal = ?2 AND relay_message_id = ?3",
params![storage_owner, target_signer_principal.0, target_message_id], params![storage_owner, target_signer_principal.0, target_message_id],
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, i64>(1)?, row.get::<_, Option<i64>>(2)?, row.get::<_, i64>(3)?)), |row| {
Ok((
row.get::<_, i64>(0)?,
row.get::<_, i64>(1)?,
row.get::<_, i64>(2)?,
row.get::<_, Option<i64>>(3)?,
row.get::<_, i64>(4)?,
))
},
) )
.optional()? .optional()?
else { else {
@ -1051,7 +1074,7 @@ pub fn record_message_receipt(
)); ));
} }
if history_deleted != 0 { if history_deleted != 0 {
return Ok(()); return Ok(None);
} }
tx.execute( tx.execute(
"INSERT OR IGNORE INTO message_receipts (storage_owner, target_signer_id, target_signer_principal, target_message_id, receipt_signer_id, receipt_signer_principal, receipt_message_id, receipt_type, event_at, recorded_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)", "INSERT OR IGNORE INTO message_receipts (storage_owner, target_signer_id, target_signer_principal, target_message_id, receipt_signer_id, receipt_signer_principal, receipt_message_id, receipt_type, event_at, recorded_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
@ -1062,17 +1085,15 @@ pub fn record_message_receipt(
} else { } else {
("client_received_at", "client_received_recorded_at") ("client_received_at", "client_received_recorded_at")
}; };
let state = MessageState::from_str(&tx.query_row( let final_state = MessageState::from_str(&tx.query_row(
"SELECT message_state FROM messages WHERE id = ?1", "SELECT message_state FROM messages WHERE id = ?1",
[message_id], [message_id],
|row| row.get::<_, String>(0), |row| row.get::<_, String>(0),
)?) )?)
.upgrade(MessageState::from_str(receipt_type)) .upgrade(MessageState::from_str(receipt_type));
.as_str()
.to_string();
tx.execute( tx.execute(
&format!("UPDATE messages SET {state_column} = COALESCE({state_column}, ?1), {recorded_column} = COALESCE({recorded_column}, ?2), message_state = ?3 WHERE id = ?4"), &format!("UPDATE messages SET {state_column} = COALESCE({state_column}, ?1), {recorded_column} = COALESCE({recorded_column}, ?2), message_state = ?3 WHERE id = ?4"),
params![event_at, recorded_at, state, message_id], params![event_at, recorded_at, final_state.as_str(), message_id],
)?; )?;
sync::record_event( sync::record_event(
&tx, &tx,
@ -1082,7 +1103,10 @@ pub fn record_message_receipt(
Operation::Upsert, Operation::Upsert,
)?; )?;
tx.commit()?; tx.commit()?;
Ok(()) Ok(Some(MessageStateUpdate {
send_time: message_time,
state: final_state,
}))
}) })
} }
@ -1579,6 +1603,10 @@ mod tests {
MessageState::Received.upgrade(MessageState::Read), MessageState::Received.upgrade(MessageState::Read),
MessageState::Read MessageState::Read
); );
assert_eq!(
MessageState::Read.upgrade(MessageState::Received),
MessageState::Read
);
} }
#[test] #[test]

View file

@ -0,0 +1,84 @@
use iota_identity::PrincipalHandle;
use iota_storage::util::chat_files::{self, MessageState, NewMessage};
#[test]
fn message_offset_counts_visible_newer_messages_in_one_chat() {
let storage = tempfile::tempdir().unwrap();
iota_util::file_util::configure_storage_directory(storage.path().to_owned());
iota_storage::util::db::initialize_database().unwrap();
iota_storage::util::db::with_db(|connection| {
connection.execute(
"INSERT INTO principals (authority_kind, authority_id, remote_user_id, descriptor_revision, last_resolved_at) VALUES ('iota', 'iota:peer', 2, 1, 1)",
[],
)?;
let principal = connection.last_insert_rowid();
connection.execute(
"INSERT INTO contacts (storage_owner, user_id, principal_handle, created_at) VALUES (1, 2, ?1, 1)",
[principal],
)?;
connection.execute(
"INSERT INTO principals (authority_kind, authority_id, remote_user_id, descriptor_revision, last_resolved_at) VALUES ('iota', 'iota:other', 3, 1, 1)",
[],
)?;
let other_principal = connection.last_insert_rowid();
connection.execute(
"INSERT INTO contacts (storage_owner, user_id, principal_handle, created_at) VALUES (1, 3, ?1, 1)",
[other_principal],
)?;
Ok(())
})
.unwrap();
for (send_time, relay_message_id, external_user, external_principal) in [
(10, "first", 2, 1),
(20, "second", 2, 1),
(30, "hidden", 2, 1),
(40, "other-chat", 3, 2),
] {
chat_files::add_message(NewMessage {
relay_signer_id: external_user,
relay_signer_principal: PrincipalHandle(external_principal),
relay_message_id,
authored_at: send_time,
send_time,
storage_owner: 1,
external_user,
external_principal: PrincipalHandle(external_principal),
sent_by_self: false,
content: relay_message_id,
height: 0,
key_version: 1,
reply_to: None,
origin_iota_received_at: None,
destination_iota_received_at: Some(send_time),
initial_state: MessageState::Sent,
})
.unwrap();
}
iota_storage::util::db::with_db(|connection| {
connection.execute(
"UPDATE messages SET history_deleted = 1 WHERE relay_message_id = 'hidden'",
[],
)?;
Ok(())
})
.unwrap();
let newest = chat_files::get_message_with_offset(1, 2, 20)
.unwrap()
.unwrap();
assert_eq!(newest.1, 0);
let first = chat_files::get_message_with_offset(1, 2, 10)
.unwrap()
.unwrap();
assert_eq!(first.1, 1);
assert!(
chat_files::get_message_with_offset(1, 3, 10)
.unwrap()
.is_none()
);
}