From 0d80c0e3feaac00129ebbfec3056f69a304c1470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 10 Nov 2022 18:40:14 +0100 Subject: [PATCH 01/78] chore(crypto): Improve the log for the one-time key signature error --- crates/matrix-sdk-crypto/src/error.rs | 16 +++++++++++++--- crates/matrix-sdk-crypto/src/olm/account.rs | 12 ++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/error.rs b/crates/matrix-sdk-crypto/src/error.rs index 483d19961..d08e94144 100644 --- a/crates/matrix-sdk-crypto/src/error.rs +++ b/crates/matrix-sdk-crypto/src/error.rs @@ -18,7 +18,7 @@ use thiserror::Error; use vodozemac::{Curve25519PublicKey, Ed25519PublicKey}; use super::store::CryptoStoreError; -use crate::olm::SessionExportError; +use crate::{olm::SessionExportError, types::SignedKey}; pub type OlmResult = Result; pub type MegolmResult = Result; @@ -229,8 +229,18 @@ pub enum SessionCreationError { OneTimeKeyUnknown(OwnedUserId, OwnedDeviceId), /// Failed to verify the one-time key signatures. - #[error("Failed to verify the one-time key signatures for {0} {1}: {2:?}")] - InvalidSignature(OwnedUserId, OwnedDeviceId, SignatureError), + #[error( + "Failed to verify the signature of a one-time key, key: {one_time_key:?}, \ + signing_key: {signing_key:?}: {error:?}" + )] + InvalidSignature { + /// The one-time key that failed the signature verification. + one_time_key: SignedKey, + /// The key that was used to verify the signature. + signing_key: Option, + /// The exact error describing why the signature verification failed. + error: SignatureError, + }, /// The user's device is missing a curve25519 key. #[error( diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 1de7f31cb..129de8aad 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -1036,12 +1036,12 @@ impl ReadOnlyAccount { Err(e) => return Err(SessionCreationError::InvalidJson(e)), }; - device.verify_one_time_key(&one_time_key).map_err(|e| { - SessionCreationError::InvalidSignature( - device.user_id().to_owned(), - device.device_id().into(), - e, - ) + device.verify_one_time_key(&one_time_key).map_err(|error| { + SessionCreationError::InvalidSignature { + signing_key: device.ed25519_key(), + one_time_key: one_time_key.clone(), + error, + } })?; let identity_key = device.curve25519_key().ok_or_else(|| { From 078855b4c926f8d5aabf9025cd1ae676f0d7fae0 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Fri, 11 Nov 2022 13:20:19 +0100 Subject: [PATCH 02/78] feat(ffi): logging support for android (#1199) * split logger into platform specific implementations * logging support for android * use platform independent wrapper for uniffi:exports * activate colors for ansi-terminals --- Cargo.lock | 40 ++++++++++++++++++ bindings/matrix-sdk-ffi/Cargo.toml | 17 ++++++-- bindings/matrix-sdk-ffi/src/lib.rs | 13 ++---- bindings/matrix-sdk-ffi/src/platform.rs | 54 +++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 14 deletions(-) create mode 100644 bindings/matrix-sdk-ffi/src/platform.rs diff --git a/Cargo.lock b/Cargo.lock index 7c8583b31..792f7ebd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -64,6 +64,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_log-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" + +[[package]] +name = "android_logger" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9dd62f37dea550caf48c77591dc50bd1a378ce08855be1a0c42a97b7550fb" +dependencies = [ + "android_log-sys", + "env_logger", + "log", + "once_cell", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -1344,6 +1362,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +[[package]] +name = "env_logger" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" +dependencies = [ + "log", + "regex", +] + [[package]] name = "errno" version = "0.2.8" @@ -2397,6 +2425,16 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" +[[package]] +name = "log-panics" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f9dd8546191c1850ecf67d22f5ff00a935b890d0e84713159a55495cc2ac5f" +dependencies = [ + "backtrace", + "log", +] + [[package]] name = "log4rs" version = "1.1.1" @@ -2694,11 +2732,13 @@ dependencies = [ name = "matrix-sdk-ffi" version = "0.2.0" dependencies = [ + "android_logger", "anyhow", "extension-trait", "futures-core", "futures-signals", "futures-util", + "log-panics", "matrix-sdk", "once_cell", "sanitize-filename-reader-friendly", diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index caedd8732..48e7185e7 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -10,7 +10,7 @@ rust-version = "1.56" repository = "https://github.com/matrix-org/matrix-rust-sdk" [lib] -crate-type = ["staticlib"] +crate-type = ["cdylib", "staticlib"] [features] default = ["experimental-room-preview"] # the whole crate is still very experimental, so this is fine @@ -27,14 +27,23 @@ futures-signals = { version = "0.3.30", default-features = false } futures-util = { version = "0.3.17", default-features = false } # FIXME: we currently can't feature flag anything in the api.udl, therefore we must enforce sliding-sync being exposed here.. # see https://github.com/matrix-org/matrix-rust-sdk/issues/1014 -matrix-sdk = { path = "../../crates/matrix-sdk", features = ["anyhow", "experimental-timeline", "markdown", "sliding-sync", "socks"], version = "0.6.0" } once_cell = "1.10.0" sanitize-filename-reader-friendly = "2.2.1" serde_json = { version = "1" } thiserror = "1.0.30" tokio = { version = "1", features = ["rt-multi-thread", "macros"] } tokio-stream = "0.1.8" -tracing = { workspace = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"] } uniffi = { workspace = true } uniffi_macros = { workspace = true } + + +[target.'cfg(target_os = "android")'.dependencies] +tracing = { version = "0.1.29", default-features = false, features = ["log"] } +android_logger = "0.11" +log-panics = { version = "2", features = ["with-backtrace"]} +matrix-sdk = { path = "../../crates/matrix-sdk", default-features = false, features = ["anyhow", "experimental-timeline", "e2e-encryption", "sled", "markdown", "sliding-sync", "socks", "rustls-tls"], version = "0.6.0" } + +[target.'cfg(not(target_os = "android"))'.dependencies] +tracing = { workspace = true } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +matrix-sdk = { path = "../../crates/matrix-sdk", features = ["anyhow", "experimental-timeline", "markdown", "sliding-sync", "socks"], version = "0.6.0" } diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index 0ad5bbdf2..ada68fec1 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -20,6 +20,8 @@ macro_rules! unwrap_or_clone_arc_into_variant { }; } +mod platform; + pub mod authentication_service; pub mod client; pub mod client_builder; @@ -30,13 +32,10 @@ pub mod sliding_sync; pub mod timeline; mod uniffi_api; -use std::io; - use client::Client; use client_builder::ClientBuilder; use once_cell::sync::Lazy; use tokio::runtime::Runtime; -use tracing_subscriber::{fmt, prelude::*, EnvFilter}; pub use uniffi_api::*; pub static RUNTIME: Lazy = @@ -72,13 +71,7 @@ impl From for ClientError { } } -#[uniffi::export] -fn setup_tracing(configuration: String) { - tracing_subscriber::registry() - .with(EnvFilter::new(configuration)) - .with(fmt::layer().with_ansi(false).with_writer(io::stderr)) - .init(); -} +pub use platform::*; mod uniffi_types { pub use matrix_sdk::ruma::events::room::{message::RoomMessageEventContent, MediaSource}; diff --git a/bindings/matrix-sdk-ffi/src/platform.rs b/bindings/matrix-sdk-ffi/src/platform.rs new file mode 100644 index 000000000..4b7d63efe --- /dev/null +++ b/bindings/matrix-sdk-ffi/src/platform.rs @@ -0,0 +1,54 @@ +#[cfg(target_os = "android")] +use android as platform_impl; +#[cfg(target_os = "ios")] +use ios as platform_impl; +#[cfg(not(any(target_os = "ios", target_os = "android")))] +use other as platform_impl; + +#[cfg(target_os = "android")] +mod android { + use android_logger::{Config, FilterBuilder}; + use tracing::log::Level; + + pub fn setup_tracing(filter: String) { + std::env::set_var("RUST_BACKTRACE", "1"); + + log_panics::init(); + + let log_config = Config::default() + .with_min_level(Level::Trace) + .with_tag("matrix-rust-sdk") + .with_filter(FilterBuilder::new().parse(&filter).build()); + + android_logger::init_once(log_config); + } +} + +#[cfg(target_os = "ios")] +mod ios { + pub fn setup_tracing(configuration: String) { + tracing_subscriber::registry() + .with(EnvFilter::new(configuration)) + .with(fmt::layer().with_ansi(false).with_writer(io::stderr)) + .init(); + } +} + +#[cfg(not(any(target_os = "ios", target_os = "android")))] +mod other { + use std::io; + + use tracing_subscriber::{fmt, prelude::*, EnvFilter}; + + pub fn setup_tracing(configuration: String) { + tracing_subscriber::registry() + .with(EnvFilter::new(configuration)) + .with(fmt::layer().with_ansi(true).with_writer(io::stderr)) + .init(); + } +} + +#[uniffi::export] +pub fn setup_tracing(filter: String) { + platform_impl::setup_tracing(filter) +} From 680ef6b93afcfda87577ec943e67024b746afec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriel=20F=C3=A9ron?= Date: Sat, 12 Nov 2022 14:04:34 +0100 Subject: [PATCH 03/78] feat(sdk): Allow using an existing sled::Db with SledStateStore --- crates/matrix-sdk-sled/src/state_store.rs | 64 +++++++++++++++++------ 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/crates/matrix-sdk-sled/src/state_store.rs b/crates/matrix-sdk-sled/src/state_store.rs index 46ad8cff5..5f2cc970c 100644 --- a/crates/matrix-sdk-sled/src/state_store.rs +++ b/crates/matrix-sdk-sled/src/state_store.rs @@ -170,11 +170,18 @@ const ALL_GLOBAL_KEYS: &[&str] = &[VERSION_KEY]; type Result = std::result::Result; -#[derive(Builder, Debug, PartialEq, Eq)] +#[derive(Debug, Clone)] +enum DbOrPath { + Db(Db), + Path(PathBuf), +} + +#[derive(Builder, Debug)] #[builder(name = "SledStateStoreBuilder", build_fn(skip))] +#[allow(dead_code)] pub struct SledStateStoreBuilderConfig { - /// Path to the sled store files, created if not yet existing - path: PathBuf, + #[builder(setter(custom))] + db_or_path: DbOrPath, /// Set the password the sled store is encrypted with (if any) passphrase: String, /// The strategy to use when a merge conflict is found, see @@ -184,22 +191,47 @@ pub struct SledStateStoreBuilderConfig { } impl SledStateStoreBuilder { + /// Path to the sled store files, created if not it doesn't exist yet. + /// + /// Mutually exclusive with [`db`][Self::db], whichever is called last wins. + pub fn path(&mut self, path: PathBuf) -> &mut SledStateStoreBuilder { + self.db_or_path = Some(DbOrPath::Path(path)); + self + } + + /// Use the given [`sled::Db`]. + /// + /// Mutually exclusive with [`path`][Self::path], whichever is called last + /// wins. + pub fn db(&mut self, db: Db) -> &mut SledStateStoreBuilder { + self.db_or_path = Some(DbOrPath::Db(db)); + self + } + + /// Create a [`SledStateStore`] with the options set on this builder. + /// + /// # Errors + /// + /// This method can fail for two general reasons: + /// + /// * Invalid path: The [`sled::Db`] could not be opened at the supplied + /// path. + /// * Migration error: The migration to a newer version of the schema + /// failed, see `SledStoreError::MigrationConflict`. pub fn build(&mut self) -> Result { - let is_temp = self.path.is_none(); - - let mut cfg = Config::new().temporary(is_temp); - - let path = if let Some(path) = &self.path { - let path = path.join("matrix-sdk-state"); - - cfg = cfg.path(&path); - Some(path) - } else { - None + let (db, path) = match &self.db_or_path { + None => { + let db = Config::new().temporary(true).open().map_err(StoreError::backend)?; + (db, None) + } + Some(DbOrPath::Db(db)) => (db.clone(), None), + Some(DbOrPath::Path(path)) => { + let path = path.join("matrix-sdk-state"); + let db = Config::new().path(&path).open().map_err(StoreError::backend)?; + (db, Some(path)) + } }; - let db = cfg.open().map_err(StoreError::backend)?; - let store_cipher = if let Some(passphrase) = &self.passphrase { if let Some(inner) = db.get("store_cipher".encode())? { Some(StoreCipher::import(passphrase, &inner)?.into()) From 4dc0e0ef3c8ece8f77efa52399b5211b86742f5d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 10 Nov 2022 13:23:10 +0100 Subject: [PATCH 04/78] chore: Upgrade Ruma --- Cargo.lock | 78 ++++++++++++------- Cargo.toml | 3 +- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 2 +- bindings/matrix-sdk-crypto-js/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-nodejs/Cargo.toml | 2 +- bindings/matrix-sdk-ffi/src/client.rs | 16 ++-- bindings/matrix-sdk-ffi/src/room.rs | 11 ++- crates/matrix-sdk-crypto/Cargo.toml | 2 +- crates/matrix-sdk-crypto/README.md | 2 +- crates/matrix-sdk-crypto/src/machine.rs | 2 +- crates/matrix-sdk-qrcode/Cargo.toml | 2 +- crates/matrix-sdk/Cargo.toml | 2 +- crates/matrix-sdk/src/account.rs | 9 ++- crates/matrix-sdk/src/client/mod.rs | 66 ++++++---------- crates/matrix-sdk/src/error.rs | 32 ++++++-- crates/matrix-sdk/src/http_client.rs | 4 +- crates/matrix-sdk/src/room/common.rs | 3 +- crates/matrix-sdk/src/room/joined.rs | 8 +- .../src/room/timeline/event_handler.rs | 6 +- crates/matrix-sdk/src/room/timeline/tests.rs | 6 +- crates/matrix-sdk/tests/integration/client.rs | 34 ++++---- .../tests/integration/refresh_token.rs | 16 +--- examples/appservice_autojoin/src/main.rs | 18 ++--- 23 files changed, 174 insertions(+), 152 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 792f7ebd6..7d1691c52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -388,9 +388,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" @@ -2349,6 +2349,28 @@ dependencies = [ "serde", ] +[[package]] +name = "konst" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330f0e13e6483b8c34885f7e6c9f19b1a7bd449c673fbb948a51c99d66ef74f4" +dependencies = [ + "konst_macro_rules", + "konst_proc_macros", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "konst_proc_macros" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "984e109462d46ad18314f10e392c286c3d47bce203088a09012de1015b45b737" + [[package]] name = "lazy-regex" version = "2.3.0" @@ -3889,8 +3911,7 @@ dependencies = [ [[package]] name = "ruma" version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dc348e3a4a18abc4e97fffa5e2e623f6edd50ba3a1dd5f47eb249fea713b69f" +source = "git+https://github.com/ruma/ruma?rev=ed100afddb5fb30f1ccf368d7e712a3a483e63bf#ed100afddb5fb30f1ccf368d7e712a3a483e63bf" dependencies = [ "assign", "js_int", @@ -3904,9 +3925,9 @@ dependencies = [ [[package]] name = "ruma-appservice-api" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4f8cecdd4e2729656fc4126b6b5b218834d4025e0ed0e5063087a0231098a5c" +source = "git+https://github.com/ruma/ruma?rev=ed100afddb5fb30f1ccf368d7e712a3a483e63bf#ed100afddb5fb30f1ccf368d7e712a3a483e63bf" dependencies = [ + "js_int", "ruma-common", "serde", "serde_json", @@ -3915,13 +3936,13 @@ dependencies = [ [[package]] name = "ruma-client-api" version = "0.15.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1e72bc731b4dc8b569aa83915f13e419144b67110d858c65bb74aa05e2dc4b7" +source = "git+https://github.com/ruma/ruma?rev=ed100afddb5fb30f1ccf368d7e712a3a483e63bf#ed100afddb5fb30f1ccf368d7e712a3a483e63bf" dependencies = [ "assign", "bytes", "http", "js_int", + "js_option", "maplit", "percent-encoding", "ruma-common", @@ -3932,8 +3953,7 @@ dependencies = [ [[package]] name = "ruma-common" version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716889595f4edc3cfeb94d9f122e413f73e37d7d80ea1c14196e1004241a3889" +source = "git+https://github.com/ruma/ruma?rev=ed100afddb5fb30f1ccf368d7e712a3a483e63bf#ed100afddb5fb30f1ccf368d7e712a3a483e63bf" dependencies = [ "base64", "bytes", @@ -3945,6 +3965,7 @@ dependencies = [ "js-sys", "js_int", "js_option", + "konst", "percent-encoding", "pulldown-cmark", "rand 0.8.5", @@ -3963,8 +3984,7 @@ dependencies = [ [[package]] name = "ruma-federation-api" version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f905d12f6144c7a754bd0339fa6893698c03d03a908abb20cc6eeb4ec7f9466" +source = "git+https://github.com/ruma/ruma?rev=ed100afddb5fb30f1ccf368d7e712a3a483e63bf#ed100afddb5fb30f1ccf368d7e712a3a483e63bf" dependencies = [ "js_int", "ruma-common", @@ -3975,8 +3995,7 @@ dependencies = [ [[package]] name = "ruma-identifiers-validation" version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabac62d16465a87435579c779d74dceabb93b09e44c766af6085050f3cc4275" +source = "git+https://github.com/ruma/ruma?rev=ed100afddb5fb30f1ccf368d7e712a3a483e63bf#ed100afddb5fb30f1ccf368d7e712a3a483e63bf" dependencies = [ "js_int", "thiserror", @@ -3985,8 +4004,7 @@ dependencies = [ [[package]] name = "ruma-macros" version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f82e91eb61cd86d9287303133ee55b54618eccb75a522cc22a42c15f5bda340" +source = "git+https://github.com/ruma/ruma?rev=ed100afddb5fb30f1ccf368d7e712a3a483e63bf#ed100afddb5fb30f1ccf368d7e712a3a483e63bf" dependencies = [ "once_cell", "proc-macro-crate", @@ -4189,9 +4207,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.144" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860" +checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] @@ -4207,9 +4225,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.144" +version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", @@ -4218,9 +4236,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.85" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" +checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" dependencies = [ "itoa 1.0.3", "ryu", @@ -4600,18 +4618,18 @@ checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" [[package]] name = "thiserror" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d0a539a918745651435ac7db7a18761589a94cd7e94cd56999f828bf73c8a57" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c251e90f708e16c49a16f4917dc2131e75222b72edfa9cb7f7c58ae56aae0c09" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" dependencies = [ "proc-macro2", "quote", @@ -4852,9 +4870,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.36" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", "log", @@ -4865,9 +4883,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 606ecb546..5961385fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,8 @@ default-members = ["benchmarks", "crates/*"] resolver = "2" [workspace.dependencies] -ruma = { version = "0.7.4", features = ["client-api-c"] } +ruma = { git = "https://github.com/ruma/ruma", rev = "ed100afddb5fb30f1ccf368d7e712a3a483e63bf", features = ["client-api-c"] } +ruma-common = { git = "https://github.com/ruma/ruma", rev = "ed100afddb5fb30f1ccf368d7e712a3a483e63bf" } tracing = { version = "0.1.36", default-features = false, features = ["std"] } uniffi = { git = "https://github.com/mozilla/uniffi-rs", rev = "779e955f21a70e4aba43a7408f1841dcdf728b32" } uniffi_macros = { git = "https://github.com/mozilla/uniffi-rs", rev = "779e955f21a70e4aba43a7408f1841dcdf728b32" } diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index b36ffcd69..e29784ac3 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -25,7 +25,7 @@ use ruma::{ upload_signatures::v3::Response as SignatureUploadResponse, }, message::send_message_event::v3::Response as RoomMessageResponse, - sync::sync_events::v3::{DeviceLists as RumaDeviceLists, ToDevice}, + sync::sync_events::{v3::ToDevice, DeviceLists as RumaDeviceLists}, to_device::send_event_to_device::v3::Response as ToDeviceResponse, }, IncomingResponse, diff --git a/bindings/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml index b446f5e53..993892ad2 100644 --- a/bindings/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -39,7 +39,7 @@ matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common" matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] } matrix-sdk-indexeddb = { version = "0.2.0", path = "../../crates/matrix-sdk-indexeddb", features = ["experimental-nodejs"] } matrix-sdk-qrcode = { version = "0.4.0", path = "../../crates/matrix-sdk-qrcode", optional = true } -ruma = { workspace = true, features = ["js", "rand", "unstable-msc2676", "unstable-msc2677"] } +ruma = { workspace = true, features = ["js", "rand", "unstable-msc2677"] } vodozemac = { workspace = true, features = ["js"] } wasm-bindgen = "0.2.83" wasm-bindgen-futures = "0.4.33" diff --git a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml index f3e8bff8b..5e2a9e895 100644 --- a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml +++ b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml @@ -26,7 +26,7 @@ tracing = ["dep:tracing-subscriber"] matrix-sdk-crypto = { version = "0.6.0", path = "../../crates/matrix-sdk-crypto", features = ["js"] } matrix-sdk-common = { version = "0.6.0", path = "../../crates/matrix-sdk-common", features = ["js"] } matrix-sdk-sled = { version = "0.2.0", path = "../../crates/matrix-sdk-sled", default-features = false, features = ["crypto-store"] } -ruma = { workspace = true, features = ["rand", "unstable-msc2676", "unstable-msc2677"] } +ruma = { workspace = true, features = ["rand", "unstable-msc2677"] } napi = { version = "2.9.1", default-features = false, features = ["napi6", "tokio_rt"] } napi-derive = "2.9.1" serde_json = "1.0.79" diff --git a/bindings/matrix-sdk-ffi/src/client.rs b/bindings/matrix-sdk-ffi/src/client.rs index 6139a7ca4..c4c50bb79 100644 --- a/bindings/matrix-sdk-ffi/src/client.rs +++ b/bindings/matrix-sdk-ffi/src/client.rs @@ -17,7 +17,7 @@ use matrix_sdk::{ serde::Raw, TransactionId, UInt, }, - Client as MatrixClient, Error, LoopCtrl, RumaApiError, + Client as MatrixClient, Error, LoopCtrl, }; use super::{ @@ -297,15 +297,13 @@ impl Client { /// Process a sync error and return loop control accordingly pub(crate) fn process_sync_error(&self, sync_error: Error) -> LoopCtrl { - if let Some(RumaApiError::ClientApi(error)) = sync_error.as_ruma_api_error() { - if let ErrorKind::UnknownToken { soft_logout } = error.kind { - self.state.write().unwrap().is_soft_logout = soft_logout; - if let Some(delegate) = &*self.delegate.read().unwrap() { - delegate.did_update_restore_token(); - delegate.did_receive_auth_error(soft_logout); - } - return LoopCtrl::Break; + if let Some(ErrorKind::UnknownToken { soft_logout }) = sync_error.client_api_error_kind() { + self.state.write().unwrap().is_soft_logout = *soft_logout; + if let Some(delegate) = &*self.delegate.read().unwrap() { + delegate.did_update_restore_token(); + delegate.did_receive_auth_error(*soft_logout); } + return LoopCtrl::Break; } tracing::warn!("Ignoring sync error: {:?}", sync_error); diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index 9e88c05ce..e572727b3 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -11,7 +11,10 @@ use matrix_sdk::{ Room as SdkRoom, }, ruma::{ - events::room::message::{Relation, Replacement, RoomMessageEvent, RoomMessageEventContent}, + events::room::message::{ + ForwardThread, MessageType, Relation, Replacement, RoomMessageEvent, + RoomMessageEventContent, + }, EventId, UserId, }, }; @@ -191,8 +194,8 @@ impl Room { let original_message = event_content.as_original().context("Couldn't retrieve original message.")?; - let reply_content = - RoomMessageEventContent::text_markdown(msg).make_reply_to(original_message); + let reply_content = RoomMessageEventContent::text_markdown(msg) + .make_reply_to(original_message, ForwardThread::Yes); timeline.send(reply_content.into(), txn_id.as_deref().map(Into::into)).await?; @@ -233,7 +236,7 @@ impl Room { let replacement = Replacement::new( event_id.to_owned(), - Box::new(RoomMessageEventContent::text_markdown(new_msg.to_owned())), + MessageType::text_markdown(new_msg.to_owned()), ); let mut edited_content = RoomMessageEventContent::text_markdown(new_msg); diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index c4e37cc48..7cc8fe677 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -44,7 +44,7 @@ matrix-sdk-common = { version = "0.6.0", path = "../matrix-sdk-common" } olm-rs = { version = "2.2.0", features = ["serde"], optional = true } pbkdf2 = { version = "0.11.0", default-features = false } rand = "0.8.5" -ruma = { workspace = true, features = ["rand", "canonical-json", "unstable-msc2676", "unstable-msc2677"] } +ruma = { workspace = true, features = ["rand", "canonical-json", "unstable-msc2677"] } serde = { version = "1.0.136", features = ["derive", "rc"] } serde_json = "1.0.79" sha2 = "0.10.2" diff --git a/crates/matrix-sdk-crypto/README.md b/crates/matrix-sdk-crypto/README.md index a7a7f7a69..e309218a6 100644 --- a/crates/matrix-sdk-crypto/README.md +++ b/crates/matrix-sdk-crypto/README.md @@ -21,7 +21,7 @@ use std::collections::BTreeMap; use matrix_sdk_crypto::{OlmMachine, OlmError}; use ruma::{ - api::client::sync::sync_events::v3::{ToDevice, DeviceLists}, + api::client::sync::sync_events::{v3::ToDevice, DeviceLists}, device_id, user_id, }; diff --git a/crates/matrix-sdk-crypto/src/machine.rs b/crates/matrix-sdk-crypto/src/machine.rs index 8d1258221..8d90a6bcc 100644 --- a/crates/matrix-sdk-crypto/src/machine.rs +++ b/crates/matrix-sdk-crypto/src/machine.rs @@ -1584,7 +1584,7 @@ pub(crate) mod tests { api::{ client::{ keys::{claim_keys, get_keys, upload_keys}, - sync::sync_events::v3::DeviceLists, + sync::sync_events::DeviceLists, to_device::send_event_to_device::v3::Response as ToDeviceResponse, }, IncomingResponse, diff --git a/crates/matrix-sdk-qrcode/Cargo.toml b/crates/matrix-sdk-qrcode/Cargo.toml index 50cc9dcb8..61677071e 100644 --- a/crates/matrix-sdk-qrcode/Cargo.toml +++ b/crates/matrix-sdk-qrcode/Cargo.toml @@ -19,7 +19,7 @@ rustdoc-args = ["--cfg", "docsrs"] base64 = "0.13.0" byteorder = "1.4.3" qrcode = { version = "0.12.0", default-features = false } -ruma-common = "0.10.0" +ruma-common = { workspace = true } thiserror = "1.0.30" vodozemac = { workspace = true } diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index ba0f77787..ff6d54d6f 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -43,7 +43,7 @@ image-proc = ["dep:image"] image-rayon = ["image-proc", "image?/jpeg_rayon"] experimental-room-preview = [] -experimental-timeline = ["ruma/unstable-msc2676", "ruma/unstable-msc2677"] +experimental-timeline = ["ruma/unstable-msc2677"] sliding-sync = [ "matrix-sdk-base/sliding-sync", diff --git a/crates/matrix-sdk/src/account.rs b/crates/matrix-sdk/src/account.rs index 897366b05..cc50ecf55 100644 --- a/crates/matrix-sdk/src/account.rs +++ b/crates/matrix-sdk/src/account.rs @@ -645,7 +645,7 @@ impl Account { /// if let Some(raw_content) = maybe_content { /// let content = raw_content.deserialize()?; /// println!("Ignored users:"); - /// for user_id in content.ignored_users { + /// for user_id in content.ignored_users.keys() { /// println!("- {user_id}"); /// } /// } @@ -676,7 +676,8 @@ impl Account { /// # let client = Client::new("http://localhost:8080".parse()?).await?; /// # let account = client.account(); /// use matrix_sdk::ruma::{ - /// events::ignored_user_list::IgnoredUserListEventContent, user_id, + /// events::ignored_user_list::{IgnoredUser, IgnoredUserListEventContent}, + /// user_id, /// }; /// /// let mut content = account @@ -685,7 +686,9 @@ impl Account { /// .map(|c| c.deserialize()) /// .transpose()? /// .unwrap_or_default(); - /// content.ignored_users.push(user_id!("@foo:bar.com").to_owned()); + /// content + /// .ignored_users + /// .insert(user_id!("@foo:bar.com").to_owned(), IgnoredUser::new()); /// account.set_account_data(content).await?; /// # anyhow::Ok(()) }; /// ``` diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index e1748911a..369f2329d 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -1699,26 +1699,21 @@ impl Client { // If this is an `M_UNKNOWN_TOKEN` error and refresh token handling is active, // try to refresh the token and retry the request. if self.inner.handle_refresh_tokens { - // FIXME: Use if-let chain once available - if let Err(Some(RumaApiError::ClientApi(error))) = - res.as_ref().map_err(HttpError::as_ruma_api_error) + if let Err(Some(ErrorKind::UnknownToken { .. })) = + res.as_ref().map_err(HttpError::client_api_error_kind) { - if matches!(error.kind, ErrorKind::UnknownToken { .. }) { - let refresh_res = self.refresh_access_token().await; - - if let Err(refresh_error) = refresh_res { - match &refresh_error { - HttpError::RefreshToken(RefreshTokenError::RefreshTokenRequired) => { - // Refreshing access tokens is not supported by - // this `Session`, ignore. - } - _ => { - return Err(refresh_error); - } + if let Err(refresh_error) = self.refresh_access_token().await { + match &refresh_error { + HttpError::RefreshToken(RefreshTokenError::RefreshTokenRequired) => { + // Refreshing access tokens is not supported by + // this `Session`, ignore. + } + _ => { + return Err(refresh_error); } - } else { - return self.send_inner(request, config, None).await; } + } else { + return self.send_inner(request, config, None).await; } } } @@ -1744,26 +1739,21 @@ impl Client { // If this is an `M_UNKNOWN_TOKEN` error and refresh token handling is active, // try to refresh the token and retry the request. if self.inner.handle_refresh_tokens { - // FIXME: Use if-let chain once available - if let Err(Some(RumaApiError::ClientApi(error))) = - res.as_ref().map_err(HttpError::as_ruma_api_error) + if let Err(Some(ErrorKind::UnknownToken { .. })) = + res.as_ref().map_err(HttpError::client_api_error_kind) { - if matches!(error.kind, ErrorKind::UnknownToken { .. }) { - let refresh_res = self.refresh_access_token().await; - - if let Err(refresh_error) = refresh_res { - match &refresh_error { - HttpError::RefreshToken(RefreshTokenError::RefreshTokenRequired) => { - // Refreshing access tokens is not supported by - // this `Session`, ignore. - } - _ => { - return Err(refresh_error); - } + if let Err(refresh_error) = self.refresh_access_token().await { + match &refresh_error { + HttpError::RefreshToken(RefreshTokenError::RefreshTokenRequired) => { + // Refreshing access tokens is not supported by + // this `Session`, ignore. + } + _ => { + return Err(refresh_error); } - } else { - return self.send_inner(request, config, homeserver).await; } + } else { + return self.send_inner(request, config, homeserver).await; } } } @@ -1876,13 +1866,7 @@ impl Client { /// /// ```no_run /// # use matrix_sdk::{ - /// # ruma::{ - /// # api::{ - /// # client::uiaa, - /// # error::{FromHttpResponseError, ServerError}, - /// # }, - /// # device_id, - /// # }, + /// # ruma::{api::client::uiaa, device_id}, /// # Client, Error, config::SyncSettings, /// # }; /// # use futures::executor::block_on; diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index ae8e65755..54464586e 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -28,7 +28,7 @@ use reqwest::Error as ReqwestError; use ruma::{ api::{ client::uiaa::{UiaaInfo, UiaaResponse}, - error::{FromHttpResponseError, IntoHttpError, ServerError}, + error::{FromHttpResponseError, IntoHttpError}, }, events::tag::InvalidUserTagName, IdParseError, @@ -119,13 +119,13 @@ pub enum HttpError { #[rustfmt::skip] // stop rustfmt breaking the `` in docs across multiple lines impl HttpError { /// If `self` is - /// [Api](Self::Api)([Server](FromHttpResponseError::Server)([Known](ServerError::Known)(e))), + /// [Api](Self::Api)([Server](FromHttpResponseError::Server)(e)), /// returns `Some(e)`. /// /// Otherwise, returns `None`. pub fn as_ruma_api_error(&self) -> Option<&RumaApiError> { match self { - Self::Api(FromHttpResponseError::Server(ServerError::Known(e))) => Some(e), + Self::Api(FromHttpResponseError::Server(e)) => Some(e), _ => None, } } @@ -136,6 +136,15 @@ impl HttpError { self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error) } + /// If `self` is a server error in the `errcode` + `error` format expected + /// for client-API endpoints, returns the error kind (`errcode`). + pub fn client_api_error_kind(&self) -> Option<&ruma::api::client::error::ErrorKind> { + self.as_client_api_error().and_then(|e| match &e.body { + ruma::api::client::error::ErrorBody::Standard { kind, .. } => Some(kind), + _ => None, + }) + } + /// Try to destructure the error into an universal interactive auth info. /// /// Some requests require universal interactive auth, doing such a request @@ -248,7 +257,7 @@ pub enum Error { #[rustfmt::skip] // stop rustfmt breaking the `` in docs across multiple lines impl Error { /// If `self` is - /// [Http](Self::Http)([Api](HttpError::Api)([Server](FromHttpResponseError::Server)([Known](ServerError::Known)(e)))), + /// [Http](Self::Http)([Api](HttpError::Api)([Server](FromHttpResponseError::Server)(e))), /// returns `Some(e)`. /// /// Otherwise, returns `None`. @@ -265,6 +274,15 @@ impl Error { self.as_ruma_api_error().and_then(RumaApiError::as_client_api_error) } + /// If `self` is a server error in the `errcode` + `error` format expected + /// for client-API endpoints, returns the error kind (`errcode`). + pub fn client_api_error_kind(&self) -> Option<&ruma::api::client::error::ErrorKind> { + self.as_client_api_error().and_then(|e| match &e.body { + ruma::api::client::error::ErrorBody::Standard { kind, .. } => Some(kind), + _ => None, + }) + } + /// Try to destructure the error into an universal interactive auth info. /// /// Some requests require universal interactive auth, doing such a request @@ -314,19 +332,19 @@ pub enum RoomKeyImportError { impl From> for HttpError { fn from(err: FromHttpResponseError) -> Self { - Self::Api(err.map(|e| e.map(RumaApiError::ClientApi))) + Self::Api(err.map(RumaApiError::ClientApi)) } } impl From> for HttpError { fn from(err: FromHttpResponseError) -> Self { - Self::Api(err.map(|e| e.map(RumaApiError::Uiaa))) + Self::Api(err.map(RumaApiError::Uiaa)) } } impl From> for HttpError { fn from(err: FromHttpResponseError) -> Self { - Self::Api(err.map(|e| e.map(RumaApiError::Other))) + Self::Api(err.map(RumaApiError::Other)) } } diff --git a/crates/matrix-sdk/src/http_client.rs b/crates/matrix-sdk/src/http_client.rs index 7cf135a2c..350ab3b47 100644 --- a/crates/matrix-sdk/src/http_client.rs +++ b/crates/matrix-sdk/src/http_client.rs @@ -295,8 +295,8 @@ async fn send_request( RetryError::Permanent } else { |err: HttpError| { - let retry_after = err.as_client_api_error().and_then(|e| match e.kind { - ClientApiErrorKind::LimitExceeded { retry_after_ms } => retry_after_ms, + let retry_after = err.client_api_error_kind().and_then(|kind| match kind { + ClientApiErrorKind::LimitExceeded { retry_after_ms } => *retry_after_ms, _ => None, }); RetryError::Transient { err, retry_after } diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index a6ca62b04..82c9a3e2d 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -15,9 +15,10 @@ use ruma::{ config::set_global_account_data, filter::RoomEventFilter, membership::{get_member_events, join_room_by_id, leave_room}, - message::get_message_events::{self, v3::Direction}, + message::get_message_events, room::get_room_event, tag::{create_tag, delete_tag}, + Direction, }, assign, events::{ diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index f218cb63c..eb50752ea 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -250,10 +250,10 @@ impl Joined { fully_read: &EventId, read_receipt: Option<&EventId>, ) -> Result<()> { - let request = - assign!(set_read_marker::v3::Request::new(self.inner.room_id(), fully_read), { - read_receipt - }); + let request = assign!(set_read_marker::v3::Request::new(self.inner.room_id()), { + fully_read: Some(fully_read), + read_receipt, + }); self.client.send(request, None).await?; Ok(()) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index b41069b16..ed23f3d39 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -22,7 +22,7 @@ use ruma::{ reaction::ReactionEventContent, room::{ encrypted::{self, RoomEncryptedEventContent}, - message::{self, Replacement, RoomMessageEventContent}, + message::{self, MessageType, Replacement, RoomMessageEventContent}, redaction::{ OriginalSyncRoomRedactionEvent, RoomRedactionEventContent, SyncRoomRedactionEvent, }, @@ -304,7 +304,7 @@ impl<'a> TimelineEventHandler<'a> { } } - fn handle_room_message_edit(&mut self, replacement: Replacement) { + fn handle_room_message_edit(&mut self, replacement: Replacement) { let event_id = &replacement.event_id; self.maybe_update_timeline_item(event_id, "edit", |item| { @@ -335,7 +335,7 @@ impl<'a> TimelineEventHandler<'a> { }; let content = TimelineItemContent::Message(Message { - msgtype: replacement.new_content.msgtype, + msgtype: replacement.new_content, in_reply_to: msg.in_reply_to.clone(), edited: true, }); diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index 54ec70a1e..6e9c1691f 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -33,7 +33,7 @@ use ruma::{ encrypted::{ EncryptedEventScheme, MegolmV1AesSha2ContentInit, RoomEncryptedEventContent, }, - message::{self, Replacement, RoomMessageEventContent}, + message::{self, MessageType, Replacement, RoomMessageEventContent}, redaction::OriginalSyncRoomRedactionEvent, }, MessageLikeEventContent, OriginalSyncMessageLikeEvent, @@ -94,7 +94,7 @@ async fn invalid_edit() { let edit = assign!(RoomMessageEventContent::text_plain(" * fake"), { relates_to: Some(message::Relation::Replacement(Replacement::new( msg_event_id.to_owned(), - Box::new(RoomMessageEventContent::text_plain("fake")), + MessageType::text_plain("fake"), ))), }); // Edit is from a different user than the previous event @@ -135,7 +135,7 @@ async fn edit_redacted() { let edit = assign!(RoomMessageEventContent::text_plain(" * test"), { relates_to: Some(message::Relation::Replacement(Replacement::new( redacted_event_id.to_owned(), - Box::new(RoomMessageEventContent::text_plain("test")), + MessageType::text_plain("test"), ))), }); timeline.handle_live_message_event(&ALICE, edit); diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs index 9b3b6a37b..1172fd312 100644 --- a/crates/matrix-sdk/tests/integration/client.rs +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -185,15 +185,20 @@ async fn login_error() { .await; if let Err(err) = client.login_username("example", "wordpass").send().await { - if let Some(RumaApiError::ClientApi(client_api::Error { kind, message, status_code })) = + if let Some(RumaApiError::ClientApi(client_api::Error { status_code, body })) = err.as_ruma_api_error() { - if *kind != client_api::error::ErrorKind::Forbidden { - panic!("found the wrong `ErrorKind` {kind:?}, expected `Forbidden"); - } - - assert_eq!(message, "Invalid password"); assert_eq!(*status_code, http::StatusCode::from_u16(403).unwrap()); + + if let client_api::error::ErrorBody::Standard { kind, message } = body { + if *kind != client_api::error::ErrorKind::Forbidden { + panic!("found the wrong `ErrorKind` {kind:?}, expected `Forbidden"); + } + + assert_eq!(message, "Invalid password"); + } else { + panic!("non-standard error body") + } } else { panic!("found the wrong `Error` type {err:?}, expected `Error::RumaResponse"); } @@ -225,17 +230,20 @@ async fn register_error() { if let Err(err) = client.register(user).await { if let Some(RumaApiError::Uiaa(UiaaResponse::MatrixError(client_api::Error { - kind, - message, status_code, + body, }))) = err.as_ruma_api_error() { - if *kind != client_api::error::ErrorKind::Forbidden { - panic!("found the wrong `ErrorKind` {kind:?}, expected `Forbidden"); - } - - assert_eq!(message, "Invalid password"); assert_eq!(*status_code, http::StatusCode::from_u16(403).unwrap()); + if let client_api::error::ErrorBody::Standard { kind, message } = body { + if *kind != client_api::error::ErrorKind::Forbidden { + panic!("found the wrong `ErrorKind` {kind:?}, expected `Forbidden"); + } + + assert_eq!(message, "Invalid password"); + } else { + panic!("non-standard error body") + } } else { panic!("found the wrong `Error` type {err:#?}, expected `UiaaResponse`"); } diff --git a/crates/matrix-sdk/tests/integration/refresh_token.rs b/crates/matrix-sdk/tests/integration/refresh_token.rs index 6ca17b508..4a796682d 100644 --- a/crates/matrix-sdk/tests/integration/refresh_token.rs +++ b/crates/matrix-sdk/tests/integration/refresh_token.rs @@ -6,13 +6,11 @@ use futures::{ }; use futures_signals::signal::SignalExt; use matches::assert_matches; -use matrix_sdk::{ - config::RequestConfig, executor::spawn, HttpError, RefreshTokenError, RumaApiError, Session, -}; +use matrix_sdk::{config::RequestConfig, executor::spawn, HttpError, RefreshTokenError, Session}; use matrix_sdk_test::{async_test, test_json}; use ruma::{ api::{ - client::{account::register, error::ErrorKind, Error as ClientApiError}, + client::{account::register, error::ErrorKind}, MatrixVersion, }, assign, device_id, user_id, @@ -229,10 +227,7 @@ async fn refresh_token_not_handled() { .await; let res = client.whoami().await.unwrap_err(); - assert_matches!( - res.as_ruma_api_error(), - Some(RumaApiError::ClientApi(ClientApiError { kind: ErrorKind::UnknownToken { .. }, .. })) - ); + assert_matches!(res.client_api_error_kind(), Some(ErrorKind::UnknownToken { .. })); } #[async_test] @@ -360,10 +355,7 @@ async fn refresh_token_handled_failure() { .await; let res = client.whoami().await.unwrap_err(); - assert_matches!( - res.as_ruma_api_error(), - Some(RumaApiError::ClientApi(ClientApiError { kind: ErrorKind::UnknownToken { .. }, .. })) - ) + assert_matches!(res.client_api_error_kind(), Some(ErrorKind::UnknownToken { .. })) } #[async_test] diff --git a/examples/appservice_autojoin/src/main.rs b/examples/appservice_autojoin/src/main.rs index de95c0d3e..eebb3efe5 100644 --- a/examples/appservice_autojoin/src/main.rs +++ b/examples/appservice_autojoin/src/main.rs @@ -8,9 +8,8 @@ use matrix_sdk_appservice::{ events::room::member::{MembershipState, OriginalSyncRoomMemberEvent}, UserId, }, - RumaApiError, }, - ruma::api::client::{error::ErrorKind, uiaa::UiaaResponse}, + ruma::api::client::error::ErrorKind, AppService, AppServiceBuilder, AppServiceRegistration, Result, }; use tracing::trace; @@ -39,15 +38,12 @@ pub fn error_if_user_not_in_use(error: matrix_sdk_appservice::Error) -> Result<( // FIXME: Use if-let chain once available match &error { // If user is already in use that's OK. - matrix_sdk_appservice::Error::Matrix(err) => match err.as_ruma_api_error() { - Some(RumaApiError::Uiaa(UiaaResponse::MatrixError(error))) - if matches!(error.kind, ErrorKind::UserInUse) => - { - Ok(()) - } - // In all other cases return with an error. - _ => Err(error), - }, + matrix_sdk_appservice::Error::Matrix(err) + if err.client_api_error_kind() == Some(&ErrorKind::UserInUse) => + { + Ok(()) + } + // In all other cases return with an error. _ => Err(error), } } From 29aa9a78c34e123036939be69a4c8ff040e15369 Mon Sep 17 00:00:00 2001 From: ismailgulek Date: Mon, 14 Nov 2022 18:56:38 +0300 Subject: [PATCH 05/78] feat(ffi): Expose file messages Expose file messages (#1203) --- bindings/matrix-sdk-ffi/src/lib.rs | 10 +++--- bindings/matrix-sdk-ffi/src/timeline.rs | 41 +++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/lib.rs b/bindings/matrix-sdk-ffi/src/lib.rs index ada68fec1..572411196 100644 --- a/bindings/matrix-sdk-ffi/src/lib.rs +++ b/bindings/matrix-sdk-ffi/src/lib.rs @@ -87,11 +87,11 @@ mod uniffi_types { SlidingSyncView, SlidingSyncViewBuilder, StoppableSpawn, UnreadNotificationsCount, }, timeline::{ - EmoteMessageContent, EncryptedMessage, EventTimelineItem, FormattedBody, ImageInfo, - ImageMessageContent, InsertAtData, Message, MessageFormat, MessageType, - NoticeMessageContent, Reaction, TextMessageContent, ThumbnailInfo, TimelineChange, - TimelineDiff, TimelineItem, TimelineItemContent, TimelineKey, UpdateAtData, VideoInfo, - VideoMessageContent, VirtualTimelineItem, + EmoteMessageContent, EncryptedMessage, EventTimelineItem, FileInfo, FileMessageContent, + FormattedBody, ImageInfo, ImageMessageContent, InsertAtData, Message, MessageFormat, + MessageType, NoticeMessageContent, Reaction, TextMessageContent, ThumbnailInfo, + TimelineChange, TimelineDiff, TimelineItem, TimelineItemContent, TimelineKey, + UpdateAtData, VideoInfo, VideoMessageContent, VirtualTimelineItem, }, }; } diff --git a/bindings/matrix-sdk-ffi/src/timeline.rs b/bindings/matrix-sdk-ffi/src/timeline.rs index d009a9477..651f82839 100644 --- a/bindings/matrix-sdk-ffi/src/timeline.rs +++ b/bindings/matrix-sdk-ffi/src/timeline.rs @@ -277,6 +277,13 @@ impl Message { info: c.info.as_deref().map(Into::into), }, }), + MTy::File(c) => Some(MessageType::File { + content: FileMessageContent { + body: c.body.clone(), + source: Arc::new(c.source.clone()), + info: c.info.as_deref().map(Into::into), + }, + }), MTy::Notice(c) => Some(MessageType::Notice { content: NoticeMessageContent { body: c.body.clone(), @@ -312,6 +319,7 @@ pub enum MessageType { Emote { content: EmoteMessageContent }, Image { content: ImageMessageContent }, Video { content: VideoMessageContent }, + File { content: FileMessageContent }, Notice { content: NoticeMessageContent }, Text { content: TextMessageContent }, } @@ -336,6 +344,13 @@ pub struct VideoMessageContent { pub info: Option, } +#[derive(Clone, uniffi::Record)] +pub struct FileMessageContent { + pub body: String, + pub source: Arc, + pub info: Option, +} + #[derive(Clone, uniffi::Record)] pub struct ImageInfo { pub height: Option, @@ -359,6 +374,14 @@ pub struct VideoInfo { pub blurhash: Option, } +#[derive(Clone, uniffi::Record)] +pub struct FileInfo { + pub mimetype: Option, + pub size: Option, + pub thumbnail_info: Option, + pub thumbnail_source: Option>, +} + #[derive(Clone, uniffi::Record)] pub struct ThumbnailInfo { pub height: Option, @@ -446,6 +469,24 @@ impl From<&matrix_sdk::ruma::events::room::message::VideoInfo> for VideoInfo { } } +impl From<&matrix_sdk::ruma::events::room::message::FileInfo> for FileInfo { + fn from(info: &matrix_sdk::ruma::events::room::message::FileInfo) -> Self { + let thumbnail_info = info.thumbnail_info.as_ref().map(|info| ThumbnailInfo { + height: info.height.map(Into::into), + width: info.width.map(Into::into), + mimetype: info.mimetype.clone(), + size: info.size.map(Into::into), + }); + + Self { + mimetype: info.mimetype.clone(), + size: info.size.map(Into::into), + thumbnail_info, + thumbnail_source: info.thumbnail_source.clone().map(Arc::new), + } + } +} + #[derive(Clone, uniffi::Enum)] pub enum EncryptedMessage { OlmV1Curve25519AesSha2 { From 544da0d324a34acc82c41eebc797e2426b4178c6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 11:11:56 +0100 Subject: [PATCH 06/78] fix(sdk): Fix broken intra-doc link --- crates/matrix-sdk/src/room/timeline/event_item.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_item.rs b/crates/matrix-sdk/src/room/timeline/event_item.rs index 96f836d40..cd56bc263 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item.rs +++ b/crates/matrix-sdk/src/room/timeline/event_item.rs @@ -302,7 +302,7 @@ pub enum TimelineItemContent { } impl TimelineItemContent { - /// If `self` is of the [`Message`][Self:Message] variant, return the inner + /// If `self` is of the [`Message`][Self::Message] variant, return the inner /// [`Message`]. pub fn as_message(&self) -> Option<&Message> { match self { From 4abb08c4a1c5c6ae1bceb7068776df2c5656f1fc Mon Sep 17 00:00:00 2001 From: Flix Date: Fri, 21 Oct 2022 11:31:50 +0200 Subject: [PATCH 07/78] feat!: Make intermediate rooms available right after joining/leaving Co-Authored-By: Jonas Platte Co-Authored-By: Benjamin Kampmann --- crates/matrix-sdk-base/src/client.rs | 55 +++++++ crates/matrix-sdk-base/src/rooms/normal.rs | 61 ++++++++ crates/matrix-sdk-base/src/sliding_sync.rs | 3 + crates/matrix-sdk/src/client/mod.rs | 67 ++++++-- crates/matrix-sdk/src/error.rs | 4 + crates/matrix-sdk/src/room/common.rs | 18 ++- crates/matrix-sdk/src/room/invited.rs | 5 +- crates/matrix-sdk/src/room/joined.rs | 3 +- crates/matrix-sdk/src/room/left.rs | 3 +- crates/matrix-sdk/tests/integration/client.rs | 4 +- .../matrix-sdk/tests/integration/room/left.rs | 26 ++++ .../src/tests.rs | 1 + .../src/tests/invitations.rs | 4 +- .../src/tests/redaction.rs | 6 +- .../src/tests/repeated_join.rs | 145 ++++++++++++++++++ 15 files changed, 376 insertions(+), 29 deletions(-) create mode 100644 testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index ab5911732..bac44601b 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -166,6 +166,12 @@ impl BaseClient { self.store.get_rooms() } + /// Lookup the Room for the given RoomId, or create one, if it didn't exist + /// yet in the store + pub async fn get_or_create_room(&self, room_id: &RoomId, room_type: RoomType) -> Room { + self.store.get_or_create_room(room_id, room_type).await + } + /// Get all the rooms this client knows about. pub fn get_stripped_rooms(&self) -> Vec { self.store.get_stripped_rooms() @@ -579,6 +585,52 @@ impl BaseClient { } } + /// User has left a room. + /// + /// Update the internal and cached state accordingly. Return the final Room. + pub async fn room_left(&self, room_id: &RoomId) -> Result { + let room = self.store.get_or_create_room(room_id, RoomType::Left).await; + if room.room_type() == RoomType::Left { + return Ok(room); + } + + // needs to be updated first + // FIXME: this might be racy. If we receive an update at the same time between + // us checking and submitting the save_changes, we might be overwriting + // some received state. See #1041 + room.set_room_type(RoomType::Left); + let mut room_info = room.clone_info(); + room_info.mark_state_partially_synced(); + let mut changes = StateChanges::default(); + changes.add_room(room_info); + self.store.save_changes(&changes).await?; + + Ok(self.store.get_or_create_room(room_id, RoomType::Left).await) + } + + /// User has joined a room. + /// + /// Update the internal and cached state accordingly. Return the final Room. + pub async fn room_joined(&self, room_id: &RoomId) -> Result { + let room = self.store.get_or_create_room(room_id, RoomType::Joined).await; + if room.room_type() == RoomType::Joined { + return Ok(room); + } + + // needs to be updated first + // FIXME: this might be racy. If we receive an update at the same time between + // us checking and submitting the save_changes, we might be overwriting + // some received state. See #1041 + room.set_room_type(RoomType::Joined); + let mut room_info = room.clone_info(); + room_info.mark_state_partially_synced(); + let mut changes = StateChanges::default(); + changes.add_room(room_info); + self.store.save_changes(&changes).await?; + + Ok(self.store.get_or_create_room(room_id, RoomType::Joined).await) + } + /// Receive a response from a sync call. /// /// # Arguments @@ -637,6 +689,7 @@ impl BaseClient { room_info.update_summary(&new_info.summary); room_info.set_prev_batch(new_info.timeline.prev_batch.as_deref()); + room_info.mark_state_fully_synced(); let mut user_ids = self .handle_state( @@ -717,6 +770,7 @@ impl BaseClient { let room = self.store.get_or_create_room(&room_id, RoomType::Left).await; let mut room_info = room.clone_info(); room_info.mark_as_left(); + room_info.mark_state_partially_synced(); let mut user_ids = self .handle_state( @@ -757,6 +811,7 @@ impl BaseClient { if let Some(r) = self.store.get_room(&room_id) { let mut room_info = r.clone_info(); room_info.mark_as_invited(); + room_info.mark_state_fully_synced(); changes.add_room(room_info); } diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 21e0e35ac..0d4d5bc92 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -119,6 +119,10 @@ impl Room { self.inner.read().unwrap().room_type } + pub(crate) fn set_room_type(&self, room_type: RoomType) { + self.inner.write().unwrap().room_type = room_type; + } + /// Whether this room's [`RoomType`](CreateRoomType) is `m.space`. pub fn is_space(&self) -> bool { self.inner.read().unwrap().room_type().map_or(false, |t| *t == CreateRoomType::Space) @@ -139,6 +143,17 @@ impl Room { self.inner.read().unwrap().members_synced } + /// Check if the room states have been synced + /// + /// States might be missing if we have only seen the room_id of this Room + /// so far, for example as the response for a `create_room` request without + /// being synced yet. + /// + /// Returns true if the state is fully synced, false otherwise. + pub fn is_state_fully_synced(&self) -> bool { + self.inner.read().unwrap().sync_info == SyncInfo::FullySynced + } + /// Get the `prev_batch` token that was received from the last sync. May be /// `None` if the last sync contained the full room history. pub fn last_prev_batch(&self) -> Option { @@ -499,11 +514,41 @@ pub struct RoomInfo { pub(crate) members_synced: bool, /// The prev batch of this room we received during the last sync. pub(crate) last_prev_batch: Option, + /// How much we know about this room. + #[serde(default = "SyncInfo::complete")] // see fn docs for why we use this default + pub(crate) sync_info: SyncInfo, /// Base room info which holds some basic event contents important for the /// room state. pub(crate) base_info: BaseRoomInfo, } +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +pub(crate) enum SyncInfo { + /// We only know the room exists and whether it is in invite / joined / left + /// state. + /// + /// This is the case when we have a limited sync or only seen the room + /// because of a request we've done, like a room creation event. + NoState, + + /// Some states have been synced, but they might have been filtered or is + /// stale, as it is from a room we've left. + PartiallySynced, + + /// We have all the latest state events + FullySynced, +} + +impl SyncInfo { + // The sync_info field introduced a new field in the database schema, but to + // avoid a database migration, we let serde assume that if the room is in + // the database, yet the field isn't, we have synced it before this field + // was introduce - which was a a full sync. + fn complete() -> Self { + SyncInfo::FullySynced + } +} + impl RoomInfo { #[doc(hidden)] // used by store tests, otherwise it would be pub(crate) pub fn new(room_id: &RoomId, room_type: RoomType) -> Self { @@ -512,6 +557,7 @@ impl RoomInfo { room_type, notification_counts: Default::default(), summary: Default::default(), + sync_info: SyncInfo::NoState, members_synced: false, last_prev_batch: None, base_info: BaseRoomInfo::new(), @@ -543,6 +589,21 @@ impl RoomInfo { self.members_synced = false; } + /// Mark this Room still missing some state information + pub fn mark_state_partially_synced(&mut self) { + self.sync_info = SyncInfo::PartiallySynced; + } + + /// Mark this Room still having all state synced + pub fn mark_state_fully_synced(&mut self) { + self.sync_info = SyncInfo::FullySynced; + } + + /// Mark this Room still having no state synced + pub fn mark_state_not_synced(&mut self) { + self.sync_info = SyncInfo::NoState; + } + /// Set the `prev_batch`-token. /// Returns whether the token has differed and thus has been upgraded: /// `false` means no update was applied as the were the same diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index c18c0293a..96794e446 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -90,10 +90,12 @@ impl BaseClient { let invite_states = &room_data.invite_state; let room = store.get_or_create_stripped_room(&room_id).await; let mut room_info = room.clone_info(); + room_info.mark_state_partially_synced(); if let Some(r) = store.get_room(&room_id) { let mut room_info = r.clone_info(); room_info.mark_as_invited(); // FIXME: this might not be accurate + room_info.mark_state_partially_synced(); changes.add_room(room_info); } @@ -107,6 +109,7 @@ impl BaseClient { let room = store.get_or_create_room(&room_id, RoomType::Joined).await; let mut room_info = room.clone_info(); room_info.mark_as_joined(); // FIXME: this might not be accurate + room_info.mark_state_partially_synced(); // FIXME not yet supported by sliding sync. // room_info.update_summary(&room_data.summary); diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 369f2329d..11ffd6cc1 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -27,8 +27,8 @@ use dashmap::DashMap; use futures_core::stream::Stream; use futures_signals::signal::Signal; use matrix_sdk_base::{ - deserialized_responses::SyncResponse, BaseClient, SendOutsideWasm, Session, SessionMeta, - SessionTokens, StateStore, SyncOutsideWasm, + deserialized_responses::SyncResponse, BaseClient, RoomType, SendOutsideWasm, Session, + SessionMeta, SessionTokens, StateStore, SyncOutsideWasm, }; use matrix_sdk_common::{ instant::Instant, @@ -1496,12 +1496,11 @@ impl Client { /// # Arguments /// /// * `room_id` - The `RoomId` of the room to be joined. - pub async fn join_room_by_id( - &self, - room_id: &RoomId, - ) -> HttpResult { + pub async fn join_room_by_id(&self, room_id: &RoomId) -> Result { let request = join_room_by_id::v3::Request::new(room_id); - self.send(request, None).await + let response = self.send(request, None).await?; + let base_room = self.base_client().room_joined(&response.room_id).await?; + room::Joined::new(self, base_room).ok_or(Error::InconsistentState) } /// Join a room by `RoomId`. @@ -1517,11 +1516,13 @@ impl Client { &self, alias: &RoomOrAliasId, server_names: &[OwnedServerName], - ) -> HttpResult { + ) -> Result { let request = assign!(join_room_by_id_or_alias::v3::Request::new(alias), { server_name: server_names, }); - self.send(request, None).await + let response = self.send(request, None).await?; + let base_room = self.base_client().room_joined(&response.room_id).await?; + room::Joined::new(self, base_room).ok_or(Error::InconsistentState) } /// Search the homeserver's directory of public rooms. @@ -1601,9 +1602,53 @@ impl Client { pub async fn create_room( &self, room: impl Into>, - ) -> HttpResult { + ) -> HttpResult { let request = room.into(); - self.send(request, None).await + let response = self.send(request, None).await?; + + let base_room = + self.base_client().get_or_create_room(&response.room_id, RoomType::Joined).await; + Ok(room::Joined::new(self, base_room).unwrap()) + } + + /// Create a direct message room with the specified user. + #[cfg(not(feature = "e2e-encryption"))] + pub async fn create_dm_room(&self, user_id: &UserId) -> Result { + use ruma::{ + api::client::room::create_room::v3::RoomPreset, events::direct::DirectEventContent, + }; + + // First we create the DM room, where we invite the user and tell the + // invitee that the room should be a DM. + let invite = &[user_id.to_owned()]; + + let request = assign!(ruma::api::client::room::create_room::v3::Request::new(), { + invite, + is_direct: true, + preset: Some(RoomPreset::TrustedPrivateChat), + }); + + let room = self.create_room(request).await?; + + // Now we need to mark the room as a DM for ourselves, we fetch the + // existing `m.direct` event and append the room to the list of DMs we + // have with this user. + let mut content = self + .account() + .account_data::() + .await? + .map(|c| c.deserialize()) + .transpose()? + .unwrap_or_default(); + + content.entry(user_id.to_owned()).or_default().push(room.room_id().to_owned()); + + // TODO We should probably save the fact that we need to send this out + // because otherwise we might end up in a state where we have a DM that + // isn't marked as one. + self.account().set_account_data(content).await?; + + Ok(room) } /// Search the homeserver's directory for public rooms with a filter. diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index 54464586e..690f66b7a 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -247,6 +247,10 @@ pub enum Error { #[error(transparent)] SlidingSync(#[from] crate::sliding_sync::Error), + /// The client is in inconsistent state. + #[error("The internal client state is inconsistent.")] + InconsistentState, + /// An other error was raised /// this might happen because encryption was enabled on the base-crate /// but not here and that raised. diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 82c9a3e2d..e671394bb 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -40,10 +40,11 @@ use serde::de::DeserializeOwned; #[cfg(feature = "experimental-timeline")] use super::timeline::Timeline; +use super::Joined; use crate::{ event_handler::{EventHandler, EventHandlerHandle, SyncEvent}, media::{MediaFormat, MediaRequest}, - room::{RoomMember, RoomType}, + room::{Left, RoomMember, RoomType}, BaseRoom, Client, Error, HttpError, HttpResult, Result, }; @@ -96,21 +97,22 @@ impl Common { /// Leave this room. /// /// Only invited and joined rooms can be left. - pub(crate) async fn leave(&self) -> Result<()> { + pub(crate) async fn leave(&self) -> Result { let request = leave_room::v3::Request::new(self.inner.room_id()); - let _response = self.client.send(request, None).await?; + self.client.send(request, None).await?; - Ok(()) + let base_room = self.client.base_client().room_left(self.room_id()).await?; + Left::new(&self.client, base_room).ok_or(Error::InconsistentState) } /// Join this room. /// /// Only invited and left rooms can be joined via this method. - pub(crate) async fn join(&self) -> Result<()> { + pub(crate) async fn join(&self) -> Result { let request = join_room_by_id::v3::Request::new(self.inner.room_id()); - let _response = self.client.send(request, None).await?; - - Ok(()) + let response = self.client.send(request, None).await?; + let base_room = self.client.base_client().room_joined(&response.room_id).await?; + Joined::new(&self.client, base_room).ok_or(Error::InconsistentState) } /// Get the inner client saved in this room instance. diff --git a/crates/matrix-sdk/src/room/invited.rs b/crates/matrix-sdk/src/room/invited.rs index 031f13670..a7ea8c5c0 100644 --- a/crates/matrix-sdk/src/room/invited.rs +++ b/crates/matrix-sdk/src/room/invited.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use thiserror::Error; +use super::{Joined, Left}; use crate::{ room::{Common, RoomMember}, BaseRoom, Client, Error, Result, RoomType, @@ -52,12 +53,12 @@ impl Invited { } /// Reject the invitation. - pub async fn reject_invitation(&self) -> Result<()> { + pub async fn reject_invitation(&self) -> Result { self.inner.leave().await } /// Accept the invitation. - pub async fn accept_invitation(&self) -> Result<()> { + pub async fn accept_invitation(&self) -> Result { self.inner.join().await } diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index eb50752ea..4f5f1fbe3 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -35,6 +35,7 @@ use tracing::debug; #[cfg(feature = "e2e-encryption")] use tracing::instrument; +use super::Left; use crate::{ attachment::AttachmentConfig, error::HttpResult, room::Common, BaseRoom, Client, Result, RoomType, @@ -83,7 +84,7 @@ impl Joined { } /// Leave this room. - pub async fn leave(&self) -> Result<()> { + pub async fn leave(&self) -> Result { self.inner.leave().await } diff --git a/crates/matrix-sdk/src/room/left.rs b/crates/matrix-sdk/src/room/left.rs index 6086fca59..cdb4a2b6c 100644 --- a/crates/matrix-sdk/src/room/left.rs +++ b/crates/matrix-sdk/src/room/left.rs @@ -2,6 +2,7 @@ use std::ops::Deref; use ruma::api::client::membership::forget_room; +use super::Joined; use crate::{room::Common, BaseRoom, Client, Result, RoomType}; /// A room in the left state. @@ -31,7 +32,7 @@ impl Left { } /// Join this room. - pub async fn join(&self) -> Result<()> { + pub async fn join(&self) -> Result { self.inner.join().await } diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs index 1172fd312..f81eed9fa 100644 --- a/crates/matrix-sdk/tests/integration/client.rs +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -406,7 +406,7 @@ async fn join_room_by_id() { assert_eq!( // this is the `join_by_room_id::Response` but since no PartialEq we check the RoomId // field - client.join_room_by_id(room_id).await.unwrap().room_id, + client.join_room_by_id(room_id).await.unwrap().room_id(), room_id ); } @@ -431,7 +431,7 @@ async fn join_room_by_id_or_alias() { .join_room_by_id_or_alias(room_id, &["server.com".try_into().unwrap()]) .await .unwrap() - .room_id, + .room_id(), room_id!("!testroom:example.org") ); } diff --git a/crates/matrix-sdk/tests/integration/room/left.rs b/crates/matrix-sdk/tests/integration/room/left.rs index 6e422608f..a11383f9f 100644 --- a/crates/matrix-sdk/tests/integration/room/left.rs +++ b/crates/matrix-sdk/tests/integration/room/left.rs @@ -2,6 +2,7 @@ use std::time::Duration; use matrix_sdk::config::SyncSettings; use matrix_sdk_test::{async_test, test_json}; +use serde_json::json; use wiremock::{ matchers::{header, method, path_regex}, Mock, ResponseTemplate, @@ -30,3 +31,28 @@ async fn forget_room() { room.forget().await.unwrap(); } + +#[async_test] +async fn rejoin_room() { + let (client, server) = logged_in_client().await; + + Mock::given(method("POST")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/join")) + .and(header("authorization", "Bearer 1234")) + .respond_with( + ResponseTemplate::new(200) + .set_body_json(json!({ "room_id": *test_json::DEFAULT_SYNC_ROOM_ID })), + ) + .mount(&server) + .await; + mock_sync(&server, &*test_json::LEAVE_SYNC, None).await; + + let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); + + let _response = client.sync_once(sync_settings).await.unwrap(); + + let room = client.get_left_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap(); + + let joined_room = room.join().await.unwrap(); + assert!(!joined_room.is_state_fully_synced()) +} diff --git a/testing/matrix-sdk-integration-testing/src/tests.rs b/testing/matrix-sdk-integration-testing/src/tests.rs index 6ec7ef3b9..108c15fa5 100644 --- a/testing/matrix-sdk-integration-testing/src/tests.rs +++ b/testing/matrix-sdk-integration-testing/src/tests.rs @@ -1,2 +1,3 @@ mod invitations; mod redaction; +mod repeated_join; diff --git a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs index 810508612..f7c14cc65 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs @@ -18,8 +18,8 @@ async fn test_invitation_details() -> Result<()> { is_direct: true, }); - let response = tamatoa.create_room(request).await?; - let room_id = response.room_id; + let room = tamatoa.create_room(request).await?; + let room_id = room.room_id().to_owned(); // the actual test sebastian.sync_once(Default::default()).await?; diff --git a/testing/matrix-sdk-integration-testing/src/tests/redaction.rs b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs index dbb29b12c..06ab9922e 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/redaction.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs @@ -32,7 +32,8 @@ async fn test_redacting_name() -> Result<()> { is_direct: true, }); - let room_id = tamatoa.create_room(request).await?.room_id; + let room = tamatoa.create_room(request).await?; + let room_id = room.room_id().to_owned(); for _ in 0..=10 { sync_once(&tamatoa).await?; if tamatoa.get_joined_room(&room_id).is_some() { @@ -101,7 +102,8 @@ async fn test_redacting_name_static() -> Result<()> { is_direct: true, }); - let room_id = tamatoa.create_room(request).await?.room_id; + let room = tamatoa.create_room(request).await?; + let room_id = room.room_id().to_owned(); for _ in 0..=10 { sync_once(&tamatoa).await?; if tamatoa.get_joined_room(&room_id).is_some() { diff --git a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs new file mode 100644 index 000000000..bc1e8bc28 --- /dev/null +++ b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs @@ -0,0 +1,145 @@ +use std::{sync::Arc, time::Duration}; + +use anyhow::Result; +use assign::assign; +use matrix_sdk::{ + event_handler::Ctx, + room::Room, + ruma::{ + api::client::room::create_room::v3::Request as CreateRoomRequest, + events::room::member::{MembershipState, StrippedRoomMemberEvent}, + }, + Client, RoomType, +}; +use tokio::sync::Notify; + +use crate::helpers::get_client_for_user; + +#[tokio::test(flavor = "multi_thread", worker_threads = 4)] +#[ignore = "Flaky because of the race of #1041; Also membership updates aren't fully supported yet"] +async fn test_repeated_join_leave() -> Result<()> { + let peter = get_client_for_user("peter".to_owned()).await?; + // FIXME: Run once with memory, once with sled + let karl = get_client_for_user("karl".to_owned()).await?; + let karl_id = karl.user_id().expect("karl has a userid!").to_owned(); + + // Create a room and invite karl. + let invites = [karl_id.clone()]; + let request = assign!(CreateRoomRequest::new(), { + invite: &invites, + is_direct: true, + }); + + // Sync after 1 second to so that create_room receives the event it is waiting + // for. + let peter_clone = peter.clone(); + tokio::spawn(async move { + tokio::time::sleep(Duration::from_secs(1)).await; + peter_clone.sync_once(Default::default()).await + }); + + let created_room = peter.create_room(request).await?; + let room_id = created_room.room_id(); + + // Sync karl once to ensure he got the invite. + karl.sync_once(Default::default()).await?; + + // Continuously sync karl from now on. + let karl_clone = karl.clone(); + let join_handle = tokio::spawn(async move { + karl_clone.sync(Default::default()).await.unwrap(); + }); + let invite_signal = Arc::new(Notify::new()); + karl.add_event_handler_context(invite_signal.clone()); + karl.add_event_handler(signal_on_invite); + + for i in 0..3 { + println!("Iteration {i}"); + + // Test that karl has the expected state in its client. + assert!(karl.get_invited_room(room_id).is_some()); + assert!(karl.get_joined_room(room_id).is_none()); + assert!(karl.get_left_room(room_id).is_none()); + + let room = karl.get_room(room_id).expect("karl has the room"); + let membership = room.get_member_no_sync(&karl_id).await?.expect("karl was invited"); + assert_eq!(*membership.membership(), MembershipState::Invite); + + // Join the room + println!("Joining.."); + let room = + karl.get_invited_room(room_id).expect("karl has the room").accept_invitation().await?; + println!("Done"); + let membership = room.get_member_no_sync(&karl_id).await?.expect("karl joined"); + assert_eq!(*membership.membership(), MembershipState::Join); + + assert!(karl.get_invited_room(room_id).is_none()); + assert!(karl.get_joined_room(room_id).is_some()); + assert!(karl.get_left_room(room_id).is_none()); + + // Leave the room + println!("Leaving.."); + let room = room.leave().await?; + println!("Done"); + let membership = room.get_member_no_sync(&karl_id).await?.expect("karl left"); + assert_eq!(*membership.membership(), MembershipState::Leave); + + assert!(karl.get_invited_room(room_id).is_none()); + assert!(karl.get_joined_room(room_id).is_none()); + assert!(karl.get_left_room(room_id).is_some()); + + // Invite karl again and wait for karl to receive the invite. + println!("Inviting.."); + let room = peter.get_joined_room(room_id).expect("peter created the room!"); + room.invite_user_by_id(&karl_id).await?; + println!("Waiting to receive invite.."); + invite_signal.notified().await; + } + + // Stop the sync. + join_handle.abort(); + + // Now check the underlying state store that it also has the correct information + // (for when the client restarts). + let invited = karl.store().get_invited_user_ids(room_id).await?; + assert_eq!(invited.len(), 1); + assert_eq!(invited[0], karl_id); + + let joined = karl.store().get_joined_user_ids(room_id).await?; + assert!(!joined.contains(&karl_id)); + + let event = + karl.store().get_member_event(room_id, &karl_id).await?.expect("member event should exist"); + assert_eq!(*event.membership(), MembershipState::Invite); + + // Yay, test succeeded + Ok(()) +} + +async fn signal_on_invite( + event: StrippedRoomMemberEvent, + room: Room, + client: Client, + sender: Ctx>, +) { + let own_id = client.user_id().expect("client is logged in"); + if event.sender == own_id { + return; + } + + if room.room_type() != RoomType::Invited { + return; + } + + if event.content.membership != MembershipState::Invite { + return; + } + + let invited = &event.state_key; + if invited != own_id { + return; + } + + // Send signal that we received an invite. + sender.notify_one(); +} From 43dd4452cd8105375dc9854de57b5f215385e80b Mon Sep 17 00:00:00 2001 From: Flix Date: Fri, 21 Oct 2022 13:54:52 +0200 Subject: [PATCH 08/78] fix: Check encryption state in intermediate rooms --- bindings/matrix-sdk-ffi/src/room.rs | 12 ++-- crates/matrix-sdk-base/src/client.rs | 2 + crates/matrix-sdk-base/src/rooms/normal.rs | 42 ++++++++++++- crates/matrix-sdk/src/client/builder.rs | 1 + crates/matrix-sdk/src/client/mod.rs | 2 + crates/matrix-sdk/src/encryption/mod.rs | 2 +- crates/matrix-sdk/src/error.rs | 14 +++++ crates/matrix-sdk/src/room/common.rs | 70 +++++++++++++++++++++- crates/matrix-sdk/src/room/joined.rs | 6 +- 9 files changed, 139 insertions(+), 12 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/room.rs b/bindings/matrix-sdk-ffi/src/room.rs index e572727b3..ff927fd31 100644 --- a/bindings/matrix-sdk-ffi/src/room.rs +++ b/bindings/matrix-sdk-ffi/src/room.rs @@ -61,10 +61,6 @@ impl Room { self.room.is_public() } - pub fn is_encrypted(&self) -> bool { - self.room.is_encrypted() - } - pub fn is_space(&self) -> bool { self.room.is_space() } @@ -100,6 +96,14 @@ impl Room { RUNTIME.block_on(async move { Ok(r.display_name().await?.to_string()) }) } + pub fn is_encrypted(&self) -> Result { + let room = self.room.clone(); + RUNTIME.block_on(async move { + let is_encrypted = room.is_encrypted().await?; + Ok(is_encrypted) + }) + } + pub fn member_avatar_url(&self, user_id: String) -> Result> { let room = self.room.clone(); let user_id = user_id; diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index bac44601b..cd9787428 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -690,6 +690,7 @@ impl BaseClient { room_info.update_summary(&new_info.summary); room_info.set_prev_batch(new_info.timeline.prev_batch.as_deref()); room_info.mark_state_fully_synced(); + room_info.mark_encryption_state_synced(); let mut user_ids = self .handle_state( @@ -812,6 +813,7 @@ impl BaseClient { let mut room_info = r.clone_info(); room_info.mark_as_invited(); room_info.mark_state_fully_synced(); + room_info.mark_encryption_state_synced(); changes.add_room(room_info); } diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 0d4d5bc92..102fbc8d3 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -154,6 +154,16 @@ impl Room { self.inner.read().unwrap().sync_info == SyncInfo::FullySynced } + /// Check if the room has it's encryption event synced. + /// + /// The encryption event can be missing when the room hasn't appeared in + /// sync yet. + /// + /// Returns true if the encryption state is synced, false otherwise. + pub fn is_encryption_state_synced(&self) -> bool { + self.inner.read().unwrap().encryption_state_synced + } + /// Get the `prev_batch` token that was received from the last sync. May be /// `None` if the last sync contained the full room history. pub fn last_prev_batch(&self) -> Option { @@ -517,6 +527,9 @@ pub struct RoomInfo { /// How much we know about this room. #[serde(default = "SyncInfo::complete")] // see fn docs for why we use this default pub(crate) sync_info: SyncInfo, + /// Whether or not the encryption info was been synced. + #[serde(default = "encryption_state_default")] // see fn docs for why we use this default + pub(crate) encryption_state_synced: bool, /// Base room info which holds some basic event contents important for the /// room state. pub(crate) base_info: BaseRoomInfo, @@ -543,12 +556,20 @@ impl SyncInfo { // The sync_info field introduced a new field in the database schema, but to // avoid a database migration, we let serde assume that if the room is in // the database, yet the field isn't, we have synced it before this field - // was introduce - which was a a full sync. + // was introduced - which was a a full sync. fn complete() -> Self { SyncInfo::FullySynced } } +// The encryption_state_synced field introduced a new field in the database +// schema, but to avoid a database migration, we let serde assume that if +// the room is in the database, yet the field isn't, we have synced it +// before this field was introduced - which was a a full sync. +fn encryption_state_default() -> bool { + true +} + impl RoomInfo { #[doc(hidden)] // used by store tests, otherwise it would be pub(crate) pub fn new(room_id: &RoomId, room_type: RoomType) -> Self { @@ -557,9 +578,10 @@ impl RoomInfo { room_type, notification_counts: Default::default(), summary: Default::default(), - sync_info: SyncInfo::NoState, members_synced: false, last_prev_batch: None, + sync_info: SyncInfo::NoState, + encryption_state_synced: false, base_info: BaseRoomInfo::new(), } } @@ -604,6 +626,16 @@ impl RoomInfo { self.sync_info = SyncInfo::NoState; } + /// Mark this Room as having the encryption state synced + pub fn mark_encryption_state_synced(&mut self) { + self.encryption_state_synced = true; + } + + /// Mark this Room still missing encryption state information + pub fn mark_encryption_state_missing(&mut self) { + self.encryption_state_synced = false; + } + /// Set the `prev_batch`-token. /// Returns whether the token has differed and thus has been upgraded: /// `false` means no update was applied as the were the same @@ -621,6 +653,12 @@ impl RoomInfo { self.base_info.encryption.is_some() } + /// Set the encryption event content in this room. + pub fn set_encryption_event(&mut self, event: Option) -> &Self { + self.base_info.encryption = event; + self + } + /// Handle the given state event. /// /// Returns true if the event modified the info, false otherwise. diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index a73aeef3c..bfb7719e3 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -400,6 +400,7 @@ impl ClientBuilder { #[cfg(feature = "e2e-encryption")] key_claim_lock: Default::default(), members_request_locks: Default::default(), + encryption_state_request_locks: Default::default(), typing_notice_times: Default::default(), event_handlers: Default::default(), notification_handlers: Default::default(), diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 11ffd6cc1..41b9add70 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -147,6 +147,8 @@ pub(crate) struct ClientInner { #[cfg(feature = "e2e-encryption")] pub(crate) key_claim_lock: Mutex<()>, pub(crate) members_request_locks: DashMap>>, + /// Locks for requests on the encryption state of rooms. + pub(crate) encryption_state_request_locks: DashMap>>, pub(crate) typing_notice_times: DashMap, /// Event handlers. See `add_event_handler`. pub(crate) event_handlers: EventHandlerStore, diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index e3d8ef937..6f0881e53 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -907,7 +907,7 @@ mod tests { client.base_client().receive_sync_response(response).await.unwrap(); let room = client.get_joined_room(room_id).expect("Room should exist"); - assert!(room.is_encrypted()); + assert!(room.is_encrypted().await.expect("Getting encryption state")); let event_id = event_id!("$1:example.org"); let reaction = ReactionEventContent::new(Relation::new(event_id.into(), "🐈".to_owned())); diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index 690f66b7a..e65e1aedd 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -162,6 +162,20 @@ impl HttpError { _ => None, } } + + /// Return whether the error was a 404 not found response. + pub fn is_not_found(&self) -> bool { + match self { + HttpError::Reqwest(err) => err.status() == Some(StatusCode::NOT_FOUND), + HttpError::AuthenticationRequired => false, + HttpError::NotClientRequest => false, + HttpError::Api(_) => false, + HttpError::IntoHttp(_) => false, + HttpError::Server(status) => *status == StatusCode::NOT_FOUND, + HttpError::UnableToCloneRequest => false, + HttpError::RefreshToken(_) => false, + } + } } /// Internal representation of errors. diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index e671394bb..1656df1fc 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -3,6 +3,7 @@ use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc}; use matrix_sdk_base::{ deserialized_responses::{MembersResponse, TimelineEvent}, store::StateStoreExt, + StateChanges, }; use matrix_sdk_common::locks::Mutex; #[cfg(feature = "e2e-encryption")] @@ -17,6 +18,7 @@ use ruma::{ membership::{get_member_events, join_room_by_id, leave_room}, message::get_message_events, room::get_room_event, + state::get_state_events_for_key, tag::{create_tag, delete_tag}, Direction, }, @@ -24,8 +26,8 @@ use ruma::{ events::{ direct::DirectEventContent, room::{ - history_visibility::HistoryVisibility, server_acl::RoomServerAclEventContent, - MediaSource, + encryption::RoomEncryptionEventContent, history_visibility::HistoryVisibility, + server_acl::RoomServerAclEventContent, MediaSource, }, tag::{TagInfo, TagName}, AnyRoomAccountDataEvent, AnyStateEvent, AnySyncStateEvent, EmptyStateKey, RedactContent, @@ -317,6 +319,70 @@ impl Common { } } + async fn request_encryption_state(&self) -> Result> { + if let Some(mutex) = self + .client + .inner + .encryption_state_request_locks + .get(self.inner.room_id()) + .map(|m| m.clone()) + { + // If a encryption state request is already going on, await the release of + // the lock. + _ = mutex.lock().await; + + Ok(None) + } else { + let mutex = Arc::new(Mutex::new(())); + self.client + .inner + .encryption_state_request_locks + .insert(self.inner.room_id().to_owned(), mutex.clone()); + + let _guard = mutex.lock().await; + + let request = get_state_events_for_key::v3::Request::new( + self.inner.room_id(), + StateEventType::RoomEncryption, + "", + ); + let response = match self.client.send(request, None).await { + Ok(response) => { + Some(response.content.deserialize_as::()?) + } + Err(err) if err.is_not_found() => None, + Err(err) => return Err(err.into()), + }; + + // FIXME: This can race with the sync setting the room_info and erase its + // state.. + let mut room_info = self.inner.clone_info(); + room_info.mark_encryption_state_synced(); + room_info.set_encryption_event(response.clone()); + let mut changes = StateChanges::default(); + changes.add_room(room_info.clone()); + self.client.store().save_changes(&changes).await?; + self.update_summary(room_info); + + self.client.inner.encryption_state_request_locks.remove(self.inner.room_id()); + + Ok(response) + } + } + + /// Check whether this room is encrypted. If the room encryption state is + /// not synced yet, it will send a request to fetch it. + /// + /// Returns true if the room is encrypted, otherwise false. + pub async fn is_encrypted(&self) -> Result { + if !self.is_encryption_state_synced() { + let encryption = self.request_encryption_state().await?; + Ok(encryption.is_some()) + } else { + Ok(self.inner.is_encrypted()) + } + } + async fn ensure_members(&self) -> Result<()> { if !self.are_events_visible() { return Ok(()); diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index 4f5f1fbe3..e61a98256 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -297,7 +297,7 @@ impl Joined { }; const SYNC_WAIT_TIME: Duration = Duration::from_secs(3); - if !self.is_encrypted() { + if !self.is_encrypted().await? { let content = RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2); self.send_state_event(content).await?; @@ -551,7 +551,7 @@ impl Joined { }; #[cfg(feature = "e2e-encryption")] - let (content, event_type) = if self.is_encrypted() { + let (content, event_type) = if self.is_encrypted().await? { // Reactions are currently famously not encrypted, skip encrypting // them until they are. if event_type == "m.reaction" { @@ -730,7 +730,7 @@ impl Joined { config: AttachmentConfig<'_>, ) -> Result { #[cfg(feature = "e2e-encryption")] - let content = if self.is_encrypted() { + let content = if self.is_encrypted().await? { self.client .prepare_encrypted_attachment_message( body, From 9d150e5cc676cfc9d1627b66f1302281b3213bee Mon Sep 17 00:00:00 2001 From: Flix Date: Fri, 21 Oct 2022 16:51:01 +0200 Subject: [PATCH 09/78] fix: Adjust repeated_join test to new changes --- crates/matrix-sdk-base/src/client.rs | 78 ++++++++++--------- crates/matrix-sdk-base/src/rooms/normal.rs | 4 - .../src/tests/repeated_join.rs | 5 +- 3 files changed, 42 insertions(+), 45 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index cd9787428..15371b5b4 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -585,29 +585,6 @@ impl BaseClient { } } - /// User has left a room. - /// - /// Update the internal and cached state accordingly. Return the final Room. - pub async fn room_left(&self, room_id: &RoomId) -> Result { - let room = self.store.get_or_create_room(room_id, RoomType::Left).await; - if room.room_type() == RoomType::Left { - return Ok(room); - } - - // needs to be updated first - // FIXME: this might be racy. If we receive an update at the same time between - // us checking and submitting the save_changes, we might be overwriting - // some received state. See #1041 - room.set_room_type(RoomType::Left); - let mut room_info = room.clone_info(); - room_info.mark_state_partially_synced(); - let mut changes = StateChanges::default(); - changes.add_room(room_info); - self.store.save_changes(&changes).await?; - - Ok(self.store.get_or_create_room(room_id, RoomType::Left).await) - } - /// User has joined a room. /// /// Update the internal and cached state accordingly. Return the final Room. @@ -617,18 +594,43 @@ impl BaseClient { return Ok(room); } - // needs to be updated first // FIXME: this might be racy. If we receive an update at the same time between // us checking and submitting the save_changes, we might be overwriting // some received state. See #1041 - room.set_room_type(RoomType::Joined); let mut room_info = room.clone_info(); + room_info.mark_as_joined(); room_info.mark_state_partially_synced(); + room_info.mark_members_missing(); // the own member event changed let mut changes = StateChanges::default(); - changes.add_room(room_info); - self.store.save_changes(&changes).await?; + changes.add_room(room_info.clone()); + self.store.save_changes(&changes).await?; // Update the store + room.update_summary(room_info); // Update the cached room handle - Ok(self.store.get_or_create_room(room_id, RoomType::Joined).await) + Ok(room) + } + + /// User has left a room. + /// + /// Update the internal and cached state accordingly. Return the final Room. + pub async fn room_left(&self, room_id: &RoomId) -> Result { + let room = self.store.get_or_create_room(room_id, RoomType::Left).await; + if room.room_type() == RoomType::Left { + return Ok(room); + } + + // FIXME: this might be racy. If we receive an update at the same time between + // us checking and submitting the save_changes, we might be overwriting + // some received state. See #1041 + let mut room_info = room.clone_info(); + room_info.mark_as_left(); + room_info.mark_state_partially_synced(); + room_info.mark_members_missing(); // the own member event changed + let mut changes = StateChanges::default(); + changes.add_room(room_info.clone()); + self.store.save_changes(&changes).await?; // Update the store + room.update_summary(room_info); // Update the cached room handle + + Ok(room) } /// Receive a response from a sync call. @@ -929,21 +931,21 @@ impl BaseClient { } ambiguity_cache.handle_event(&changes, room_id, &member).await?; + } - if member.state_key() == member.sender() { - changes - .profiles - .entry(room_id.to_owned()) - .or_default() - .insert(member.sender().to_owned(), member.borrow().into()); - } - + if member.state_key() == member.sender() { changes - .members + .profiles .entry(room_id.to_owned()) .or_default() - .insert(member.state_key().to_owned(), member); + .insert(member.sender().to_owned(), member.borrow().into()); } + + changes + .members + .entry(room_id.to_owned()) + .or_default() + .insert(member.state_key().to_owned(), member); } #[cfg(feature = "e2e-encryption")] diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 102fbc8d3..47450f9c0 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -119,10 +119,6 @@ impl Room { self.inner.read().unwrap().room_type } - pub(crate) fn set_room_type(&self, room_type: RoomType) { - self.inner.write().unwrap().room_type = room_type; - } - /// Whether this room's [`RoomType`](CreateRoomType) is `m.space`. pub fn is_space(&self) -> bool { self.inner.read().unwrap().room_type().map_or(false, |t| *t == CreateRoomType::Space) diff --git a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs index bc1e8bc28..56f77d40e 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs @@ -16,7 +16,6 @@ use tokio::sync::Notify; use crate::helpers::get_client_for_user; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] -#[ignore = "Flaky because of the race of #1041; Also membership updates aren't fully supported yet"] async fn test_repeated_join_leave() -> Result<()> { let peter = get_client_for_user("peter".to_owned()).await?; // FIXME: Run once with memory, once with sled @@ -70,7 +69,7 @@ async fn test_repeated_join_leave() -> Result<()> { let room = karl.get_invited_room(room_id).expect("karl has the room").accept_invitation().await?; println!("Done"); - let membership = room.get_member_no_sync(&karl_id).await?.expect("karl joined"); + let membership = room.get_member(&karl_id).await?.expect("karl joined"); assert_eq!(*membership.membership(), MembershipState::Join); assert!(karl.get_invited_room(room_id).is_none()); @@ -81,7 +80,7 @@ async fn test_repeated_join_leave() -> Result<()> { println!("Leaving.."); let room = room.leave().await?; println!("Done"); - let membership = room.get_member_no_sync(&karl_id).await?.expect("karl left"); + let membership = room.get_member(&karl_id).await?.expect("karl left"); assert_eq!(*membership.membership(), MembershipState::Leave); assert!(karl.get_invited_room(room_id).is_none()); From 65721aafb9781082ee68b1fac4573593aa383d7a Mon Sep 17 00:00:00 2001 From: Flix Date: Sat, 22 Oct 2022 11:08:49 +0200 Subject: [PATCH 10/78] fix: Lock syncing to the store to avoid races --- crates/matrix-sdk-base/src/client.rs | 18 ++++++++++++------ crates/matrix-sdk-base/src/store/mod.rs | 12 ++++++++++++ crates/matrix-sdk/src/room/common.rs | 4 ++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 15371b5b4..527412456 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -28,6 +28,7 @@ use matrix_sdk_common::{ SyncTimelineEvent, Timeline, }, instant::Instant, + locks::RwLock, }; #[cfg(feature = "e2e-encryption")] use matrix_sdk_crypto::{ @@ -594,9 +595,7 @@ impl BaseClient { return Ok(room); } - // FIXME: this might be racy. If we receive an update at the same time between - // us checking and submitting the save_changes, we might be overwriting - // some received state. See #1041 + let sync_lock = self.sync_lock().read().await; let mut room_info = room.clone_info(); room_info.mark_as_joined(); room_info.mark_state_partially_synced(); @@ -605,6 +604,7 @@ impl BaseClient { changes.add_room(room_info.clone()); self.store.save_changes(&changes).await?; // Update the store room.update_summary(room_info); // Update the cached room handle + drop(sync_lock); Ok(room) } @@ -618,9 +618,7 @@ impl BaseClient { return Ok(room); } - // FIXME: this might be racy. If we receive an update at the same time between - // us checking and submitting the save_changes, we might be overwriting - // some received state. See #1041 + let sync_lock = self.sync_lock().read().await; let mut room_info = room.clone_info(); room_info.mark_as_left(); room_info.mark_state_partially_synced(); @@ -629,10 +627,16 @@ impl BaseClient { changes.add_room(room_info.clone()); self.store.save_changes(&changes).await?; // Update the store room.update_summary(room_info); // Update the cached room handle + drop(sync_lock); Ok(room) } + /// Get access to the store's sync lock. + pub fn sync_lock(&self) -> &RwLock<()> { + self.store.sync_lock() + } + /// Receive a response from a sync call. /// /// # Arguments @@ -843,9 +847,11 @@ impl BaseClient { changes.ambiguity_maps = ambiguity_cache.cache; + let sync_lock = self.sync_lock().write().await; self.store.save_changes(&changes).await?; *self.store.sync_token.write().await = Some(next_batch.clone()); self.apply_changes(&changes).await; + drop(sync_lock); info!("Processed a sync response in {:?}", now.elapsed()); diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 3eee556ea..59cda12d3 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -505,6 +505,12 @@ pub(crate) struct Store { pub(super) sync_token: Arc>>, rooms: Arc>, stripped_rooms: Arc>, + /// A lock to synchronize access to the store, such that data by the sync is + /// never overwritten. The sync processing is supposed to use write access, + /// such that only it is currently accessing the store overall. Other things + /// might acquire read access, such that access to different rooms can be + /// parallelized. + sync_lock: Arc>, } impl Store { @@ -524,9 +530,15 @@ impl Store { sync_token: Default::default(), rooms: Default::default(), stripped_rooms: Default::default(), + sync_lock: Default::default(), } } + /// Get access to the syncing lock. + pub fn sync_lock(&self) -> &RwLock<()> { + &self.sync_lock + } + /// Restore the access to the Store from the given `Session`, overwrites any /// previously existing access to the Store. pub async fn restore_session(&self, session: Session) -> Result<()> { diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 1656df1fc..ef95b454d 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -354,8 +354,7 @@ impl Common { Err(err) => return Err(err.into()), }; - // FIXME: This can race with the sync setting the room_info and erase its - // state.. + let sync_lock = self.client.base_client().sync_lock().read().await; let mut room_info = self.inner.clone_info(); room_info.mark_encryption_state_synced(); room_info.set_encryption_event(response.clone()); @@ -363,6 +362,7 @@ impl Common { changes.add_room(room_info.clone()); self.client.store().save_changes(&changes).await?; self.update_summary(room_info); + drop(sync_lock); self.client.inner.encryption_state_request_locks.remove(self.inner.room_id()); From 956e270941ea6cae5f8bf1ab672797fe7173bb76 Mon Sep 17 00:00:00 2001 From: Flix Date: Mon, 24 Oct 2022 08:54:46 +0200 Subject: [PATCH 11/78] feat: Implement room sync_up method --- crates/matrix-sdk/src/room/common.rs | 17 +++++++++++++++++ .../src/tests/repeated_join.rs | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index ef95b454d..bb2a29970 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -124,6 +124,12 @@ impl Common { self.client.clone() } + /// Get the sync state of this room, i.e. whether it was fully synced with + /// the server. + pub fn is_synced(&self) -> bool { + self.inner.is_state_fully_synced() + } + /// Gets the avatar of this room, if set. /// /// Returns the avatar. @@ -414,6 +420,17 @@ impl Common { self.request_members().await } + /// Wait for the room to be fully synced. + /// + /// This method makes sure the room that was returned when joining/leaving + /// rooms has been echoed back in the sync. Warning: This waits until a sync + /// happens and does not return if no sync is happening! + pub async fn sync_up(&self) { + while !self.is_synced() { + self.client.inner.sync_beat.listen().await; + } + } + /// Get active members for this room, includes invited, joined members. /// /// *Note*: This method will fetch the members from the homeserver if the diff --git a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs index 56f77d40e..af30468a0 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs @@ -76,6 +76,11 @@ async fn test_repeated_join_leave() -> Result<()> { assert!(karl.get_joined_room(room_id).is_some()); assert!(karl.get_left_room(room_id).is_none()); + // Syncs can overwrite the internal state. If the sync lags behind because we + // change so often so fast, we can get errors in the asserts here. So we have to + // wait here a bit till the sync happened. + room.sync_up().await; + // Leave the room println!("Leaving.."); let room = room.leave().await?; From be7c3239a82ea8530fc76eff3a3f4091301124fe Mon Sep 17 00:00:00 2001 From: Flix Date: Wed, 2 Nov 2022 10:24:57 +0100 Subject: [PATCH 12/78] refactor: Adjust create_dm_room to new room API --- crates/matrix-sdk/src/client/mod.rs | 40 ------------------- .../src/encryption/identities/users.rs | 6 +-- crates/matrix-sdk/src/encryption/mod.rs | 21 ++-------- 3 files changed, 5 insertions(+), 62 deletions(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 41b9add70..3b10fca2d 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -1613,46 +1613,6 @@ impl Client { Ok(room::Joined::new(self, base_room).unwrap()) } - /// Create a direct message room with the specified user. - #[cfg(not(feature = "e2e-encryption"))] - pub async fn create_dm_room(&self, user_id: &UserId) -> Result { - use ruma::{ - api::client::room::create_room::v3::RoomPreset, events::direct::DirectEventContent, - }; - - // First we create the DM room, where we invite the user and tell the - // invitee that the room should be a DM. - let invite = &[user_id.to_owned()]; - - let request = assign!(ruma::api::client::room::create_room::v3::Request::new(), { - invite, - is_direct: true, - preset: Some(RoomPreset::TrustedPrivateChat), - }); - - let room = self.create_room(request).await?; - - // Now we need to mark the room as a DM for ourselves, we fetch the - // existing `m.direct` event and append the room to the list of DMs we - // have with this user. - let mut content = self - .account() - .account_data::() - .await? - .map(|c| c.deserialize()) - .transpose()? - .unwrap_or_default(); - - content.entry(user_id.to_owned()).or_default().push(room.room_id().to_owned()); - - // TODO We should probably save the fact that we need to send this out - // because otherwise we might end up in a state where we have a DM that - // isn't marked as one. - self.account().set_account_data(content).await?; - - Ok(room) - } - /// Search the homeserver's directory for public rooms with a filter. /// /// # Arguments diff --git a/crates/matrix-sdk/src/encryption/identities/users.rs b/crates/matrix-sdk/src/encryption/identities/users.rs index 9e8d27248..711a6416e 100644 --- a/crates/matrix-sdk/src/encryption/identities/users.rs +++ b/crates/matrix-sdk/src/encryption/identities/users.rs @@ -475,12 +475,8 @@ impl OtherUserIdentity { room.invite_user_by_id(self.inner.user_id()).await?; } room.clone() - } else if let Some(room) = - self.client.create_dm_room(self.inner.user_id().to_owned()).await? - { - room } else { - return Err(RequestVerificationError::RoomCreation(self.inner.user_id().to_owned())); + self.client.create_dm_room(self.inner.user_id().to_owned()).await? }; let response = room diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index 6f0881e53..a7cca5c2c 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -38,7 +38,6 @@ pub use matrix_sdk_base::crypto::{ use matrix_sdk_base::crypto::{ CrossSigningStatus, OutgoingRequest, RoomMessageRequest, ToDeviceRequest, }; -use matrix_sdk_common::instant::Duration; #[cfg(feature = "e2e-encryption")] use ruma::OwnedDeviceId; use ruma::{ @@ -227,16 +226,11 @@ impl Client { } #[cfg(feature = "e2e-encryption")] - pub(crate) async fn create_dm_room( - &self, - user_id: OwnedUserId, - ) -> Result> { + pub(crate) async fn create_dm_room(&self, user_id: OwnedUserId) -> Result { use ruma::{ api::client::room::create_room::v3::RoomPreset, events::direct::DirectEventContent, }; - const SYNC_WAIT_TIME: Duration = Duration::from_secs(3); - // First we create the DM room, where we invite the user and tell the // invitee that the room should be a DM. let invite = &[user_id.clone()]; @@ -247,7 +241,7 @@ impl Client { preset: Some(RoomPreset::TrustedPrivateChat), }); - let response = self.send(request, None).await?; + let room = self.create_room(request).await?; // Now we need to mark the room as a DM for ourselves, we fetch the // existing `m.direct` event and append the room to the list of DMs we @@ -260,21 +254,14 @@ impl Client { .transpose()? .unwrap_or_default(); - content.entry(user_id.to_owned()).or_default().push(response.room_id.to_owned()); + content.entry(user_id.to_owned()).or_default().push(room.room_id().to_owned()); // TODO We should probably save the fact that we need to send this out // because otherwise we might end up in a state where we have a DM that // isn't marked as one. self.account().set_account_data(content).await?; - // If the room is already in our store, fetch it, otherwise wait for a - // sync to be done which should put the room into our store. - if let Some(room) = self.get_joined_room(&response.room_id) { - Ok(Some(room)) - } else { - self.inner.sync_beat.listen().wait_timeout(SYNC_WAIT_TIME); - Ok(self.get_joined_room(&response.room_id)) - } + Ok(room) } /// Claim one-time keys creating new Olm sessions. From fb8045b25419c477919e20e368d792ad6135ae2a Mon Sep 17 00:00:00 2001 From: Flix Date: Wed, 2 Nov 2022 10:30:22 +0100 Subject: [PATCH 13/78] fix: Fix UDL or is_encrypted --- bindings/matrix-sdk-ffi/src/api.udl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bindings/matrix-sdk-ffi/src/api.udl b/bindings/matrix-sdk-ffi/src/api.udl index 7878a6010..faf93c7c5 100644 --- a/bindings/matrix-sdk-ffi/src/api.udl +++ b/bindings/matrix-sdk-ffi/src/api.udl @@ -203,6 +203,9 @@ dictionary Session { interface Room { [Throws=ClientError] string display_name(); + + [Throws=ClientError] + boolean is_encrypted(); [Throws=ClientError] string? member_avatar_url(string user_id); From 6951f7f5bf384f5c6653afc58c36bd894451af0d Mon Sep 17 00:00:00 2001 From: Flix Date: Wed, 26 Oct 2022 13:34:45 +0200 Subject: [PATCH 14/78] fix: Review comments --- crates/matrix-sdk-base/src/rooms/normal.rs | 31 +++++++++---------- crates/matrix-sdk/src/error.rs | 3 +- crates/matrix-sdk/src/room/common.rs | 4 +-- .../src/helpers.rs | 10 +++++- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index 47450f9c0..e681eba39 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -129,7 +129,7 @@ impl Room { self.inner.read().unwrap().notification_counts } - /// Check if the room has it's members fully synced. + /// Check if the room has its members fully synced. /// /// Members might be missing if lazy member loading was enabled for the /// sync. @@ -150,7 +150,7 @@ impl Room { self.inner.read().unwrap().sync_info == SyncInfo::FullySynced } - /// Check if the room has it's encryption event synced. + /// Check if the room has its encryption event synced. /// /// The encryption event can be missing when the room hasn't appeared in /// sync yet. @@ -544,7 +544,7 @@ pub(crate) enum SyncInfo { /// stale, as it is from a room we've left. PartiallySynced, - /// We have all the latest state events + /// We have all the latest state events. FullySynced, } @@ -582,52 +582,52 @@ impl RoomInfo { } } - /// Mark this Room as joined + /// Mark this Room as joined. pub fn mark_as_joined(&mut self) { self.room_type = RoomType::Joined; } - /// Mark this Room as left + /// Mark this Room as left. pub fn mark_as_left(&mut self) { self.room_type = RoomType::Left; } - /// Mark this Room as invited + /// Mark this Room as invited. pub fn mark_as_invited(&mut self) { self.room_type = RoomType::Invited; } - /// Mark this Room as having all the members synced + /// Mark this Room as having all the members synced. pub fn mark_members_synced(&mut self) { self.members_synced = true; } - /// Mark this Room still missing member information + /// Mark this Room still missing member information. pub fn mark_members_missing(&mut self) { self.members_synced = false; } - /// Mark this Room still missing some state information + /// Mark this Room still missing some state information. pub fn mark_state_partially_synced(&mut self) { self.sync_info = SyncInfo::PartiallySynced; } - /// Mark this Room still having all state synced + /// Mark this Room still having all state synced. pub fn mark_state_fully_synced(&mut self) { self.sync_info = SyncInfo::FullySynced; } - /// Mark this Room still having no state synced + /// Mark this Room still having no state synced. pub fn mark_state_not_synced(&mut self) { self.sync_info = SyncInfo::NoState; } - /// Mark this Room as having the encryption state synced + /// Mark this Room as having the encryption state synced. pub fn mark_encryption_state_synced(&mut self) { self.encryption_state_synced = true; } - /// Mark this Room still missing encryption state information + /// Mark this Room still missing encryption state information. pub fn mark_encryption_state_missing(&mut self) { self.encryption_state_synced = false; } @@ -644,15 +644,14 @@ impl RoomInfo { } } - /// Whether this is an encrypted Room + /// Returns whether this is an encrypted Room. pub fn is_encrypted(&self) -> bool { self.base_info.encryption.is_some() } /// Set the encryption event content in this room. - pub fn set_encryption_event(&mut self, event: Option) -> &Self { + pub fn set_encryption_event(&mut self, event: Option) { self.base_info.encryption = event; - self } /// Handle the given state event. diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index e65e1aedd..387428d25 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -261,7 +261,8 @@ pub enum Error { #[error(transparent)] SlidingSync(#[from] crate::sliding_sync::Error), - /// The client is in inconsistent state. + /// The client is in inconsistent state. This happens when we set a room to + /// a specific type, but then cannot get it in this type. #[error("The internal client state is inconsistent.")] InconsistentState, diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index bb2a29970..a648d8059 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -1,4 +1,4 @@ -use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc}; +use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc, time::Duration}; use matrix_sdk_base::{ deserialized_responses::{MembersResponse, TimelineEvent}, @@ -427,7 +427,7 @@ impl Common { /// happens and does not return if no sync is happening! pub async fn sync_up(&self) { while !self.is_synced() { - self.client.inner.sync_beat.listen().await; + self.client.inner.sync_beat.listen().wait_timeout(Duration::from_secs(1)); } } diff --git a/testing/matrix-sdk-integration-testing/src/helpers.rs b/testing/matrix-sdk-integration-testing/src/helpers.rs index e68abda75..72594a0b1 100644 --- a/testing/matrix-sdk-integration-testing/src/helpers.rs +++ b/testing/matrix-sdk-integration-testing/src/helpers.rs @@ -3,6 +3,7 @@ use std::{collections::HashMap, option_env}; use anyhow::Result; use assign::assign; use matrix_sdk::{ + config::RequestConfig, ruma::api::client::{account::register::v3::Request as RegistrationRequest, uiaa}, Client, }; @@ -14,9 +15,15 @@ static USERS: Lazy>> = Lazy::new(Mutex: #[ctor::ctor] fn init_logging() { + use tracing::Level; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; tracing_subscriber::registry() - .with(tracing_subscriber::EnvFilter::from_default_env()) + .with( + tracing_subscriber::EnvFilter::builder() + .with_default_directive(Level::TRACE.into()) + .from_env() + .unwrap(), + ) .with(tracing_subscriber::fmt::layer().with_test_writer()) .init(); } @@ -43,6 +50,7 @@ pub async fn get_client_for_user(username: String) -> Result { .user_agent("matrix-sdk-integation-tests") .sled_store(tmp_dir.path(), None) .homeserver_url(homeserver_url) + .request_config(RequestConfig::short_retry()) .build() .await?; // safe to assume we have not registered this user yet, but ignore if we did From a3a9858bf4960c446ba39632a44546b255d2b195 Mon Sep 17 00:00:00 2001 From: Flix Date: Wed, 2 Nov 2022 14:24:42 +0100 Subject: [PATCH 15/78] refactor: Make if branches instead of early return --- crates/matrix-sdk-base/src/client.rs | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 527412456..b8b824dcf 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -592,21 +592,21 @@ impl BaseClient { pub async fn room_joined(&self, room_id: &RoomId) -> Result { let room = self.store.get_or_create_room(room_id, RoomType::Joined).await; if room.room_type() == RoomType::Joined { - return Ok(room); + Ok(room) + } else { + let _sync_lock = self.sync_lock().read().await; + + let mut room_info = room.clone_info(); + room_info.mark_as_joined(); + room_info.mark_state_partially_synced(); + room_info.mark_members_missing(); // the own member event changed + let mut changes = StateChanges::default(); + changes.add_room(room_info.clone()); + self.store.save_changes(&changes).await?; // Update the store + room.update_summary(room_info); // Update the cached room handle + + Ok(room) } - - let sync_lock = self.sync_lock().read().await; - let mut room_info = room.clone_info(); - room_info.mark_as_joined(); - room_info.mark_state_partially_synced(); - room_info.mark_members_missing(); // the own member event changed - let mut changes = StateChanges::default(); - changes.add_room(room_info.clone()); - self.store.save_changes(&changes).await?; // Update the store - room.update_summary(room_info); // Update the cached room handle - drop(sync_lock); - - Ok(room) } /// User has left a room. @@ -615,21 +615,21 @@ impl BaseClient { pub async fn room_left(&self, room_id: &RoomId) -> Result { let room = self.store.get_or_create_room(room_id, RoomType::Left).await; if room.room_type() == RoomType::Left { - return Ok(room); + Ok(room) + } else { + let _sync_lock = self.sync_lock().read().await; + + let mut room_info = room.clone_info(); + room_info.mark_as_left(); + room_info.mark_state_partially_synced(); + room_info.mark_members_missing(); // the own member event changed + let mut changes = StateChanges::default(); + changes.add_room(room_info.clone()); + self.store.save_changes(&changes).await?; // Update the store + room.update_summary(room_info); // Update the cached room handle + + Ok(room) } - - let sync_lock = self.sync_lock().read().await; - let mut room_info = room.clone_info(); - room_info.mark_as_left(); - room_info.mark_state_partially_synced(); - room_info.mark_members_missing(); // the own member event changed - let mut changes = StateChanges::default(); - changes.add_room(room_info.clone()); - self.store.save_changes(&changes).await?; // Update the store - room.update_summary(room_info); // Update the cached room handle - drop(sync_lock); - - Ok(room) } /// Get access to the store's sync lock. From 1ab33a28c90c727d783c2d9db6ea98c8135f20fd Mon Sep 17 00:00:00 2001 From: Flix Date: Wed, 2 Nov 2022 15:42:06 +0100 Subject: [PATCH 16/78] fix: Make sure room.sync_up works under all conditions --- crates/matrix-sdk/src/room/common.rs | 13 +------------ crates/matrix-sdk/src/room/joined.rs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index a648d8059..2dd9aa9c0 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -1,4 +1,4 @@ -use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc, time::Duration}; +use std::{borrow::Borrow, collections::BTreeMap, ops::Deref, sync::Arc}; use matrix_sdk_base::{ deserialized_responses::{MembersResponse, TimelineEvent}, @@ -420,17 +420,6 @@ impl Common { self.request_members().await } - /// Wait for the room to be fully synced. - /// - /// This method makes sure the room that was returned when joining/leaving - /// rooms has been echoed back in the sync. Warning: This waits until a sync - /// happens and does not return if no sync is happening! - pub async fn sync_up(&self) { - while !self.is_synced() { - self.client.inner.sync_beat.listen().wait_timeout(Duration::from_secs(1)); - } - } - /// Get active members for this room, includes invited, joined members. /// /// *Note*: This method will fetch the members from the homeserver if the diff --git a/crates/matrix-sdk/src/room/joined.rs b/crates/matrix-sdk/src/room/joined.rs index e61a98256..13f5c31bf 100644 --- a/crates/matrix-sdk/src/room/joined.rs +++ b/crates/matrix-sdk/src/room/joined.rs @@ -383,6 +383,19 @@ impl Joined { Ok(()) } + /// Wait for the room to be fully synced. + /// + /// This method makes sure the room that was returned when joining a room + /// has been echoed back in the sync. + /// Warning: This waits until a sync happens and does not return if no sync + /// is happening! It can also return early when the room is not a joined + /// room anymore! + pub async fn sync_up(&self) { + while !self.is_synced() && self.room_type() == RoomType::Joined { + self.client.inner.sync_beat.listen().wait_timeout(Duration::from_secs(1)); + } + } + /// Send a room message to this room. /// /// Returns the parsed response from the server. From b1c2da9a684f76a22600a9dace61f8de3f19d288 Mon Sep 17 00:00:00 2001 From: Flix Date: Wed, 2 Nov 2022 14:52:22 +0100 Subject: [PATCH 17/78] fix: Don't mark encryption state to be synced on sync As it appears, the first sync for a room might not include the encryption state, so we cannot set the encryption state to be synced on incoming syncs. That way, we always fetch the encryption state manually. --- crates/matrix-sdk-base/src/client.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index b8b824dcf..fb49975eb 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -696,7 +696,6 @@ impl BaseClient { room_info.update_summary(&new_info.summary); room_info.set_prev_batch(new_info.timeline.prev_batch.as_deref()); room_info.mark_state_fully_synced(); - room_info.mark_encryption_state_synced(); let mut user_ids = self .handle_state( @@ -819,7 +818,6 @@ impl BaseClient { let mut room_info = r.clone_info(); room_info.mark_as_invited(); room_info.mark_state_fully_synced(); - room_info.mark_encryption_state_synced(); changes.add_room(room_info); } From ab846f79f13fa785853f1072bdf0b0a7c6473e7c Mon Sep 17 00:00:00 2001 From: Flix Date: Thu, 3 Nov 2022 11:02:33 +0100 Subject: [PATCH 18/78] test: Add encryption state mock to tests where it is needed --- crates/matrix-sdk/src/encryption/mod.rs | 12 ++++++++- crates/matrix-sdk/tests/integration/main.rs | 27 ++++++++++++++++++- .../tests/integration/room/joined.rs | 7 ++++- .../tests/integration/room/timeline.rs | 4 ++- .../src/test_json/sync_events.rs | 14 ++++++---- 5 files changed, 55 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index a7cca5c2c..552e37527 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -860,7 +860,7 @@ mod tests { }; use serde_json::json; use wiremock::{ - matchers::{method, path_regex}, + matchers::{header, method, path_regex}, Mock, MockServer, ResponseTemplate, }; @@ -874,6 +874,16 @@ mod tests { let event_id = event_id!("$2:example.org"); let room_id = &test_json::DEFAULT_SYNC_ROOM_ID; + Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?")) + .and(header("authorization", "Bearer 1234")) + .respond_with( + ResponseTemplate::new(200) + .set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT), + ) + .mount(&server) + .await; + Mock::given(method("PUT")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m%2Ereaction/.*".to_owned())) .respond_with(ResponseTemplate::new(200).set_body_json(json!({ diff --git a/crates/matrix-sdk/tests/integration/main.rs b/crates/matrix-sdk/tests/integration/main.rs index 592bdfffe..4be34961a 100644 --- a/crates/matrix-sdk/tests/integration/main.rs +++ b/crates/matrix-sdk/tests/integration/main.rs @@ -2,10 +2,11 @@ #![cfg(not(target_arch = "wasm32"))] use matrix_sdk::{config::RequestConfig, Client, ClientBuilder, Session}; +use matrix_sdk_test::test_json; use ruma::{api::MatrixVersion, device_id, user_id}; use serde::Serialize; use wiremock::{ - matchers::{header, method, path, query_param, query_param_is_missing}, + matchers::{header, method, path, path_regex, query_param, query_param_is_missing}, Mock, MockServer, ResponseTemplate, }; @@ -69,3 +70,27 @@ async fn mock_sync(server: &MockServer, response_body: impl Serialize, since: Op .mount(server) .await; } + +/// Mount a Mock on the given server to handle the `GET +/// /rooms/.../state/m.room.encryption` endpoint with an option whether it +/// should return an encryption event or not. +async fn mock_encryption_state(server: &MockServer, is_encrypted: bool) { + let builder = Mock::given(method("GET")) + .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?")) + .and(header("authorization", "Bearer 1234")); + + if is_encrypted { + builder + .respond_with( + ResponseTemplate::new(200) + .set_body_json(&*test_json::sync_events::ENCRYPTION_CONTENT), + ) + .mount(server) + .await; + } else { + builder + .respond_with(ResponseTemplate::new(404).set_body_json(&*test_json::NOT_FOUND)) + .mount(server) + .await; + } +} diff --git a/crates/matrix-sdk/tests/integration/room/joined.rs b/crates/matrix-sdk/tests/integration/room/joined.rs index 1db6cc95a..e7b85a802 100644 --- a/crates/matrix-sdk/tests/integration/room/joined.rs +++ b/crates/matrix-sdk/tests/integration/room/joined.rs @@ -19,7 +19,7 @@ use wiremock::{ Mock, ResponseTemplate, }; -use crate::{logged_in_client, mock_sync}; +use crate::{logged_in_client, mock_encryption_state, mock_sync}; #[async_test] async fn invite_user_by_id() { @@ -256,6 +256,7 @@ async fn room_message_send() { .await; mock_sync(&server, &*test_json::SYNC, None).await; + mock_encryption_state(&server, false).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -297,6 +298,7 @@ async fn room_attachment_send() { .await; mock_sync(&server, &*test_json::SYNC, None).await; + mock_encryption_state(&server, false).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -346,6 +348,7 @@ async fn room_attachment_send_info() { .await; mock_sync(&server, &*test_json::SYNC, None).await; + mock_encryption_state(&server, false).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -397,6 +400,7 @@ async fn room_attachment_send_wrong_info() { .await; mock_sync(&server, &*test_json::SYNC, None).await; + mock_encryption_state(&server, false).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); @@ -455,6 +459,7 @@ async fn room_attachment_send_info_thumbnail() { .await; mock_sync(&server, &*test_json::SYNC, None).await; + mock_encryption_state(&server, false).await; let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); diff --git a/crates/matrix-sdk/tests/integration/room/timeline.rs b/crates/matrix-sdk/tests/integration/room/timeline.rs index 93a4b9096..4deb0e9df 100644 --- a/crates/matrix-sdk/tests/integration/room/timeline.rs +++ b/crates/matrix-sdk/tests/integration/room/timeline.rs @@ -26,7 +26,7 @@ use wiremock::{ Mock, ResponseTemplate, }; -use crate::{logged_in_client, mock_sync}; +use crate::{logged_in_client, mock_encryption_state, mock_sync}; #[async_test] async fn edit() { @@ -157,6 +157,8 @@ async fn echo() { let event_id = event_id!("$wWgymRfo7ri1uQx0NXO40vLJ"); let txn_id: &TransactionId = "my-txn-id".into(); + mock_encryption_state(&server, false).await; + Mock::given(method("PUT")) .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/.*")) .and(header("authorization", "Bearer 1234")) diff --git a/testing/matrix-sdk-test/src/test_json/sync_events.rs b/testing/matrix-sdk-test/src/test_json/sync_events.rs index 109451ac5..407d4e2fd 100644 --- a/testing/matrix-sdk-test/src/test_json/sync_events.rs +++ b/testing/matrix-sdk-test/src/test_json/sync_events.rs @@ -97,13 +97,17 @@ pub static JOIN_RULES: Lazy = Lazy::new(|| { }) }); +pub static ENCRYPTION_CONTENT: Lazy = Lazy::new(|| { + json!({ + "algorithm": "m.megolm.v1.aes-sha2", + "rotation_period_ms": 604800000, + "rotation_period_msgs": 100 + }) +}); + pub static ENCRYPTION: Lazy = Lazy::new(|| { json!({ - "content": { - "algorithm": "m.megolm.v1.aes-sha2", - "rotation_period_ms": 604800000, - "rotation_period_msgs": 100 - }, + "content": *ENCRYPTION_CONTENT, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts": 1432735824653u64, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", From 456e00e95399dc7271ddfbb74f0a3431538430f1 Mon Sep 17 00:00:00 2001 From: Flix Date: Thu, 3 Nov 2022 10:07:30 +0100 Subject: [PATCH 19/78] fix: Add missing not_found errors --- crates/matrix-sdk/src/error.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index 387428d25..798406773 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -76,6 +76,18 @@ impl RumaApiError { _ => None, } } + + /// Return whether the error was a 404 not found response. + pub fn is_not_found(&self) -> bool { + match self { + RumaApiError::ClientApi(err) => err.status_code == StatusCode::NOT_FOUND, + RumaApiError::Uiaa(UiaaResponse::MatrixError(err)) => { + err.status_code == StatusCode::NOT_FOUND + } + RumaApiError::Uiaa(_) => false, + RumaApiError::Other(err) => err.status_code == StatusCode::NOT_FOUND, + } + } } /// An HTTP error, representing either a connection error or an error while @@ -169,6 +181,9 @@ impl HttpError { HttpError::Reqwest(err) => err.status() == Some(StatusCode::NOT_FOUND), HttpError::AuthenticationRequired => false, HttpError::NotClientRequest => false, + HttpError::Api(FromHttpResponseError::Server(ServerError::Known(err))) => { + err.is_not_found() + } HttpError::Api(_) => false, HttpError::IntoHttp(_) => false, HttpError::Server(status) => *status == StatusCode::NOT_FOUND, From 0a45e401e3a2d2409cb45283ab22b10015d638d6 Mon Sep 17 00:00:00 2001 From: Flix Date: Wed, 2 Nov 2022 11:18:39 +0100 Subject: [PATCH 20/78] test: Use MemoryStore to fix problems in coverage tests --- .../matrix-sdk-integration-testing/src/helpers.rs | 15 +++++++++------ .../src/tests/invitations.rs | 4 ++-- .../src/tests/redaction.rs | 4 ++-- .../src/tests/repeated_join.rs | 4 ++-- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/testing/matrix-sdk-integration-testing/src/helpers.rs b/testing/matrix-sdk-integration-testing/src/helpers.rs index 72594a0b1..50d037eeb 100644 --- a/testing/matrix-sdk-integration-testing/src/helpers.rs +++ b/testing/matrix-sdk-integration-testing/src/helpers.rs @@ -36,7 +36,7 @@ pub fn test_server_conf() -> (String, String) { ) } -pub async fn get_client_for_user(username: String) -> Result { +pub async fn get_client_for_user(username: String, use_sled_store: bool) -> Result { let mut users = USERS.lock().await; if let Some((client, _)) = users.get(&username) { return Ok(client.clone()); @@ -46,13 +46,16 @@ pub async fn get_client_for_user(username: String) -> Result { let tmp_dir = tempdir()?; - let client = Client::builder() + let client_builder = Client::builder() .user_agent("matrix-sdk-integation-tests") - .sled_store(tmp_dir.path(), None) .homeserver_url(homeserver_url) - .request_config(RequestConfig::short_retry()) - .build() - .await?; + .request_config(RequestConfig::short_retry()); + let client = if use_sled_store { + client_builder.sled_store(tmp_dir.path(), None).build().await? + } else { + client_builder.build().await? + }; + // safe to assume we have not registered this user yet, but ignore if we did if let Err(resp) = client.register(RegistrationRequest::new()).await { diff --git a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs index f7c14cc65..decab71fd 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/invitations.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/invitations.rs @@ -8,8 +8,8 @@ use crate::helpers::get_client_for_user; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_invitation_details() -> Result<()> { - let tamatoa = get_client_for_user("tamatoa".to_owned()).await?; - let sebastian = get_client_for_user("sebastian".to_owned()).await?; + let tamatoa = get_client_for_user("tamatoa".to_owned(), true).await?; + let sebastian = get_client_for_user("sebastian".to_owned(), true).await?; let invites = [sebastian.user_id().expect("sebastian has a userid!").to_owned()]; // create a room and invite sebastian; diff --git a/testing/matrix-sdk-integration-testing/src/tests/redaction.rs b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs index 06ab9922e..f8877266f 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/redaction.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs @@ -26,7 +26,7 @@ async fn sync_once(client: &Client) -> Result<()> { #[ignore = "Broken since synapse update, see #1069"] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_redacting_name() -> Result<()> { - let tamatoa = get_client_for_user("tamatoa".to_owned()).await?; + let tamatoa = get_client_for_user("tamatoa".to_owned(), true).await?; // create a room let request = assign!(CreateRoomRequest::new(), { is_direct: true, @@ -96,7 +96,7 @@ async fn test_redacting_name() -> Result<()> { #[ignore = "Broken since synapse update, see #1069"] #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_redacting_name_static() -> Result<()> { - let tamatoa = get_client_for_user("tamatoa".to_owned()).await?; + let tamatoa = get_client_for_user("tamatoa".to_owned(), true).await?; // create a room let request = assign!(CreateRoomRequest::new(), { is_direct: true, diff --git a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs index af30468a0..966fd17c9 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/repeated_join.rs @@ -17,9 +17,9 @@ use crate::helpers::get_client_for_user; #[tokio::test(flavor = "multi_thread", worker_threads = 4)] async fn test_repeated_join_leave() -> Result<()> { - let peter = get_client_for_user("peter".to_owned()).await?; + let peter = get_client_for_user("peter".to_owned(), true).await?; // FIXME: Run once with memory, once with sled - let karl = get_client_for_user("karl".to_owned()).await?; + let karl = get_client_for_user("karl".to_owned(), false).await?; let karl_id = karl.user_id().expect("karl has a userid!").to_owned(); // Create a room and invite karl. From deb8ef801bff4f51b886d970544c3ce5a2f9e720 Mon Sep 17 00:00:00 2001 From: Flix Date: Mon, 14 Nov 2022 16:11:30 +0100 Subject: [PATCH 21/78] fix: Remove the condition to only set new members in `get_members` --- crates/matrix-sdk-base/src/client.rs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index fb49975eb..c06dc335d 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -925,18 +925,25 @@ impl BaseClient { for member in &members { let member: SyncRoomMemberEvent = member.clone().into(); - if self.store.get_member_event(room_id, member.state_key()).await?.is_none() { - #[cfg(feature = "e2e-encryption")] - match member.membership() { - MembershipState::Join | MembershipState::Invite => { - user_ids.insert(member.state_key().to_owned()); - } - _ => (), - } + // TODO: All the actions in this loop used to be done only when the membership + // event was not in the store before. This was changed with the new room API, + // because e.g. leaving a room makes members events outdated and they need to be + // fetched by `get_members`. Therefore, they need to be overwritten here, even + // if they exist. + // However, this makes a new problem occur where setting the member events here + // potentially races with the sync. + // See . - ambiguity_cache.handle_event(&changes, room_id, &member).await?; + #[cfg(feature = "e2e-encryption")] + match member.membership() { + MembershipState::Join | MembershipState::Invite => { + user_ids.insert(member.state_key().to_owned()); + } + _ => (), } + ambiguity_cache.handle_event(&changes, room_id, &member).await?; + if member.state_key() == member.sender() { changes .profiles From 8d8903729614c82ba78272f5ef330ca4e26a2b44 Mon Sep 17 00:00:00 2001 From: Flix Date: Mon, 14 Nov 2022 16:45:44 +0100 Subject: [PATCH 22/78] fix: Missing ServerError after ruma update --- crates/matrix-sdk/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index 798406773..3355e6372 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -181,7 +181,7 @@ impl HttpError { HttpError::Reqwest(err) => err.status() == Some(StatusCode::NOT_FOUND), HttpError::AuthenticationRequired => false, HttpError::NotClientRequest => false, - HttpError::Api(FromHttpResponseError::Server(ServerError::Known(err))) => { + HttpError::Api(FromHttpResponseError::Server(err)) => { err.is_not_found() } HttpError::Api(_) => false, From 882b206144803568b50fa6f6c8d3613afdd69ec8 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Tue, 15 Nov 2022 13:06:33 +0100 Subject: [PATCH 23/78] feat(xtask): build xcframework * Move swift build scripts into xtask (#1201) * fix(ffi): use target_path from `cargo metadata` rather than guessing * ci(ffi): install necessary target arch for build-framework test * feat(xtask): copy to target without rsync. --- .github/workflows/bindings_ci.yml | 12 +- Cargo.lock | 1 + bindings/apple/build_xcframework.sh | 87 +--------- bindings/apple/debug_build_xcframework.sh | 63 +------ bindings/matrix-sdk-ffi/src/platform.rs | 3 + xtask/Cargo.toml | 1 + xtask/src/swift.rs | 193 ++++++++++++++++++---- xtask/src/workspace.rs | 11 ++ 8 files changed, 187 insertions(+), 184 deletions(-) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 9f048a818..23bed08e8 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -243,6 +243,9 @@ jobs: profile: minimal override: true + - name: Install aarch64-apple-ios target + run: rustup target install aarch64-apple-ios + - name: Load cache uses: Swatinem/rust-cache@v1 @@ -252,15 +255,12 @@ jobs: path: target/debug/xtask key: xtask-macos-${{ hashFiles('Cargo.toml', 'xtask/**') }} - - name: Install Uniffi - uses: actions-rs/cargo@v1 - with: - command: install - args: uniffi_bindgen --git https://github.com/mozilla/uniffi-rs --rev ${{ env.UNIFFI_REV }} - - name: Build library & bindings run: target/debug/xtask swift build-library - name: Run XCTests working-directory: bindings/apple run: swift test + + - name: Build Framework + run: cargo xtask swift build-framework --only-target=aarch64-apple-ios \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 7d1691c52..1a83c107d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5596,6 +5596,7 @@ version = "0.1.0" dependencies = [ "camino", "clap 4.0.18", + "fs_extra", "serde", "serde_json", "uniffi_bindgen", diff --git a/bindings/apple/build_xcframework.sh b/bindings/apple/build_xcframework.sh index 222d0e48d..46d7474a9 100755 --- a/bindings/apple/build_xcframework.sh +++ b/bindings/apple/build_xcframework.sh @@ -2,89 +2,6 @@ set -eEu cd "$(dirname "$0")" +cd ../.. -# Path to the repo root -SRC_ROOT=../.. - -TARGET_DIR="${SRC_ROOT}/target" - -GENERATED_DIR="${SRC_ROOT}/generated" -mkdir -p ${GENERATED_DIR} - -REL_FLAG="--release" -REL_TYPE_DIR="release" - -# Build static libs for all the different architectures - -# iOS -echo -e "Building for iOS [1/5]" -cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios" - -# MacOS -echo -e "\nBuilding for macOS (Apple Silicon) [2/5]" -cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-darwin" -echo -e "\nBuilding for macOS (Intel) [3/5]" -cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-darwin" - -# iOS Simulator -echo -e "\nBuilding for iOS Simulator (Apple Silicon) [4/5]" -cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "aarch64-apple-ios-sim" -echo -e "\nBuilding for iOS Simulator (Intel) [5/5]" -cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "x86_64-apple-ios" - -echo -e "\nCreating XCFramework" -# Lipo together the libraries for the same platform - -# MacOS -lipo -create \ - "${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - "${TARGET_DIR}/aarch64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - -output "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" - -# iOS Simulator -lipo -create \ - "${TARGET_DIR}/x86_64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - "${TARGET_DIR}/aarch64-apple-ios-sim/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - -output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" - - -# Generate uniffi files -# Architecture for the .a file argument doesn't matter, since the API is the same on all -uniffi-bindgen generate \ - --language swift \ - --lib-file "${TARGET_DIR}/x86_64-apple-darwin/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - --out-dir ${GENERATED_DIR} \ - "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" - -# Move them to the right place -HEADERS_DIR=${GENERATED_DIR}/headers -mkdir -p ${HEADERS_DIR} - -mv ${GENERATED_DIR}/*.h ${HEADERS_DIR} - -# Rename and move modulemap to the right place -mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap - -SWIFT_DIR="${GENERATED_DIR}/swift" -mkdir -p ${SWIFT_DIR} - -mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR} - -# Build the xcframework - -if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi - -xcodebuild -create-xcframework \ - -library "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" \ - -headers ${HEADERS_DIR} \ - -library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \ - -headers ${HEADERS_DIR} \ - -library "${TARGET_DIR}/aarch64-apple-ios/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - -headers ${HEADERS_DIR} \ - -output "${GENERATED_DIR}/MatrixSDKFFI.xcframework" - -# Cleanup - -if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_macos.a"; fi -if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi -if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi +cargo xtask swift build-framework --release $* diff --git a/bindings/apple/debug_build_xcframework.sh b/bindings/apple/debug_build_xcframework.sh index 920a2d474..1c8b89c03 100755 --- a/bindings/apple/debug_build_xcframework.sh +++ b/bindings/apple/debug_build_xcframework.sh @@ -19,19 +19,6 @@ else echo "Running debug build" fi -echo "Active architecture ${ACTIVE_ARCH}" - -# Path to the repo root -SRC_ROOT=../.. - -TARGET_DIR="${SRC_ROOT}/target" - -GENERATED_DIR="${SRC_ROOT}/bindings/apple/generated" -mkdir -p ${GENERATED_DIR} - -REL_FLAG="" -REL_TYPE_DIR="debug" - # iOS Simulator arm64 if [ "$ACTIVE_ARCH" = "arm64" ]; then TARGET="aarch64-apple-ios-sim" @@ -39,52 +26,6 @@ if [ "$ACTIVE_ARCH" = "arm64" ]; then else TARGET="x86_64-apple-ios" fi +echo "Active architecture ${ACTIVE_ARCH}" -cargo build -p matrix-sdk-ffi ${REL_FLAG} --target "$TARGET" - -lipo -create \ - "${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - -output "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" - -# Generate uniffi files -uniffi-bindgen generate \ - --language swift \ - --lib-file "${TARGET_DIR}/$TARGET/${REL_TYPE_DIR}/libmatrix_sdk_ffi.a" \ - --out-dir ${GENERATED_DIR} \ - "${SRC_ROOT}/bindings/matrix-sdk-ffi/src/api.udl" - -# Move them to the right place -HEADERS_DIR=${GENERATED_DIR}/headers -mkdir -p ${HEADERS_DIR} - -mv ${GENERATED_DIR}/*.h ${HEADERS_DIR} - -# Rename and move modulemap to the right place -mv ${GENERATED_DIR}/*.modulemap ${HEADERS_DIR}/module.modulemap - -SWIFT_DIR="${GENERATED_DIR}/swift" -mkdir -p ${SWIFT_DIR} - -mv ${GENERATED_DIR}/*.swift ${SWIFT_DIR} - -# Build the xcframework - -if [ -d "${GENERATED_DIR}/MatrixSDKFFI.xcframework" ]; then rm -rf "${GENERATED_DIR}/MatrixSDKFFI.xcframework"; fi - -xcodebuild -create-xcframework \ - -library "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" \ - -headers ${HEADERS_DIR} \ - -output "${GENERATED_DIR}/MatrixSDKFFI.xcframework" - -# Cleanup - -if [ -f "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a" ]; then rm -rf "${GENERATED_DIR}/libmatrix_sdk_ffi_iossimulator.a"; fi -if [ -d ${HEADERS_DIR} ]; then rm -rf ${HEADERS_DIR}; fi - -if [ "$IS_CI" = false ] ; then - echo "Preparing matrix-rust-components-swift" - - # Debug -> Copy generated files over to ../../../matrix-rust-components-swift - rsync -a --delete "${GENERATED_DIR}/MatrixSDKFFI.xcframework" "${SRC_ROOT}/../matrix-rust-components-swift/" - rsync -a --delete "${GENERATED_DIR}/swift/" "${SRC_ROOT}/../matrix-rust-components-swift/Sources/MatrixRustSDK" -fi +cargo xtask swift build-framework --sim-only-target=${TARGET} $* diff --git a/bindings/matrix-sdk-ffi/src/platform.rs b/bindings/matrix-sdk-ffi/src/platform.rs index 4b7d63efe..b1ed9702b 100644 --- a/bindings/matrix-sdk-ffi/src/platform.rs +++ b/bindings/matrix-sdk-ffi/src/platform.rs @@ -26,6 +26,9 @@ mod android { #[cfg(target_os = "ios")] mod ios { + use std::io; + + use tracing_subscriber::{fmt, prelude::*, EnvFilter}; pub fn setup_tracing(configuration: String) { tracing_subscriber::registry() .with(EnvFilter::new(configuration)) diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 1a2065f8c..04d48d279 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -13,5 +13,6 @@ camino = "1.0.8" clap = { version = "4.0.18", features = ["derive"] } serde = { version = "1.0.136", features = ["derive"] } serde_json = "1.0.79" +fs_extra = "1" uniffi_bindgen = { workspace = true } xshell = "0.1.17" diff --git a/xtask/src/swift.rs b/xtask/src/swift.rs index 76b5799a1..2b95c2dea 100644 --- a/xtask/src/swift.rs +++ b/xtask/src/swift.rs @@ -1,4 +1,7 @@ -use std::fs; +use std::{ + fs::{create_dir_all, remove_dir_all, remove_file, rename}, + path::PathBuf, +}; use clap::{Args, Subcommand}; use xshell::{cmd, pushd}; @@ -17,7 +20,18 @@ enum SwiftCommand { /// Builds the SDK for Swift as a static lib. BuildLibrary, /// Builds the SDK for Swift as an XCFramework. - BuildFramework, + BuildFramework { + /// Build in release mode + #[clap(long)] + release: bool, + /// Build the given target only + #[clap(long)] + only_target: Option, + /// Move the generated xcframework and swift sources into the given + /// components-folder + #[clap(long)] + components_path: Option, + }, } impl SwiftArgs { @@ -26,7 +40,9 @@ impl SwiftArgs { match self.cmd { SwiftCommand::BuildLibrary => build_library(), - SwiftCommand::BuildFramework => build_xcframework(), + SwiftCommand::BuildFramework { release, components_path, only_target } => { + build_xcframework(release, only_target, components_path) + } } } } @@ -38,54 +54,167 @@ fn build_library() -> Result<()> { let static_lib_filename = "libmatrix_sdk_ffi.a"; let root_directory = workspace::root_path()?; - let target_directory = root_directory.join("target"); + let target_directory = workspace::target_path()?; let ffi_directory = root_directory.join("bindings/apple/generated/matrix_sdk_ffi"); - let swift_directory = root_directory.join("bindings/apple/generated/swift"); - let udl_file = camino::Utf8PathBuf::from_path_buf( - root_directory.join("bindings/matrix-sdk-ffi/src/api.udl"), - ) - .expect("Root Dir contains non-utf8 characters"); - let outdir_overwrite = camino::Utf8PathBuf::from_path_buf(ffi_directory.clone()) - .expect("Root Dir contains non-utf8 characters"); - let library_file = camino::Utf8PathBuf::from_path_buf(ffi_directory.join(static_lib_filename)) - .expect("Root Dir contains non-utf8 characters"); + let library_file = ffi_directory.join(static_lib_filename); - fs::create_dir_all(ffi_directory.as_path())?; - fs::create_dir_all(swift_directory.as_path())?; + create_dir_all(ffi_directory.as_path())?; cmd!("cargo build -p matrix-sdk-ffi").run()?; - fs::rename( + rename( target_directory.join(release_type).join(static_lib_filename), ffi_directory.join(static_lib_filename), )?; + let swift_directory = root_directory.join("bindings/apple/generated/swift"); + create_dir_all(swift_directory.as_path())?; + + generate_uniffi(&library_file, &ffi_directory)?; + + let module_map_file = ffi_directory.join("module.modulemap"); + if module_map_file.exists() { + remove_file(module_map_file.as_path())?; + } + + // TODO: Find the modulemap in the ffi directory. + rename(ffi_directory.join("matrix_sdk_ffiFFI.modulemap"), module_map_file)?; + // TODO: Move all swift files. + rename( + ffi_directory.join("matrix_sdk_ffi.swift"), + swift_directory.join("matrix_sdk_ffi.swift"), + )?; + Ok(()) +} + +fn generate_uniffi(library_file: &PathBuf, ffi_directory: &PathBuf) -> Result<()> { + let root_directory = workspace::root_path()?; + let udl_file = camino::Utf8PathBuf::from_path_buf( + root_directory.join("bindings/matrix-sdk-ffi/src/api.udl"), + ) + .unwrap(); + let outdir_overwrite = camino::Utf8PathBuf::from_path_buf(ffi_directory.clone()).unwrap(); + let library_path = camino::Utf8PathBuf::from_path_buf(library_file.clone()).unwrap(); uniffi_bindgen::generate_bindings( udl_file.as_path(), None, vec!["swift"], Some(outdir_overwrite.as_path()), - Some(library_file.as_path()), + Some(library_path.as_path()), false, )?; + Ok(()) +} - let module_map_file = ffi_directory.join("module.modulemap"); - if module_map_file.exists() { - fs::remove_file(module_map_file.as_path())?; +fn build_for_target(target: &str, release: bool) -> Result { + let mut cmd = cmd!("cargo build -p matrix-sdk-ffi --target {target}"); + if release { + cmd = cmd.arg("--release"); + } + cmd.run()?; + Ok(workspace::target_path()? + .join(target) + .join(if release { "release" } else { "debug" }) + .join("libmatrix_sdk_ffi.a")) +} + +fn build_xcframework( + release_mode: bool, + only_target: Option, + components_path: Option, +) -> Result<()> { + let root_dir = workspace::root_path()?; + let generated_dir = root_dir.join("generated"); + let headers_dir = generated_dir.join("ls"); + let swift_dir = generated_dir.join("swift"); + create_dir_all(headers_dir.clone())?; + create_dir_all(swift_dir.clone())?; + + let (libs, uniff_lib_path) = if let Some(target) = only_target { + println!("-- Building for {target} 1/1"); + let build_path = build_for_target(target.as_str(), release_mode)?; + + (vec![build_path.clone()], build_path) + } else { + println!("-- Building for iOS [1/5]"); + let ios_path = build_for_target("aarch64-apple-ios", release_mode)?; + + println!("-- Building for macOS (Apple Silicon) [2/5]"); + let darwin_arm_path = build_for_target("aarch64-apple-darwin", release_mode)?; + println!("-- Building for macOS (Intel) [3/5]"); + let darwin_x86_path = build_for_target("x86_64-apple-darwin", release_mode)?; + + println!("-- Building for iOS Simulator (Apple Silicon) [4/5]"); + let ios_sim_arm_path = build_for_target("aarch64-apple-ios-sim", release_mode)?; + println!("-- Building for iOS Simulator (Intel) [5/5]"); + let ios_sim_x86_path = build_for_target("x86_64-apple-ios", release_mode)?; + + println!("-- Running Lipo for MacOs [1/2]"); + // # MacOS + let lipo_target_macos = generated_dir.join("libmatrix_sdk_ffi_macos.a"); + cmd!( + "lipo -create {darwin_x86_path} {darwin_arm_path} + -output {lipo_target_macos}" + ) + .run()?; + + println!("-- Running Lipo for iOS Simulator [2/2]"); + // # iOS Simulator + let lipo_target_sim = generated_dir.join("libmatrix_sdk_ffi_iossimulator.a"); + cmd!( + "lipo -create {ios_sim_arm_path} {ios_sim_x86_path} + -output {lipo_target_sim}" + ) + .run()?; + (vec![lipo_target_macos, lipo_target_sim, ios_path], darwin_x86_path) + }; + + println!("-- Generate uniffi files"); + generate_uniffi(&uniff_lib_path, &generated_dir)?; + + rename(generated_dir.join("matrix_sdk_ffiFFI.h"), headers_dir.join("matrix_sdk_ffiFFI.h"))?; + + rename(generated_dir.join("matrix_sdk_ffi.swift"), swift_dir.join("matrix_sdk_ffi.swift"))?; + + println!("-- Generate MatrixSDKFFI.xcframework framework"); + let xcframework_path = generated_dir.join("MatrixSDKFFI.xcframework"); + if xcframework_path.exists() { + remove_dir_all(xcframework_path.as_path())?; + } + let mut cmd = cmd!("xcodebuild -create-xcframework"); + for p in libs { + cmd = cmd.arg("-library").arg(p).arg("-headers").arg(headers_dir.as_path()) + } + cmd.arg("-output").arg(xcframework_path.as_path()).run()?; + + // cleaning up the intermediate data + remove_dir_all(headers_dir.as_path())?; + + if let Some(path) = components_path { + println!("-- Copying MatrixSDKFFI.xcframework to {path:?}"); + let framework_target = path.join("MatrixSDKFFI.xcframework"); + let swift_target = path.join("Sources/MatrixRustSDK"); + if framework_target.exists() { + remove_dir_all(framework_target.as_path())?; + } + if swift_target.exists() { + remove_dir_all(swift_target.as_path())?; + } + create_dir_all(framework_target.as_path())?; + create_dir_all(swift_target.as_path())?; + fs_extra::dir::copy( + xcframework_path.as_path(), + framework_target.as_path(), + &fs_extra::dir::CopyOptions::default(), + )?; + fs_extra::dir::copy( + swift_dir.as_path(), + framework_target.as_path(), + &fs_extra::dir::CopyOptions::default(), + )?; } - // TODO: Find the modulemap in the ffi directory. - fs::rename(ffi_directory.join("matrix_sdk_ffiFFI.modulemap"), module_map_file)?; - // TODO: Move all swift files. - fs::rename( - ffi_directory.join("matrix_sdk_ffi.swift"), - swift_directory.join("matrix_sdk_ffi.swift"), - )?; + println!("-- All done and hunky dory. Enjoy!"); Ok(()) } - -fn build_xcframework() -> Result<()> { - println!("XCFramework not yet implemented."); - Ok(()) -} diff --git a/xtask/src/workspace.rs b/xtask/src/workspace.rs index c97ffd5e6..a4a3da3b9 100644 --- a/xtask/src/workspace.rs +++ b/xtask/src/workspace.rs @@ -15,3 +15,14 @@ pub fn root_path() -> Result { let metadata_json = cmd!("{cargo} metadata --no-deps --format-version 1").read()?; Ok(serde_json::from_str::(&metadata_json)?.workspace_root) } + +pub fn target_path() -> Result { + #[derive(Deserialize)] + struct Metadata { + target_directory: PathBuf, + } + + let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_owned()); + let metadata_json = cmd!("{cargo} metadata --no-deps --format-version 1").read()?; + Ok(serde_json::from_str::(&metadata_json)?.target_directory) +} From 5a1e347333051c2f219cdb3db1826af3ef786496 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 18:17:13 +0100 Subject: [PATCH 24/78] ci: Install stable toolchain for test-uniffi-codegen The xtask is explicitly using the stable, and there is no reason to use a less stable toolchain for this job. --- .github/workflows/bindings_ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/bindings_ci.yml b/.github/workflows/bindings_ci.yml index 23bed08e8..8dfb405a9 100644 --- a/.github/workflows/bindings_ci.yml +++ b/.github/workflows/bindings_ci.yml @@ -61,7 +61,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable profile: minimal override: true From 826705c174e82ded82626feab9c8db4d6d1c6f7f Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 3 Nov 2022 15:58:49 +0100 Subject: [PATCH 25/78] chore: Bump MSRV to 1.65 --- Cargo.toml | 3 +++ README.md | 2 +- benchmarks/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-ffi/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-js/Cargo.toml | 2 +- bindings/matrix-sdk-crypto-nodejs/Cargo.toml | 2 +- bindings/matrix-sdk-ffi/Cargo.toml | 2 +- crates/matrix-sdk-appservice/Cargo.toml | 2 +- crates/matrix-sdk-base/Cargo.toml | 2 +- crates/matrix-sdk-common/Cargo.toml | 2 +- crates/matrix-sdk-crypto/Cargo.toml | 2 +- crates/matrix-sdk-indexeddb/Cargo.toml | 2 +- crates/matrix-sdk-qrcode/Cargo.toml | 2 +- crates/matrix-sdk-sled/Cargo.toml | 2 +- crates/matrix-sdk-store-encryption/Cargo.toml | 2 +- crates/matrix-sdk/Cargo.toml | 2 +- testing/matrix-sdk-test-macros/Cargo.toml | 2 +- testing/matrix-sdk-test/Cargo.toml | 2 +- 18 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5961385fc..b01c7354d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ members = [ default-members = ["benchmarks", "crates/*"] resolver = "2" +[workspace.package] +rust-version = "1.65" + [workspace.dependencies] ruma = { git = "https://github.com/ruma/ruma", rev = "ed100afddb5fb30f1ccf368d7e712a3a483e63bf", features = ["client-api-c"] } ruma-common = { git = "https://github.com/ruma/ruma", rev = "ed100afddb5fb30f1ccf368d7e712a3a483e63bf" } diff --git a/README.md b/README.md index 22cb464dd..4befa0f35 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ The rust-sdk consists of multiple crates that can be picked at your convenience: ## Minimum Supported Rust Version (MSRV) -These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.64` +These crates are built with the Rust language version 2021 and require a minimum compiler version of `1.65`. ## Status diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 6efd9a2dc..1eb8bde76 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -3,7 +3,7 @@ name = "benchmarks" description = "Matrix SDK benchmarks" edition = "2021" license = "Apache-2.0" -rust-version = "1.56" +rust-version = { workspace = true } version = "1.0.0" publish = false diff --git a/bindings/matrix-sdk-crypto-ffi/Cargo.toml b/bindings/matrix-sdk-crypto-ffi/Cargo.toml index e8fc2de34..1826346d8 100644 --- a/bindings/matrix-sdk-crypto-ffi/Cargo.toml +++ b/bindings/matrix-sdk-crypto-ffi/Cargo.toml @@ -3,7 +3,7 @@ name = "matrix-sdk-crypto-ffi" version = "0.1.0" authors = ["Damir Jelić "] edition = "2021" -rust-version = "1.62" +rust-version = { workspace = true } description = "Uniffi based bindings for the Rust SDK crypto crate" repository = "https://github.com/matrix-org/matrix-rust-sdk" license = "Apache-2.0" diff --git a/bindings/matrix-sdk-crypto-js/Cargo.toml b/bindings/matrix-sdk-crypto-js/Cargo.toml index 993892ad2..9c2632704 100644 --- a/bindings/matrix-sdk-crypto-js/Cargo.toml +++ b/bindings/matrix-sdk-crypto-js/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["matrix", "chat", "messaging", "ruma", "nio"] license = "Apache-2.0" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.1.0-alpha.0" publish = false diff --git a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml index 5e2a9e895..21ce91d07 100644 --- a/bindings/matrix-sdk-crypto-nodejs/Cargo.toml +++ b/bindings/matrix-sdk-crypto-nodejs/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-crypto-nodejs" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.6.0" [package.metadata.docs.rs] diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index 48e7185e7..173fb3ea9 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -6,7 +6,7 @@ homepage = "https://github.com/matrix-org/matrix-rust-sdk" keywords = ["matrix", "chat", "messaging", "ffi"] license = "Apache-2.0" readme = "README.md" -rust-version = "1.56" +rust-version = { workspace = true } repository = "https://github.com/matrix-org/matrix-rust-sdk" [lib] diff --git a/crates/matrix-sdk-appservice/Cargo.toml b/crates/matrix-sdk-appservice/Cargo.toml index c4bd10106..de56797d2 100644 --- a/crates/matrix-sdk-appservice/Cargo.toml +++ b/crates/matrix-sdk-appservice/Cargo.toml @@ -8,7 +8,7 @@ keywords = ["matrix", "chat", "messaging", "ruma", "nio", "appservice"] license = "Apache-2.0" name = "matrix-sdk-appservice" version = "0.1.0" -rust-version = "1.62" +rust-version = { workspace = true } publish = false [features] diff --git a/crates/matrix-sdk-base/Cargo.toml b/crates/matrix-sdk-base/Cargo.toml index 7836c1993..807be807e 100644 --- a/crates/matrix-sdk-base/Cargo.toml +++ b/crates/matrix-sdk-base/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-base" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.6.1" [package.metadata.docs.rs] diff --git a/crates/matrix-sdk-common/Cargo.toml b/crates/matrix-sdk-common/Cargo.toml index fabb6b406..1eb38072e 100644 --- a/crates/matrix-sdk-common/Cargo.toml +++ b/crates/matrix-sdk-common/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-common" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.6.0" [package.metadata.docs.rs] diff --git a/crates/matrix-sdk-crypto/Cargo.toml b/crates/matrix-sdk-crypto/Cargo.toml index 7cc8fe677..dd61a6ac8 100644 --- a/crates/matrix-sdk-crypto/Cargo.toml +++ b/crates/matrix-sdk-crypto/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-crypto" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.6.0" [package.metadata.docs.rs] diff --git a/crates/matrix-sdk-indexeddb/Cargo.toml b/crates/matrix-sdk-indexeddb/Cargo.toml index 5b0ef4f4b..cd5703ce7 100644 --- a/crates/matrix-sdk-indexeddb/Cargo.toml +++ b/crates/matrix-sdk-indexeddb/Cargo.toml @@ -5,7 +5,7 @@ repository = "https://github.com/matrix-org/matrix-rust-sdk" description = "Web's IndexedDB Storage backend for matrix-sdk" license = "Apache-2.0" edition = "2021" -rust-version = "1.62" +rust-version = { workspace = true } readme = "README.md" [package.metadata.docs.rs] diff --git a/crates/matrix-sdk-qrcode/Cargo.toml b/crates/matrix-sdk-qrcode/Cargo.toml index 61677071e..818d2abcf 100644 --- a/crates/matrix-sdk-qrcode/Cargo.toml +++ b/crates/matrix-sdk-qrcode/Cargo.toml @@ -8,7 +8,7 @@ homepage = "https://github.com/matrix-org/matrix-rust-sdk" keywords = ["matrix", "chat", "messaging", "ruma", "nio"] license = "Apache-2.0" readme = "README.md" -rust-version = "1.62" +rust-version = { workspace = true } repository = "https://github.com/matrix-org/matrix-rust-sdk" [package.metadata.docs.rs] diff --git a/crates/matrix-sdk-sled/Cargo.toml b/crates/matrix-sdk-sled/Cargo.toml index b9b3b689a..81c086731 100644 --- a/crates/matrix-sdk-sled/Cargo.toml +++ b/crates/matrix-sdk-sled/Cargo.toml @@ -6,7 +6,7 @@ authors = ["Damir Jelić "] repository = "https://github.com/matrix-org/matrix-rust-sdk" description = "Sled Storage backend for matrix-sdk for native environments" license = "Apache-2.0" -rust-version = "1.62" +rust-version = { workspace = true } readme = "README.md" [package.metadata.docs.rs] diff --git a/crates/matrix-sdk-store-encryption/Cargo.toml b/crates/matrix-sdk-store-encryption/Cargo.toml index 35a423218..038ba7b17 100644 --- a/crates/matrix-sdk-store-encryption/Cargo.toml +++ b/crates/matrix-sdk-store-encryption/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" description = "Helpers for encrypted storage keys for the Matrix SDK" repository = "https://github.com/matrix-org/matrix-rust-sdk" license = "Apache-2.0" -rust-version = "1.62" +rust-version = { workspace = true } [package.metadata.docs.rs] rustdoc-args = ["--cfg", "docsrs"] diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index ff6d54d6f..9363882a2 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.6.2" [package.metadata.docs.rs] diff --git a/testing/matrix-sdk-test-macros/Cargo.toml b/testing/matrix-sdk-test-macros/Cargo.toml index 1a1d8cdfc..2fd9c7b7a 100644 --- a/testing/matrix-sdk-test-macros/Cargo.toml +++ b/testing/matrix-sdk-test-macros/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-test-macros" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.3.0" [lib] diff --git a/testing/matrix-sdk-test/Cargo.toml b/testing/matrix-sdk-test/Cargo.toml index 07677ed57..beac0fc34 100644 --- a/testing/matrix-sdk-test/Cargo.toml +++ b/testing/matrix-sdk-test/Cargo.toml @@ -8,7 +8,7 @@ license = "Apache-2.0" name = "matrix-sdk-test" readme = "README.md" repository = "https://github.com/matrix-org/matrix-rust-sdk" -rust-version = "1.62" +rust-version = { workspace = true } version = "0.6.0" [lib] From 7d2865f0047f7c56ba7867d7e4abf8e296e5853c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 11:57:41 +0100 Subject: [PATCH 26/78] refactor(bindings): Shorten is_transaction_id_valid method --- bindings/matrix-sdk-ffi/src/session_verification.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/session_verification.rs b/bindings/matrix-sdk-ffi/src/session_verification.rs index d2d175bc8..b5a5ffcb9 100644 --- a/bindings/matrix-sdk-ffi/src/session_verification.rs +++ b/bindings/matrix-sdk-ffi/src/session_verification.rs @@ -173,11 +173,10 @@ impl SessionVerificationController { } fn is_transaction_id_valid(&self, transaction_id: String) -> bool { - if let Some(verification) = &*self.verification_request.read().unwrap() { - return verification.flow_id() == transaction_id; + match &*self.verification_request.read().unwrap() { + Some(verification) => verification.flow_id() == transaction_id, + None => false, } - - false } async fn start_sas_verification(&self) { From db5d34e3852ef065746dfba251e48e6c10a5a32a Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 12:05:17 +0100 Subject: [PATCH 27/78] refactor(crypto): Reduce rightwards drift in test_device_signatures --- crates/matrix-sdk-crypto/src/backups/mod.rs | 66 ++++++++++----------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index bf82348e1..6b8484357 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -241,43 +241,43 @@ impl BackupMachine { if let Some(user_signatures) = signatures.get(self.account.user_id()) { for device_key_id in user_signatures.keys() { - if device_key_id.algorithm() == DeviceKeyAlgorithm::Ed25519 { - // No need to check our own device here, we're doing that - // using the check_own_device_signature(). - if device_key_id.device_id() == self.account.device_id() { - continue; - } else { - // We might iterate over some non-device signatures as well, but in this - // case there's no corresponding device and we get `Ok(None)` here, so - // things still work out. - let device = self - .store - .get_device(self.store.user_id(), device_key_id.device_id()) - .await?; + if device_key_id.algorithm() != DeviceKeyAlgorithm::Ed25519 { + continue; + } - trace!( - device_id = %device_key_id.device_id(), - "Checking backup auth data for device" - ); + // No need to check our own device here, we're doing that using + // the check_own_device_signature(). + if device_key_id.device_id() == self.account.device_id() { + continue; + } - let state = if let Some(device) = device { - self.backup_signed_by_device(device, signatures, auth_data) - } else { - trace!( - device_id = %device_key_id.device_id(), - "Device not found, can't check signature" - ); - SignatureState::Missing - }; + // We might iterate over some non-device signatures as well, + // but in this case there's no corresponding device and we get + // `Ok(None)` here, so things still work out. + let device = + self.store.get_device(self.store.user_id(), device_key_id.device_id()).await?; - result.insert(device_key_id.device_id().to_owned(), state); + trace!( + device_id = %device_key_id.device_id(), + "Checking backup auth data for device" + ); - // Abort the loop if we found a trusted and valid - // signature, unless we should check all of them. - if state.trusted() && !compute_all_signatures { - break; - } - } + let state = if let Some(device) = device { + self.backup_signed_by_device(device, signatures, auth_data) + } else { + trace!( + device_id = %device_key_id.device_id(), + "Device not found, can't check signature" + ); + SignatureState::Missing + }; + + result.insert(device_key_id.device_id().to_owned(), state); + + // Abort the loop if we found a trusted and valid signature, + // unless we should check all of them. + if state.trusted() && !compute_all_signatures { + break; } } } From cf241d8c320e8dac55b74e41f7ce12f02454a76c Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 12:09:37 +0100 Subject: [PATCH 28/78] refactor(crypto): Merge matches and reflow comments in accept_secret --- .../src/gossiping/machine.rs | 32 ++++++++----------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 2a1fc1ef7..93c216d67 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -751,29 +751,23 @@ impl GossipMachine { if secret_name != &SecretName::RecoveryKey { match self.store.import_secret(secret_name, &event.content.secret).await { Ok(_) => self.mark_as_done(request).await?, + // If this is a store error propagate it up the call stack. + Err(SecretImportError::Store(e)) => return Err(e), + // Otherwise warn that there was something wrong with the + // secret. Err(e) => { - // If this is a store error propagate it up - // the call stack. - if let SecretImportError::Store(e) = e { - return Err(e); - } else { - // Otherwise warn that there was - // something wrong with the secret. - warn!( - secret_name = secret_name.as_ref(), - error = ?e, - "Error while importing a secret" - ) - } + warn!( + secret_name = secret_name.as_ref(), + error = ?e, + "Error while importing a secret" + ); } } } else { - // Skip importing the recovery key here since - // we'll want to check if the public key matches - // to the latest version on the server. The key - // will not be zeroized and - // instead leave the key in the event and let - // the user import it later. + // Skip importing the recovery key here since we'll want to check + // if the public key matches to the latest version on the server. + // The key will not be zeroized and instead leave the key in the + // event and let the user import it later. } Ok(()) From 2528a5501bd82901de418a1ad094c558ab477380 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 12:12:26 +0100 Subject: [PATCH 29/78] refactor(crypto): Remove unnecessary ref mut in patterns --- crates/matrix-sdk-crypto/src/identities/manager.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/identities/manager.rs b/crates/matrix-sdk-crypto/src/identities/manager.rs index df9fb8e5f..3a9a6e6db 100644 --- a/crates/matrix-sdk-crypto/src/identities/manager.rs +++ b/crates/matrix-sdk-crypto/src/identities/manager.rs @@ -432,7 +432,7 @@ impl IdentityManager { let result = if let Some(mut i) = self.store.get_user_identity(user_id).await? { match &mut i { - ReadOnlyUserIdentities::Own(ref mut identity) => { + ReadOnlyUserIdentities::Own(identity) => { let user_signing = if let Some(s) = response .user_signing_keys .get(user_id) @@ -452,7 +452,7 @@ impl IdentityManager { .update(master_key, self_signing, user_signing) .map(|_| (i, false)) } - ReadOnlyUserIdentities::Other(ref mut identity) => { + ReadOnlyUserIdentities::Other(identity) => { identity.update(master_key, self_signing).map(|_| (i, false)) } } From d312aaecec0420ebf8ab9bf460331acdcae080b6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 12:35:42 +0100 Subject: [PATCH 30/78] refactor(crypto): Replace ? on if-else by early return --- crates/matrix-sdk-crypto/src/olm/signing/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs index 0d5028c43..a1273cba2 100644 --- a/crates/matrix-sdk-crypto/src/olm/signing/mod.rs +++ b/crates/matrix-sdk-crypto/src/olm/signing/mod.rs @@ -228,13 +228,13 @@ impl PrivateCrossSigningIdentity { let master = MasterSigning::from_base64(self.user_id().to_owned(), master_key)?; if public_identity.master_key() == &master.public_key { - Ok(Some(master)) + Some(master) } else { - Err(SecretImportError::MismatchedPublicKeys) + return Err(SecretImportError::MismatchedPublicKeys); } } else { - Ok(None) - }?; + None + }; let user_signing = if let Some(user_signing_key) = user_signing_key { let subkey = UserSigning::from_base64(self.user_id().to_owned(), user_signing_key)?; From e59acfe28c03ffc84f6d1806af3a5195b8e775d9 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 12:43:18 +0100 Subject: [PATCH 31/78] refactor: Use let-else to remove boilerplate code --- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 4 +- .../matrix-sdk-crypto-js/src/attachment.rs | 11 ++-- .../src/attachment.rs | 13 ++--- .../src/authentication_service.rs | 10 ++-- crates/matrix-sdk-appservice/src/lib.rs | 23 ++++----- crates/matrix-sdk-base/src/rooms/normal.rs | 9 ++-- .../src/gossiping/machine.rs | 50 +++++++++---------- .../src/identities/device.rs | 8 +-- crates/matrix-sdk-crypto/src/olm/account.rs | 33 ++++++------ .../src/verification/machine.rs | 5 +- .../src/verification/requests.rs | 50 ++++++++----------- .../matrix-sdk-indexeddb/src/crypto_store.rs | 5 +- .../matrix-sdk-indexeddb/src/state_store.rs | 5 +- crates/matrix-sdk/src/client/mod.rs | 5 +- .../src/room/timeline/event_handler.rs | 24 ++++----- crates/matrix-sdk/src/sync.rs | 9 ++-- examples/command_bot/src/main.rs | 9 ++-- examples/custom_events/src/main.rs | 20 +++----- examples/getting_started/src/main.rs | 33 +++++------- examples/image_bot/src/main.rs | 33 ++++-------- examples/wasm_command_bot/src/lib.rs | 23 ++------- labs/jack-in/src/components/details.rs | 19 ++----- 22 files changed, 150 insertions(+), 251 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index e29784ac3..223eabb44 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -797,9 +797,7 @@ impl OlmMachine { /// * `user_id` - The ID of the user for which we would like to fetch the /// verification requests. pub fn get_verification_requests(&self, user_id: &str) -> Vec { - let user_id = if let Ok(user_id) = UserId::parse(user_id) { - user_id - } else { + let Ok(user_id) = UserId::parse(user_id) else { return vec![]; }; diff --git a/bindings/matrix-sdk-crypto-js/src/attachment.rs b/bindings/matrix-sdk-crypto-js/src/attachment.rs index 59e65b566..7bf0b8cda 100644 --- a/bindings/matrix-sdk-crypto-js/src/attachment.rs +++ b/bindings/matrix-sdk-crypto-js/src/attachment.rs @@ -41,13 +41,10 @@ impl Attachment { /// destroyed. It is still possible to get a JSON-encoded backup /// by calling `EncryptedAttachment.mediaEncryptionInfo`. pub fn decrypt(attachment: &mut EncryptedAttachment) -> Result, JsError> { - let media_encryption_info = match attachment.media_encryption_info.take() { - Some(media_encryption_info) => media_encryption_info, - None => { - return Err(JsError::new( - "The media encryption info are absent from the given encrypted attachment", - )) - } + let Some(media_encryption_info) = attachment.media_encryption_info.take() else { + return Err(JsError::new( + "The media encryption info are absent from the given encrypted attachment", + )); }; let encrypted_data: &[u8] = attachment.encrypted_data.as_slice(); diff --git a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs index c4fee6d15..f0f2c9b2e 100644 --- a/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs +++ b/bindings/matrix-sdk-crypto-nodejs/src/attachment.rs @@ -50,14 +50,11 @@ impl Attachment { /// by calling `EncryptedAttachment.mediaEncryptionInfo`. #[napi] pub fn decrypt(attachment: &mut EncryptedAttachment) -> napi::Result { - let media_encryption_info = match attachment.media_encryption_info.take() { - Some(media_encryption_info) => media_encryption_info, - None => { - return Err(napi::Error::from_reason( - "The media encryption info are absent from the given encrypted attachment" - .to_owned(), - )) - } + let Some(media_encryption_info) = attachment.media_encryption_info.take() else { + return Err(napi::Error::from_reason( + "The media encryption info are absent from the given encrypted attachment" + .to_owned(), + )); }; let encrypted_data: &[u8] = attachment.encrypted_data.deref(); diff --git a/bindings/matrix-sdk-ffi/src/authentication_service.rs b/bindings/matrix-sdk-ffi/src/authentication_service.rs index 8468858b0..bef5a7f7b 100644 --- a/bindings/matrix-sdk-ffi/src/authentication_service.rs +++ b/bindings/matrix-sdk-ffi/src/authentication_service.rs @@ -104,9 +104,8 @@ impl AuthenticationService { initial_device_name: Option, device_id: Option, ) -> Result, AuthenticationError> { - let client = match &*self.client.read().unwrap() { - Some(client) => client.clone(), - None => return Err(AuthenticationError::ClientMissing), + let Some(client) = self.client.read().unwrap().clone() else { + return Err(AuthenticationError::ClientMissing); }; // Login and ask the server for the full user ID as this could be different from @@ -143,9 +142,8 @@ impl AuthenticationService { token: String, device_id: String, ) -> Result, AuthenticationError> { - let client = match &*self.client.read().unwrap() { - Some(client) => client.clone(), - None => return Err(AuthenticationError::ClientMissing), + let Some(client) = self.client.read().unwrap().clone() else { + return Err(AuthenticationError::ClientMissing); }; // Restore the client and ask the server for the full user ID as this diff --git a/crates/matrix-sdk-appservice/src/lib.rs b/crates/matrix-sdk-appservice/src/lib.rs index 2aa534466..d83c2de0d 100644 --- a/crates/matrix-sdk-appservice/src/lib.rs +++ b/crates/matrix-sdk-appservice/src/lib.rs @@ -434,10 +434,10 @@ impl AppService { // Find membership events affecting members in our namespace, and update // membership accordingly - for event in transaction.events.iter() { - let event = match event.deserialize() { - Ok(AnyTimelineEvent::State(AnyStateEvent::RoomMember(event))) => event, - _ => continue, + for raw_event in transaction.events.iter() { + let res = raw_event.deserialize(); + let Ok(AnyTimelineEvent::State(AnyStateEvent::RoomMember(event))) = res else { + continue; }; if !self.user_id_is_in_namespace(event.state_key()) { continue; @@ -468,11 +468,11 @@ impl AppService { let sender_localpart = self.registration.sender_localpart.clone(); let task = tokio::spawn(async move { - let virtual_user_localpart = match virtual_user_client.user_id() { - Some(user_id) => user_id.localpart(), + let Some(user_id) = virtual_user_client.user_id() else { // The client is not logged in, skipping - None => return Ok(()), + return Ok(()); }; + let virtual_user_localpart = user_id.localpart(); let mut response = sync_events::v3::Response::new(transaction.txn_id.to_string()); // Clients expect events to be grouped per room, where the @@ -483,12 +483,9 @@ impl AppService { // We special-case the `sender_localpart` user which receives all events and // by falling back to a membership of "join" if it's unknown. for raw_event in &transaction.events { - let room_id = match raw_event.deserialize_as::()?.room_id { - Some(room_id) => room_id, - None => { - warn!("Transaction contained event with no ID"); - continue; - } + let Some(room_id) = raw_event.deserialize_as::()?.room_id else { + warn!("Transaction contained event with no ID"); + continue; }; let key = &[USER_MEMBER, room_id.as_bytes(), b".", virtual_user_localpart.as_bytes()] diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index e681eba39..e8ac4e966 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -426,12 +426,9 @@ impl Room { /// return a `RoomMember` that can be in a joined, invited, left, banned /// state. pub async fn get_member(&self, user_id: &UserId) -> StoreResult> { - let member_event = - if let Some(m) = self.store.get_member_event(self.room_id(), user_id).await? { - m - } else { - return Ok(None); - }; + let Some(member_event) = self.store.get_member_event(self.room_id(), user_id).await? else { + return Ok(None); + }; let presence = self.store.get_presence_event(user_id).await?.and_then(|e| e.deserialize().ok()); diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 93c216d67..257282aae 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -1539,15 +1539,15 @@ mod tests { let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); - if let AnyDecryptedOlmEvent::ForwardedRoomKey(e) = decrypted.result.event { - let session = alice_machine - .receive_forwarded_room_key(decrypted.result.sender_key, &e) - .await - .unwrap(); - alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); - } else { + let AnyDecryptedOlmEvent::ForwardedRoomKey(ev) = decrypted.result.event else { panic!("Invalid decrypted event type"); - } + }; + + let session = alice_machine + .receive_forwarded_room_key(decrypted.result.sender_key, &ev) + .await + .unwrap(); + alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); // Check that alice now does have the session. let session = alice_machine @@ -1597,16 +1597,16 @@ mod tests { .is_none()); let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); - if let AnyDecryptedOlmEvent::ForwardedRoomKey(e) = decrypted.result.event { - let session = alice_machine - .receive_forwarded_room_key(decrypted.result.sender_key, &e) - .await - .unwrap(); - - assert!(session.is_none(), "We should not receive a room key from another user"); - } else { + let AnyDecryptedOlmEvent::ForwardedRoomKey(ev) = decrypted.result.event else { panic!("Invalid decrypted event type"); - } + }; + + let session = alice_machine + .receive_forwarded_room_key(decrypted.result.sender_key, &ev) + .await + .unwrap(); + + assert!(session.is_none(), "We should not receive a room key from another user"); } #[async_test] @@ -1750,15 +1750,15 @@ mod tests { let decrypted = alice_account.decrypt_to_device_event(&event).await.unwrap(); - if let AnyDecryptedOlmEvent::ForwardedRoomKey(e) = decrypted.result.event { - let session = alice_machine - .receive_forwarded_room_key(decrypted.result.sender_key, &e) - .await - .unwrap(); - alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); - } else { + let AnyDecryptedOlmEvent::ForwardedRoomKey(ev) = decrypted.result.event else { panic!("Invalid decrypted event type"); - } + }; + + let session = alice_machine + .receive_forwarded_room_key(decrypted.result.sender_key, &ev) + .await + .unwrap(); + alice_machine.store.save_inbound_group_sessions(&[session.unwrap()]).await.unwrap(); // Check that alice now does have the session. let session = alice_machine diff --git a/crates/matrix-sdk-crypto/src/identities/device.rs b/crates/matrix-sdk-crypto/src/identities/device.rs index 65cb364f9..da8a43420 100644 --- a/crates/matrix-sdk-crypto/src/identities/device.rs +++ b/crates/matrix-sdk-crypto/src/identities/device.rs @@ -572,9 +572,7 @@ impl ReadOnlyDevice { event_type: &str, content: Value, ) -> OlmResult<(Session, Raw)> { - let sender_key = if let Some(k) = self.curve25519_key() { - k - } else { + let Some(sender_key) = self.curve25519_key() else { warn!( user_id = %self.user_id(), device_id = %self.device_id(), @@ -592,9 +590,7 @@ impl ReadOnlyDevice { None }; - let mut session = if let Some(s) = session { - s - } else { + let Some(mut session) = session else { warn!( "Trying to encrypt a Megolm session for user {} on device {}, \ but no Olm session is found", diff --git a/crates/matrix-sdk-crypto/src/olm/account.rs b/crates/matrix-sdk-crypto/src/olm/account.rs index 129de8aad..cdf4392c8 100644 --- a/crates/matrix-sdk-crypto/src/olm/account.rs +++ b/crates/matrix-sdk-crypto/src/olm/account.rs @@ -276,10 +276,8 @@ impl Account { ) -> OlmResult> { let s = self.store.get_sessions(&sender_key.to_base64()).await?; - // We don't have any existing sessions, return early. - let sessions = if let Some(s) = s { - s - } else { + let Some(sessions) = s else { + // We don't have any existing sessions, return early. return Ok(None); }; @@ -442,22 +440,21 @@ impl Account { // ensures that we receive the room key even if we don't have access // to the device. if !matches!(event, AnyDecryptedOlmEvent::RoomKey(_)) { - if let Some(device) = - self.store.get_device_from_curve_key(event.sender(), sender_key).await? - { - if let Some(key) = device.ed25519_key() { - if key != event.keys().ed25519 { - return Err(EventError::MismatchedKeys( - key.into(), - event.keys().ed25519.into(), - ) - .into()); - } - } else { + let Some(device) = + self.store.get_device_from_curve_key(event.sender(), sender_key).await? else { return Err(EventError::MissingSigningKey.into()); - } - } else { + }; + + let Some(key) = device.ed25519_key() else { return Err(EventError::MissingSigningKey.into()); + }; + + if key != event.keys().ed25519 { + return Err(EventError::MismatchedKeys( + key.into(), + event.keys().ed25519.into(), + ) + .into()); } } diff --git a/crates/matrix-sdk-crypto/src/verification/machine.rs b/crates/matrix-sdk-crypto/src/verification/machine.rs index 8bcb1014b..bf97998e6 100644 --- a/crates/matrix-sdk-crypto/src/verification/machine.rs +++ b/crates/matrix-sdk-crypto/src/verification/machine.rs @@ -317,10 +317,7 @@ impl VerificationMachine { ) -> Result<(), CryptoStoreError> { let event = event.into(); - #[allow(clippy::question_mark)] - let flow_id = if let Ok(flow_id) = FlowId::try_from(&event) { - flow_id - } else { + let Ok(flow_id) = FlowId::try_from(&event) else { // This isn't a verification event, return early. return Ok(()); }; diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index 55c83d8ed..a9680bb85 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -994,19 +994,16 @@ impl RequestState { return Ok(None); } - let device = if let Some(device) = - self.store.get_device(&self.other_user_id, &self.state.other_device_id).await? - { - device - } else { - warn!( - user_id = self.other_user_id.as_str(), - device_id = self.state.other_device_id.as_str(), - "Can't create a QR code, the device that accepted the \ - verification doesn't exist" - ); - return Ok(None); - }; + let Some(device) = + self.store.get_device(&self.other_user_id, &self.state.other_device_id).await? else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't create a QR code, the device that accepted the \ + verification doesn't exist" + ); + return Ok(None); + }; let identities = self.store.get_identities(device).await?; @@ -1123,9 +1120,7 @@ impl RequestState { "Received a new verification start event", ); - let device = if let Some(d) = self.store.get_device(sender, content.from_device()).await? { - d - } else { + let Some(device) = self.store.get_device(sender, content.from_device()).await? else { warn!( sender = sender.as_str(), device = content.from_device().as_str(), @@ -1213,19 +1208,16 @@ impl RequestState { } // TODO signal why starting the sas flow doesn't work? - let device = if let Some(device) = - self.store.get_device(&self.other_user_id, &self.state.other_device_id).await? - { - device - } else { - warn!( - user_id = self.other_user_id.as_str(), - device_id = self.state.other_device_id.as_str(), - "Can't start the SAS verification flow, the device that \ - accepted the verification doesn't exist" - ); - return Ok(None); - }; + let Some(device) = + self.store.get_device(&self.other_user_id, &self.state.other_device_id).await? else { + warn!( + user_id = self.other_user_id.as_str(), + device_id = self.state.other_device_id.as_str(), + "Can't start the SAS verification flow, the device that \ + accepted the verification doesn't exist" + ); + return Ok(None); + }; let identities = self.store.get_identities(device).await?; diff --git a/crates/matrix-sdk-indexeddb/src/crypto_store.rs b/crates/matrix-sdk-indexeddb/src/crypto_store.rs index 7d88aa659..d618a8927 100644 --- a/crates/matrix-sdk-indexeddb/src/crypto_store.rs +++ b/crates/matrix-sdk-indexeddb/src/crypto_store.rs @@ -530,10 +530,7 @@ impl IndexeddbCryptoStore { for user_id in user_ids.iter() { let dirty: bool = !matches!(os.get(&user_id)?.await?.map(|v| v.into_serde()), Some(Ok(false))); - let user = match user_id.as_string().map(UserId::parse) { - Some(Ok(user)) => user, - _ => continue, - }; + let Some(Ok(user)) = user_id.as_string().map(UserId::parse) else { continue }; self.tracked_users_cache.insert(user.clone()); if dirty { diff --git a/crates/matrix-sdk-indexeddb/src/state_store.rs b/crates/matrix-sdk-indexeddb/src/state_store.rs index e2b610c1e..c5da59a13 100644 --- a/crates/matrix-sdk-indexeddb/src/state_store.rs +++ b/crates/matrix-sdk-indexeddb/src/state_store.rs @@ -793,10 +793,7 @@ impl IndexeddbStateStore { for (room_id, redactions) in &changes.redactions { let range = self.encode_to_range(KEYS::ROOM_STATE, room_id)?; - let cursor = match state.open_cursor_with_range(&range)?.await? { - Some(c) => c, - _ => continue, - }; + let Some(cursor) = state.open_cursor_with_range(&range)?.await? else { continue }; let mut room_version = None; diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 3b10fca2d..88d3b6b20 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -1312,11 +1312,8 @@ impl Client { let lock = self.inner.refresh_token_lock.try_lock(); if let Some(mut guard) = lock { - let mut session_tokens = if let Some(tokens) = self.session_tokens() { - tokens - } else { + let Some(mut session_tokens) = self.session_tokens() else { *guard = Err(RefreshTokenError::RefreshTokenRequired); - return Err(RefreshTokenError::RefreshTokenRequired.into()); }; diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index ed23f3d39..4f4d2d1d3 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -402,9 +402,8 @@ impl<'a> TimelineEventHandler<'a> { did_update = self.maybe_update_timeline_item(&rel.event_id, "redaction", |item| { let mut reactions = item.reactions.clone(); - let mut details_entry = match reactions.bundled.entry(rel.key) { - Entry::Occupied(o) => o, - Entry::Vacant(_) => return None, + let Entry::Occupied(mut details_entry) = reactions.bundled.entry(rel.key) else { + return None; }; let details = details_entry.get_mut(); details.count -= uint!(1); @@ -414,17 +413,14 @@ impl<'a> TimelineEventHandler<'a> { return Some(item.with_reactions(reactions)); } - let senders = match &mut details.senders { - TimelineDetails::Ready(senders) => senders, - _ => { - // FIXME: We probably want to support this somehow in - // the future, but right now it's not possible. - warn!( - "inconsistent state: shouldn't have a reaction_map entry for a \ - timeline item with incomplete reactions" - ); - return None; - } + let TimelineDetails::Ready(senders) = &mut details.senders else { + // FIXME: We probably want to support this somehow in + // the future, but right now it's not possible. + warn!( + "inconsistent state: shouldn't have a reaction_map entry for a \ + timeline item with incomplete reactions" + ); + return None; }; if let Some(idx) = senders.iter().position(|s| *s == sender) { diff --git a/crates/matrix-sdk/src/sync.rs b/crates/matrix-sdk/src/sync.rs index 48491d5e9..41290a4aa 100644 --- a/crates/matrix-sdk/src/sync.rs +++ b/crates/matrix-sdk/src/sync.rs @@ -93,12 +93,9 @@ impl Client { let mut futures = Vec::new(); for handler in &*self.notification_handlers().await { for (room_id, room_notifications) in notifications { - let room = match self.get_room(room_id) { - Some(room) => room, - None => { - warn!(%room_id, "Can't call notification handler, room not found"); - continue; - } + let Some(room) = self.get_room(room_id) else { + warn!(%room_id, "Can't call notification handler, room not found"); + continue; }; futures.extend(room_notifications.iter().map(|notification| { diff --git a/examples/command_bot/src/main.rs b/examples/command_bot/src/main.rs index 5355bd7bd..07e85e28e 100644 --- a/examples/command_bot/src/main.rs +++ b/examples/command_bot/src/main.rs @@ -4,19 +4,18 @@ use matrix_sdk::{ config::SyncSettings, room::Room, ruma::events::room::message::{ - MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, TextMessageEventContent, + MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, }, Client, }; async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { if let Room::Joined(room) = room { - let msg_body = match event.content.msgtype { - MessageType::Text(TextMessageEventContent { body, .. }) => body, - _ => return, + let MessageType::Text(text_content) = event.content.msgtype else { + return; }; - if msg_body.contains("!party") { + if text_content.body.contains("!party") { let content = RoomMessageEventContent::text_plain("🎉🎊🥳 let's PARTY!! 🥳🎊🎉"); println!("sending"); diff --git a/examples/custom_events/src/main.rs b/examples/custom_events/src/main.rs index ff2a32066..3b9315bd8 100644 --- a/examples/custom_events/src/main.rs +++ b/examples/custom_events/src/main.rs @@ -19,7 +19,7 @@ use matrix_sdk::{ macros::EventContent, room::{ member::StrippedRoomMemberEvent, - message::{MessageType, OriginalSyncRoomMessageEvent, TextMessageEventContent}, + message::{MessageType, OriginalSyncRoomMessageEvent}, }, }, OwnedEventId, @@ -51,19 +51,15 @@ pub struct AckEventContent { // we want to start the ping-ack-flow on "!ping" messages. async fn on_regular_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { - if let Room::Joined(room) = room { - let msg_body = match event.content.msgtype { - MessageType::Text(TextMessageEventContent { body, .. }) => body, - _ => return, - }; + let Room::Joined(room) = room else { return }; + let MessageType::Text(text_content) = event.content.msgtype else { return }; - if msg_body.contains("!ping") { - let content = PingEventContent {}; + if text_content.body.contains("!ping") { + let content = PingEventContent {}; - println!("sending ping"); - room.send(content, None).await.unwrap(); - println!("ping sent"); - } + println!("sending ping"); + room.send(content, None).await.unwrap(); + println!("ping sent"); } } diff --git a/examples/getting_started/src/main.rs b/examples/getting_started/src/main.rs index 162b4ca60..c342ed673 100644 --- a/examples/getting_started/src/main.rs +++ b/examples/getting_started/src/main.rs @@ -17,10 +17,7 @@ use matrix_sdk::{ room::Room, ruma::events::room::{ member::StrippedRoomMemberEvent, - message::{ - MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, - TextMessageEventContent, - }, + message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent}, }, Client, }; @@ -158,25 +155,21 @@ async fn on_stripped_state_member( async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { // First, we need to unpack the message: We only want messages from rooms we are // still in and that are regular text messages - ignoring everything else. - if let Room::Joined(room) = room { - let msg_body = match event.content.msgtype { - MessageType::Text(TextMessageEventContent { body, .. }) => body, - _ => return, - }; + let Room::Joined(room) = room else { return }; + let MessageType::Text(text_content) = event.content.msgtype else { return }; - // here comes the actual "logic": when the bot see's a `!party` in the message, - // it responds - if msg_body.contains("!party") { - let content = RoomMessageEventContent::text_plain("🎉🎊🥳 let's PARTY!! 🥳🎊🎉"); + // here comes the actual "logic": when the bot see's a `!party` in the message, + // it responds + if text_content.body.contains("!party") { + let content = RoomMessageEventContent::text_plain("🎉🎊🥳 let's PARTY!! 🥳🎊🎉"); - println!("sending"); + println!("sending"); - // send our message to the room we found the "!party" command in - // the last parameter is an optional transaction id which we don't - // care about. - room.send(content, None).await.unwrap(); + // send our message to the room we found the "!party" command in + // the last parameter is an optional transaction id which we don't + // care about. + room.send(content, None).await.unwrap(); - println!("message sent"); - } + println!("message sent"); } } diff --git a/examples/image_bot/src/main.rs b/examples/image_bot/src/main.rs index 0cfa1c321..9e506023c 100644 --- a/examples/image_bot/src/main.rs +++ b/examples/image_bot/src/main.rs @@ -5,37 +5,22 @@ use matrix_sdk::{ attachment::AttachmentConfig, config::SyncSettings, room::Room, - ruma::events::room::message::{ - MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, TextMessageEventContent, - }, + ruma::events::room::message::{MessageType, OriginalSyncRoomMessageEvent}, Client, }; use url::Url; async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room, image: Arc<[u8]>) { - if let Room::Joined(room) = room { - let msg_body = if let OriginalSyncRoomMessageEvent { - content: - RoomMessageEventContent { - msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }), - .. - }, - .. - } = event - { - msg_body - } else { - return; - }; + let Room::Joined(room) = room else { return }; + let MessageType::Text(text_content) = event.content.msgtype else { return }; - if msg_body.contains("!image") { - println!("sending image"); - room.send_attachment("cat", &mime::IMAGE_JPEG, &image, AttachmentConfig::new()) - .await - .unwrap(); + if text_content.body.contains("!image") { + println!("sending image"); + room.send_attachment("cat", &mime::IMAGE_JPEG, &image, AttachmentConfig::new()) + .await + .unwrap(); - println!("message sent"); - } + println!("message sent"); } } diff --git a/examples/wasm_command_bot/src/lib.rs b/examples/wasm_command_bot/src/lib.rs index 86298a8bb..935d82e58 100644 --- a/examples/wasm_command_bot/src/lib.rs +++ b/examples/wasm_command_bot/src/lib.rs @@ -3,10 +3,7 @@ use matrix_sdk::{ deserialized_responses::SyncResponse, ruma::{ events::{ - room::message::{ - MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, - TextMessageEventContent, - }, + room::message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent}, AnyMessageLikeEventContent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent, }, @@ -22,23 +19,11 @@ struct WasmBot(Client); impl WasmBot { async fn on_room_message(&self, room_id: &RoomId, event: &OriginalSyncRoomMessageEvent) { - let msg_body = if let OriginalSyncRoomMessageEvent { - content: - RoomMessageEventContent { - msgtype: MessageType::Text(TextMessageEventContent { body: msg_body, .. }), - .. - }, - .. - } = event - { - msg_body - } else { - return; - }; + let MessageType::Text(text_content) = &event.content.msgtype else { return }; - console::log_1(&format!("Received message event {:?}", &msg_body).into()); + console::log_1(&format!("Received message event {:?}", &text_content.body).into()); - if msg_body.contains("!party") { + if text_content.body.contains("!party") { let content = AnyMessageLikeEventContent::RoomMessage( RoomMessageEventContent::text_plain("🎉🎊🥳 let's PARTY!! 🥳🎊🎉"), ); diff --git a/labs/jack-in/src/components/details.rs b/labs/jack-in/src/components/details.rs index 3610eed68..bfea43336 100644 --- a/labs/jack-in/src/components/details.rs +++ b/labs/jack-in/src/components/details.rs @@ -45,16 +45,9 @@ impl Details { } pub fn refresh_data(&mut self) { - let room_id = - if let Some(r) = self.sstate.selected_room.lock_ref().clone() { r } else { return }; - - let room_data = { - let l = self.sstate.view().rooms.lock_ref(); - if let Some(room) = l.get(&room_id) { - room.clone() - } else { - return; - } + let Some(room_id) = self.sstate.selected_room.lock_ref().clone() else { return }; + let Some(room_data) = self.sstate.view().rooms.lock_ref().get(&room_id).cloned() else { + return; }; let name = room_data.name.clone().unwrap_or_else(|| "unknown".to_owned()); @@ -122,9 +115,7 @@ impl MockComponent for Details { .get_or(Attribute::Borders, AttrValue::Borders(Borders::default())) .unwrap_borders(); - let name = if let Some(name) = &self.name { - name.clone() - } else { + let Some(name) = &self.name else { // still empty frame.render_widget( Table::new(vec![Row::new(vec![Cell::from( @@ -172,7 +163,7 @@ impl MockComponent for Details { chunks[0], ); - let title = (name, Alignment::Left); + let title = (name.to_owned(), Alignment::Left); let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); let mut details = vec![]; From a30e40ed3a95e0548dc1edf97fcbfd444af66620 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 4 Nov 2022 13:38:36 +0100 Subject: [PATCH 32/78] refactor: Introduce more early returns to reduce rightwards drift --- crates/matrix-sdk-crypto/src/backups/mod.rs | 72 ++-- .../src/gossiping/machine.rs | 169 +++++---- .../src/identities/device.rs | 7 +- crates/matrix-sdk-crypto/src/olm/utility.rs | 17 +- .../src/verification/machine.rs | 322 +++++++++--------- .../matrix-sdk-crypto/src/verification/mod.rs | 66 ++-- .../src/verification/requests.rs | 147 ++++---- .../src/verification/sas/inner_sas.rs | 21 +- .../src/verification/sas/sas_state.rs | 153 +++++---- crates/matrix-sdk/src/client/builder.rs | 2 +- crates/matrix-sdk/src/client/mod.rs | 14 +- crates/matrix-sdk/src/encryption/mod.rs | 39 +-- .../src/encryption/verification/requests.rs | 22 +- crates/matrix-sdk/src/media.rs | 120 +++---- crates/matrix-sdk/src/room/common.rs | 9 +- crates/matrix-sdk/src/room/member.rs | 9 +- crates/matrix-sdk/src/sliding_sync.rs | 4 +- 17 files changed, 571 insertions(+), 622 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index 6b8484357..f8bd1094d 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -449,7 +449,7 @@ impl BackupMachine { .collect(); for session in &sessions { - session.mark_as_backed_up() + session.mark_as_backed_up(); } trace!(request_id = ?r.request_id, keys = ?r.sessions, "Marking room keys as backed up"); @@ -470,54 +470,54 @@ impl BackupMachine { expected = r.request_id.to_string().as_str(), got = request_id.to_string().as_str(), "Tried to mark a pending backup as sent but the request id didn't match" - ) + ); } } else { warn!( request_id = request_id.to_string().as_str(), "Tried to mark a pending backup as sent but there isn't a backup pending" ); - } + }; Ok(()) } async fn backup_helper(&self) -> Result, CryptoStoreError> { - if let Some(backup_key) = &*self.backup_key.read().await { - if let Some(version) = backup_key.backup_version() { - let sessions = - self.store.inbound_group_sessions_for_backup(Self::BACKUP_BATCH_SIZE).await?; - - if !sessions.is_empty() { - let key_count = sessions.len(); - let (backup, session_record) = Self::backup_keys(sessions, backup_key).await; - - info!( - key_count = key_count, - keys = ?session_record, - ?backup_key, - "Successfully created a room keys backup request" - ); - - let request = PendingBackup { - request_id: TransactionId::new(), - request: KeysBackupRequest { version, rooms: backup }, - sessions: session_record, - }; - - Ok(Some(request)) - } else { - trace!(?backup_key, "No room keys need to be backed up"); - Ok(None) - } - } else { - warn!("Trying to backup room keys but the backup key wasn't uploaded"); - Ok(None) - } - } else { + let Some(backup_key) = &*self.backup_key.read().await else { warn!("Trying to backup room keys but no backup key was found"); - Ok(None) + return Ok(None); + }; + + let Some(version) = backup_key.backup_version() else { + warn!("Trying to backup room keys but the backup key wasn't uploaded"); + return Ok(None); + }; + + let sessions = + self.store.inbound_group_sessions_for_backup(Self::BACKUP_BATCH_SIZE).await?; + + if sessions.is_empty() { + trace!(?backup_key, "No room keys need to be backed up"); + return Ok(None); } + + let key_count = sessions.len(); + let (backup, session_record) = Self::backup_keys(sessions, backup_key).await; + + info!( + key_count = key_count, + keys = ?session_record, + ?backup_key, + "Successfully created a room keys backup request" + ); + + let request = PendingBackup { + request_id: TransactionId::new(), + request: KeysBackupRequest { version, rooms: backup }, + sessions: session_record, + }; + + Ok(Some(request)) } /// Backup all the non-backed up room keys we know about diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 257282aae..19409cf1d 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -315,68 +315,7 @@ impl GossipMachine { let device = self.store.get_device(&event.sender, &event.content.requesting_device_id).await?; - if let Some(device) = device { - match self.should_share_key(&device, &session).await { - Err(e) => { - if let KeyForwardDecision::ChangedSenderKey = e { - warn!( - user_id = device.user_id().as_str(), - device_id = device.device_id().as_str(), - "Received a key request from a device that changed \ - their Curve25519 sender key" - ); - } else { - debug!( - user_id = device.user_id().as_str(), - device_id = device.device_id().as_str(), - reason = ?e, - "Received a key request that we won't serve", - ); - } - - Ok(None) - } - Ok(message_index) => { - info!( - user_id = %device.user_id(), - device_id = %device.device_id(), - session_id = session.session_id(), - room_id = %session.room_id, - ?message_index, - "Serving a room key request", - ); - - match self.forward_room_key(&session, &device, message_index).await { - Ok(s) => Ok(Some(s)), - Err(OlmError::MissingSession) => { - info!( - user_id = %device.user_id(), - device_id = %device.device_id(), - session_id = session.session_id(), - "Key request is missing an Olm session, \ - putting the request in the wait queue", - ); - self.handle_key_share_without_session(device, event.to_owned().into()); - - Ok(None) - } - Err(OlmError::SessionExport(e)) => { - warn!( - user_id = %device.user_id(), - device_id = %device.device_id(), - session_id = session.session_id(), - "Can't serve a room key request, the session \ - can't be exported into a forwarded room key: \ - {:?}", - e - ); - Ok(None) - } - Err(e) => Err(e), - } - } - } - } else { + let Some(device) = device else { warn!( user_id = %event.sender, device_id = %event.content.requesting_device_id, @@ -384,7 +323,66 @@ impl GossipMachine { ); self.store.update_tracked_user(&event.sender, true).await?; - Ok(None) + return Ok(None); + }; + + let message_index = match self.should_share_key(&device, &session).await { + Ok(message_index) => message_index, + Err(e) => { + if let KeyForwardDecision::ChangedSenderKey = e { + warn!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + "Received a key request from a device that changed \ + their Curve25519 sender key" + ); + } else { + debug!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + reason = ?e, + "Received a key request that we won't serve", + ); + } + + return Ok(None); + } + }; + + info!( + user_id = %device.user_id(), + device_id = %device.device_id(), + session_id = session.session_id(), + room_id = %session.room_id, + ?message_index, + "Serving a room key request", + ); + + match self.forward_room_key(&session, &device, message_index).await { + Ok(s) => Ok(Some(s)), + Err(OlmError::MissingSession) => { + info!( + user_id = %device.user_id(), + device_id = %device.device_id(), + session_id = session.session_id(), + "Key request is missing an Olm session, \ + putting the request in the wait queue", + ); + self.handle_key_share_without_session(device, event.to_owned().into()); + + Ok(None) + } + Err(OlmError::SessionExport(e)) => { + warn!( + user_id = %device.user_id(), + device_id = %device.device_id(), + session_id = session.session_id(), + "Can't serve a room key request, the session \ + can't be exported into a forwarded room key: {e:?}", + ); + Ok(None) + } + Err(e) => Err(e), } } @@ -919,25 +917,18 @@ impl GossipMachine { sender_key: Curve25519PublicKey, event: &DecryptedForwardedRoomKeyEvent, ) -> Result, CryptoStoreError> { - if let Some(info) = event.room_key_info() { - if let Some(request) = - self.store.get_secret_request_by_info(&info.clone().into()).await? - { - if self.should_accept_forward(&request, sender_key).await? { - self.accept_forwarded_room_key(&request, sender_key, event).await - } else { - warn!( - sender = %event.sender, - %sender_key, - room_id = %info.room_id(), - session_id = info.session_id(), - "Received a forwarded room key from an unknown device, or \ - from a device that the key request recipient doesn't own", - ); + let Some(info) = event.room_key_info() else { + warn!( + sender = event.sender.as_str(), + sender_key = sender_key.to_base64(), + algorithm = %event.content.algorithm(), + "Received a forwarded room key with an unsupported algorithm", + ); + return Ok(None); + }; - Ok(None) - } - } else { + let Some(request) = + self.store.get_secret_request_by_info(&info.clone().into()).await? else { warn!( sender = %event.sender, sender_key = %sender_key, @@ -947,15 +938,19 @@ impl GossipMachine { algorithm = %info.algorithm(), "Received a forwarded room key that we didn't request", ); + return Ok(None); + }; - Ok(None) - } + if self.should_accept_forward(&request, sender_key).await? { + self.accept_forwarded_room_key(&request, sender_key, event).await } else { warn!( - sender = event.sender.as_str(), - sender_key = sender_key.to_base64(), - algorithm = %event.content.algorithm(), - "Received a forwarded room key with an unsupported algorithm", + sender = %event.sender, + %sender_key, + room_id = %info.room_id(), + session_id = info.session_id(), + "Received a forwarded room key from an unknown device, or \ + from a device that the key request recipient doesn't own", ); Ok(None) diff --git a/crates/matrix-sdk-crypto/src/identities/device.rs b/crates/matrix-sdk-crypto/src/identities/device.rs index da8a43420..24eb4b679 100644 --- a/crates/matrix-sdk-crypto/src/identities/device.rs +++ b/crates/matrix-sdk-crypto/src/identities/device.rs @@ -208,11 +208,8 @@ impl Device { /// Get the Olm sessions that belong to this device. pub(crate) async fn get_sessions(&self) -> StoreResult>>>> { - if let Some(k) = self.curve25519_key() { - self.verification_machine.store.get_sessions(&k.to_base64()).await - } else { - Ok(None) - } + let Some(k) = self.curve25519_key() else { return Ok(None) }; + self.verification_machine.store.get_sessions(&k.to_base64()).await } /// Is this device considered to be verified. diff --git a/crates/matrix-sdk-crypto/src/olm/utility.rs b/crates/matrix-sdk-crypto/src/olm/utility.rs index 64c23b60c..1d1545bac 100644 --- a/crates/matrix-sdk-crypto/src/olm/utility.rs +++ b/crates/matrix-sdk-crypto/src/olm/utility.rs @@ -147,14 +147,15 @@ fn verify_signature( signatures: &Signatures, canonical_json: &str, ) -> Result<(), SignatureError> { - if let Some(s) = signatures.get(user_id).and_then(|m| m.get(key_id)) { - match s { - Ok(Signature::Ed25519(s)) => Ok(public_key.verify(canonical_json.as_bytes(), s)?), - Ok(Signature::Other(_)) => Err(SignatureError::UnsupportedAlgorithm), - Err(_) => Err(SignatureError::InvalidSignature), - } - } else { - Err(SignatureError::NoSignatureFound) + let s = signatures + .get(user_id) + .and_then(|m| m.get(key_id)) + .ok_or(SignatureError::NoSignatureFound)?; + + match s { + Ok(Signature::Ed25519(s)) => Ok(public_key.verify(canonical_json.as_bytes(), s)?), + Ok(Signature::Other(_)) => Err(SignatureError::UnsupportedAlgorithm), + Err(_) => Err(SignatureError::InvalidSignature), } } diff --git a/crates/matrix-sdk-crypto/src/verification/machine.rs b/crates/matrix-sdk-crypto/src/verification/machine.rs index bf97998e6..221794c5f 100644 --- a/crates/matrix-sdk-crypto/src/verification/machine.rs +++ b/crates/matrix-sdk-crypto/src/verification/machine.rs @@ -341,180 +341,186 @@ impl VerificationMachine { } }; - if let Some(content) = event.verification_content() { - match &content { - AnyVerificationContent::Request(r) => { - info!( + let Some(content) = event.verification_content() else { return Ok(()) }; + match &content { + AnyVerificationContent::Request(r) => { + info!( + sender = event.sender().as_str(), + from_device = r.from_device().as_str(), + "Received a new verification request", + ); + + let Some(timestamp) = event.timestamp() else { + warn!( sender = event.sender().as_str(), from_device = r.from_device().as_str(), - "Received a new verification request", + "The key verification request didn't contain a valid timestamp" ); + return Ok(()); + }; - if let Some(timestamp) = event.timestamp() { - if Self::is_timestamp_valid(timestamp) { - if !event_sent_from_us(&event, r.from_device()) { - let request = VerificationRequest::from_request( - self.verifications.clone(), - self.store.clone(), - event.sender(), - flow_id, - r, - ); - - self.insert_request(request); - } else { - trace!( - sender = event.sender().as_str(), - from_device = r.from_device().as_str(), - "The received verification request was sent by us, ignoring it", - ); - } - } else { - trace!( - sender = event.sender().as_str(), - from_device = r.from_device().as_str(), - ?timestamp, - "The received verification request was too old or too far into the future", - ); - } - } else { - warn!( - sender = event.sender().as_str(), - from_device = r.from_device().as_str(), - "The key verification request didn't contain a valid timestamp" - ); - } + if !Self::is_timestamp_valid(timestamp) { + trace!( + sender = event.sender().as_str(), + from_device = r.from_device().as_str(), + ?timestamp, + "The received verification request was too old or too far into the future", + ); + return Ok(()); } - AnyVerificationContent::Cancel(c) => { - if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) { - verification.receive_cancel(event.sender(), c); - } - if let Some(verification) = - self.get_verification(event.sender(), flow_id.as_str()) - { - match verification { - Verification::SasV1(sas) => { - // This won't produce an outgoing content - let _ = sas.receive_any_event(event.sender(), &content); - } - #[cfg(feature = "qrcode")] - Verification::QrV1(qr) => qr.receive_cancel(event.sender(), c), - } - } + if event_sent_from_us(&event, r.from_device()) { + trace!( + sender = event.sender().as_str(), + from_device = r.from_device().as_str(), + "The received verification request was sent by us, ignoring it", + ); + return Ok(()); } - AnyVerificationContent::Ready(c) => { - if let Some(request) = self.get_request(event.sender(), flow_id.as_str()) { - if request.flow_id() == &flow_id { - request.receive_ready(event.sender(), c); - } else { - flow_id_mismatch(); - } - } - } - AnyVerificationContent::Start(c) => { - if let Some(request) = self.get_request(event.sender(), flow_id.as_str()) { - if request.flow_id() == &flow_id { - request.receive_start(event.sender(), c).await? - } else { - flow_id_mismatch(); - } - } else if let FlowId::ToDevice(_) = flow_id { - // TODO remove this soon, this has been deprecated by - // MSC3122 https://github.com/matrix-org/matrix-doc/pull/3122 - if let Some(device) = - self.store.get_device(event.sender(), c.from_device()).await? - { - let identities = self.store.get_identities(device).await?; - match Sas::from_start_event(flow_id, c, identities, None, false) { - Ok(sas) => { - self.verifications.insert_sas(sas); - } - Err(cancellation) => self.queue_up_content( - event.sender(), - c.from_device(), - cancellation, - None, - ), - } - } - } - } - AnyVerificationContent::Accept(_) | AnyVerificationContent::Key(_) => { - if let Some(sas) = self.get_sas(event.sender(), flow_id.as_str()) { - if sas.flow_id() == &flow_id { - if let Some((content, request_info)) = - sas.receive_any_event(event.sender(), &content) - { - self.queue_up_content( - sas.other_user_id(), - sas.other_device_id(), - content, - request_info, - ); - } - } else { - flow_id_mismatch(); - } - } - } - AnyVerificationContent::Mac(_) => { - if let Some(s) = self.get_sas(event.sender(), flow_id.as_str()) { - if s.flow_id() == &flow_id { - let content = s.receive_any_event(event.sender(), &content); + let request = VerificationRequest::from_request( + self.verifications.clone(), + self.store.clone(), + event.sender(), + flow_id, + r, + ); - if s.is_done() { - self.mark_sas_as_done(s, content.map(|(c, _)| c)).await?; - } else { - // Even if we are not done (yet), there might be content to send - // out, e.g. in the case where we are done with our side of the - // verification process, but the other side has not yet sent their - // "done". - if let Some((content, request_id)) = content { - self.queue_up_content( - s.other_user_id(), - s.other_device_id(), - content, - request_id, - ); - } - } - } else { - flow_id_mismatch(); - } - } + self.insert_request(request); + } + AnyVerificationContent::Cancel(c) => { + if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) { + verification.receive_cancel(event.sender(), c); } - AnyVerificationContent::Done(c) => { - if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) { - verification.receive_done(event.sender(), c); - } - #[allow(clippy::single_match)] - match self.get_verification(event.sender(), flow_id.as_str()) { - Some(Verification::SasV1(sas)) => { - let content = sas.receive_any_event(event.sender(), &content); - - if sas.is_done() { - self.mark_sas_as_done(sas, content.map(|(c, _)| c)).await?; - } + if let Some(verification) = self.get_verification(event.sender(), flow_id.as_str()) + { + match verification { + Verification::SasV1(sas) => { + // This won't produce an outgoing content + let _ = sas.receive_any_event(event.sender(), &content); } #[cfg(feature = "qrcode")] - Some(Verification::QrV1(qr)) => { - let (cancellation, request) = qr.receive_done(c).await?; - - if let Some(c) = cancellation { - self.verifications.add_request(c.into()) - } - - if let Some(s) = request { - self.verifications.add_request(s.into()) - } - } - None => (), + Verification::QrV1(qr) => qr.receive_cancel(event.sender(), c), } } } + AnyVerificationContent::Ready(c) => { + let Some(request) = self.get_request(event.sender(), flow_id.as_str()) else { + return Ok(()); + }; + + if request.flow_id() == &flow_id { + request.receive_ready(event.sender(), c); + } else { + flow_id_mismatch(); + } + } + AnyVerificationContent::Start(c) => { + if let Some(request) = self.get_request(event.sender(), flow_id.as_str()) { + if request.flow_id() == &flow_id { + request.receive_start(event.sender(), c).await? + } else { + flow_id_mismatch(); + } + } else if let FlowId::ToDevice(_) = flow_id { + // TODO remove this soon, this has been deprecated by + // MSC3122 https://github.com/matrix-org/matrix-doc/pull/3122 + if let Some(device) = + self.store.get_device(event.sender(), c.from_device()).await? + { + let identities = self.store.get_identities(device).await?; + + match Sas::from_start_event(flow_id, c, identities, None, false) { + Ok(sas) => { + self.verifications.insert_sas(sas); + } + Err(cancellation) => self.queue_up_content( + event.sender(), + c.from_device(), + cancellation, + None, + ), + } + } + } + } + AnyVerificationContent::Accept(_) | AnyVerificationContent::Key(_) => { + let Some(sas) = self.get_sas(event.sender(), flow_id.as_str()) else { + return Ok(()); + }; + + if sas.flow_id() != &flow_id { + flow_id_mismatch(); + return Ok(()); + } + + let Some((content, request_info)) = + sas.receive_any_event(event.sender(), &content) else { return Ok(()) }; + + self.queue_up_content( + sas.other_user_id(), + sas.other_device_id(), + content, + request_info, + ); + } + AnyVerificationContent::Mac(_) => { + let Some(s) = self.get_sas(event.sender(), flow_id.as_str()) else { return Ok(()) }; + + if s.flow_id() != &flow_id { + flow_id_mismatch(); + return Ok(()); + } + + let content = s.receive_any_event(event.sender(), &content); + + if s.is_done() { + self.mark_sas_as_done(s, content.map(|(c, _)| c)).await?; + } else { + // Even if we are not done (yet), there might be content to + // send out, e.g. in the case where we are done with our + // side of the verification process, but the other side has + // not yet sent their "done". + let Some((content, request_id)) = content else { return Ok(()) }; + + self.queue_up_content( + s.other_user_id(), + s.other_device_id(), + content, + request_id, + ); + } + } + AnyVerificationContent::Done(c) => { + if let Some(verification) = self.get_request(event.sender(), flow_id.as_str()) { + verification.receive_done(event.sender(), c); + } + + #[allow(clippy::single_match)] + match self.get_verification(event.sender(), flow_id.as_str()) { + Some(Verification::SasV1(sas)) => { + let content = sas.receive_any_event(event.sender(), &content); + + if sas.is_done() { + self.mark_sas_as_done(sas, content.map(|(c, _)| c)).await?; + } + } + #[cfg(feature = "qrcode")] + Some(Verification::QrV1(qr)) => { + let (cancellation, request) = qr.receive_done(c).await?; + + if let Some(c) = cancellation { + self.verifications.add_request(c.into()) + } + + if let Some(s) = request { + self.verifications.add_request(s.into()) + } + } + None => {} + } + } } Ok(()) diff --git a/crates/matrix-sdk-crypto/src/verification/mod.rs b/crates/matrix-sdk-crypto/src/verification/mod.rs index b5018afa0..d274e8148 100644 --- a/crates/matrix-sdk-crypto/src/verification/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/mod.rs @@ -701,45 +701,41 @@ impl IdentitiesBeingVerified { ) -> Result, CryptoStoreError> { let device = self.store.get_device(self.other_user_id(), self.other_device_id()).await?; - if let Some(device) = device { - if device.keys() == self.device_being_verified.keys() { - if verified_devices.map_or(false, |v| v.contains(&device)) { - trace!( - user_id = device.user_id().as_str(), - device_id = device.device_id().as_str(), - "Marking device as verified.", - ); - - device.set_trust_state(LocalTrust::Verified); - - Ok(Some(device)) - } else { - info!( - user_id = device.user_id().as_str(), - device_id = device.device_id().as_str(), - "The interactive verification process didn't verify \ - the device", - ); - - Ok(None) - } - } else { - warn!( - user_id = device.user_id().as_str(), - device_id = device.device_id().as_str(), - "The device keys have changed while an interactive \ - verification was going on, not marking the device as verified.", - ); - Ok(None) - } - } else { + let Some(device) = device else { let device = &self.device_being_verified; - info!( user_id = device.user_id().as_str(), device_id = device.device_id().as_str(), - "The device was deleted while an interactive verification was \ - going on.", + "The device was deleted while an interactive verification was going on.", + ); + return Ok(None); + }; + + if device.keys() != self.device_being_verified.keys() { + warn!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + "The device keys have changed while an interactive verification \ + was going on, not marking the device as verified.", + ); + return Ok(None); + } + + if verified_devices.map_or(false, |v| v.contains(&device)) { + trace!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + "Marking device as verified.", + ); + + device.set_trust_state(LocalTrust::Verified); + + Ok(Some(device)) + } else { + info!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + "The interactive verification process didn't verify the device", ); Ok(None) diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index a9680bb85..63febb9cb 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -322,8 +322,8 @@ impl VerificationRequest { &self, data: QrVerificationData, ) -> Result, ScanError> { - let fut = if let InnerRequest::Ready(r) = &*self.inner.lock().unwrap() { - Some(QrVerification::from_scan( + let future = if let InnerRequest::Ready(r) = &*self.inner.lock().unwrap() { + QrVerification::from_scan( r.store.clone(), r.other_user_id.clone(), r.state.other_device_id.clone(), @@ -331,41 +331,37 @@ impl VerificationRequest { data, self.we_started, Some(self.inner.clone().into()), - )) + ) } else { - None + return Ok(None); }; - if let Some(future) = fut { - let qr_verification = future.await?; + let qr_verification = future.await?; - // We may have previously started our own QR verification (e.g. two devices - // displaying QR code at the same time), so we need to replace it with the newly - // scanned code. - if self - .verification_cache - .get_qr(qr_verification.other_user_id(), qr_verification.flow_id().as_str()) - .is_some() - { - debug!( - user_id = %self.other_user(), - flow_id = self.flow_id().as_str(), - "Replacing existing QR verification" - ); - self.verification_cache.replace_qr(qr_verification.clone()); - } else { - debug!( - user_id = %self.other_user(), - flow_id = self.flow_id().as_str(), - "Inserting new QR verification" - ); - self.verification_cache.insert_qr(qr_verification.clone()); - } - - Ok(Some(qr_verification)) + // We may have previously started our own QR verification (e.g. two devices + // displaying QR code at the same time), so we need to replace it with the newly + // scanned code. + if self + .verification_cache + .get_qr(qr_verification.other_user_id(), qr_verification.flow_id().as_str()) + .is_some() + { + debug!( + user_id = %self.other_user(), + flow_id = self.flow_id().as_str(), + "Replacing existing QR verification" + ); + self.verification_cache.replace_qr(qr_verification.clone()); } else { - Ok(None) + debug!( + user_id = %self.other_user(), + flow_id = self.flow_id().as_str(), + "Inserting new QR verification" + ); + self.verification_cache.insert_qr(qr_verification.clone()); } + + Ok(Some(qr_verification)) } pub(crate) fn from_request( @@ -541,31 +537,24 @@ impl VerificationRequest { let cancelled = Cancelled::new(true, code); let cancel_content = cancelled.as_content(self.flow_id()); - if let OutgoingContent::ToDevice(c) = cancel_content { - let recipients: Vec = self - .recipient_devices - .iter() - .filter(|&d| filter_device.map_or(true, |device| **d != *device)) - .cloned() - .collect(); + let OutgoingContent::ToDevice(c) = cancel_content else { return None }; + let recip_devices: Vec = self + .recipient_devices + .iter() + .filter(|&d| filter_device.map_or(true, |device| **d != *device)) + .cloned() + .collect(); - // We don't need to notify anyone if no recipients were present - // but we did have a filter device, since this means that only a - // single device received the `m.key.verification.request` and that - // device accepted the request. - if recipients.is_empty() && filter_device.is_some() { - None - } else { - Some(ToDeviceRequest::for_recipients( - self.other_user(), - recipients, - c, - TransactionId::new(), - )) - } - } else { - None + if recip_devices.is_empty() && filter_device.is_some() { + // We don't need to notify anyone if no recipients were present but + // we did have a filter device, since this means that only a single + // device received the `m.key.verification.request` and that device + // accepted the request. + return None; } + + let recipient = self.other_user(); + Some(ToDeviceRequest::for_recipients(recipient, recip_devices, c, TransactionId::new())) } pub(crate) fn receive_ready(&self, sender: &UserId, content: &ReadyContent<'_>) { @@ -601,17 +590,16 @@ impl VerificationRequest { ) -> Result<(), CryptoStoreError> { let inner = self.inner.lock().unwrap().clone(); - if let InnerRequest::Ready(s) = inner { - s.receive_start(sender, content, self.we_started, self.inner.clone().into()).await?; - } else { + let InnerRequest::Ready(s) = inner else { warn!( sender = sender.as_str(), device_id = content.from_device().as_str(), "Received a key verification start event but we're not yet in the ready state" ); - } + return Ok(()); + }; - Ok(()) + s.receive_start(sender, content, self.we_started, self.inner.clone().into()).await } pub(crate) fn receive_done(&self, sender: &UserId, content: &DoneContent<'_>) { @@ -628,21 +616,23 @@ impl VerificationRequest { } pub(crate) fn receive_cancel(&self, sender: &UserId, content: &CancelContent<'_>) { - if sender == self.other_user() { - trace!( - sender = sender.as_str(), - code = content.cancel_code().as_str(), - "Cancelling a verification request, other user has cancelled" - ); - let mut inner = self.inner.lock().unwrap(); - inner.cancel(false, content.cancel_code()); + if sender != self.other_user() { + return; + } - if self.we_started() { - if let Some(request) = - self.cancel_for_other_devices(content.cancel_code().to_owned(), None) - { - self.verification_cache.add_verification_request(request.into()); - } + trace!( + sender = sender.as_str(), + code = content.cancel_code().as_str(), + "Cancelling a verification request, other user has cancelled" + ); + let mut inner = self.inner.lock().unwrap(); + inner.cancel(false, content.cancel_code()); + + if self.we_started() { + if let Some(request) = + self.cancel_for_other_devices(content.cancel_code().to_owned(), None) + { + self.verification_cache.add_verification_request(request.into()); } } } @@ -726,14 +716,11 @@ impl InnerRequest { } fn accept(&mut self, methods: Vec) -> Option { - if let InnerRequest::Requested(s) = self { - let (state, content) = s.clone().accept(methods); - *self = InnerRequest::Ready(state); + let InnerRequest::Requested(s) = self else { return None }; + let (state, content) = s.clone().accept(methods); + *self = InnerRequest::Ready(state); - Some(content) - } else { - None - } + Some(content) } fn receive_done(&mut self, content: &DoneContent<'_>) { diff --git a/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs b/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs index 9e6d839a4..9c679814d 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/inner_sas.rs @@ -180,20 +180,17 @@ impl InnerSas { self, methods: Vec, ) -> Option<(InnerSas, OwnedAcceptContent)> { - if let InnerSas::Started(s) = self { - let sas = s.into_we_accepted(methods); - let content = sas.as_content(); + let InnerSas::Started(s) = self else { return None }; + let sas = s.into_we_accepted(methods); + let content = sas.as_content(); - trace!( - flow_id = sas.verification_flow_id.as_str(), - accepted_protocols = ?sas.state.accepted_protocols, - "Accepted a SAS verification" - ); + trace!( + flow_id = sas.verification_flow_id.as_str(), + accepted_protocols = ?sas.state.accepted_protocols, + "Accepted a SAS verification" + ); - Some((InnerSas::WeAccepted(sas), content)) - } else { - None - } + Some((InnerSas::WeAccepted(sas), content)) } #[cfg(test)] diff --git a/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs b/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs index 02d231fd5..c16c98fd5 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/sas_state.rs @@ -615,30 +615,30 @@ impl SasState { ) -> Result, SasState> { self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; - if let AcceptMethod::SasV1(content) = content.method() { - let accepted_protocols = AcceptedProtocols::try_from(content.clone()) - .map_err(|c| self.clone().cancel(true, c))?; + let AcceptMethod::SasV1(content) = content.method() else { + return Err(self.cancel(true, CancelCode::UnknownMethod)); + }; - let start_content = self.as_content().into(); + let accepted_protocols = AcceptedProtocols::try_from(content.clone()) + .map_err(|c| self.clone().cancel(true, c))?; - Ok(SasState { - inner: self.inner, - our_public_key: self.our_public_key, - ids: self.ids, - verification_flow_id: self.verification_flow_id, - creation_time: self.creation_time, - last_event_time: Instant::now().into(), - started_from_request: self.started_from_request, - state: Arc::new(Accepted { - start_content, - commitment: content.commitment.clone(), - request_id: TransactionId::new(), - accepted_protocols, - }), - }) - } else { - Err(self.cancel(true, CancelCode::UnknownMethod)) - } + let start_content = self.as_content().into(); + + Ok(SasState { + inner: self.inner, + our_public_key: self.our_public_key, + ids: self.ids, + verification_flow_id: self.verification_flow_id, + creation_time: self.creation_time, + last_event_time: Instant::now().into(), + started_from_request: self.started_from_request, + state: Arc::new(Accepted { + start_content, + commitment: content.commitment.clone(), + request_id: TransactionId::new(), + accepted_protocols, + }), + }) } } @@ -689,41 +689,44 @@ impl SasState { state: Arc::new(Cancelled::new(true, CancelCode::UnknownMethod)), }; - if let StartMethod::SasV1(method_content) = content.method() { - let commitment = calculate_commitment(our_public_key, content); + let state = match content.method() { + StartMethod::SasV1(method_content) => { + let commitment = calculate_commitment(our_public_key, content); - info!( - public_key = our_public_key.to_base64(), - %commitment, - ?content, - "Calculated SAS commitment", - ); + info!( + public_key = our_public_key.to_base64(), + %commitment, + ?content, + "Calculated SAS commitment", + ); - if let Ok(accepted_protocols) = AcceptedProtocols::try_from(method_content) { - Ok(SasState { - inner: Arc::new(Mutex::new(Some(sas))), - our_public_key, + let Ok(accepted_protocols) = AcceptedProtocols::try_from(method_content) else { + return Err(canceled()); + }; - ids: SasIds { account, other_device, other_identity, own_identity }, - - creation_time: Arc::new(Instant::now()), - last_event_time: Arc::new(Instant::now()), - started_from_request, - - verification_flow_id: flow_id, - - state: Arc::new(Started { - protocol_definitions: method_content.to_owned(), - accepted_protocols, - commitment, - }), - }) - } else { - Err(canceled()) + Started { + protocol_definitions: method_content.to_owned(), + accepted_protocols, + commitment, + } } - } else { - Err(canceled()) - } + _ => return Err(canceled()), + }; + + Ok(SasState { + inner: Arc::new(Mutex::new(Some(sas))), + our_public_key, + + ids: SasIds { account, other_device, other_identity, own_identity }, + + creation_time: Arc::new(Instant::now()), + last_event_time: Arc::new(Instant::now()), + started_from_request, + + verification_flow_id: flow_id, + + state: Arc::new(state), + }) } #[cfg(test)] @@ -813,30 +816,30 @@ impl SasState { ) -> Result, SasState> { self.check_event(sender, content.flow_id()).map_err(|c| self.clone().cancel(true, c))?; - if let AcceptMethod::SasV1(content) = content.method() { - let accepted_protocols = AcceptedProtocols::try_from(content.clone()) - .map_err(|c| self.clone().cancel(true, c))?; + let AcceptMethod::SasV1(content) = content.method() else { + return Err(self.cancel(true, CancelCode::UnknownMethod)); + }; - let start_content = self.as_content().into(); + let accepted_protocols = AcceptedProtocols::try_from(content.clone()) + .map_err(|c| self.clone().cancel(true, c))?; - Ok(SasState { - inner: self.inner, - our_public_key: self.our_public_key, - ids: self.ids, - verification_flow_id: self.verification_flow_id, - creation_time: self.creation_time, - last_event_time: Instant::now().into(), - started_from_request: self.started_from_request, - state: Arc::new(Accepted { - start_content, - commitment: content.commitment.clone(), - request_id: TransactionId::new(), - accepted_protocols, - }), - }) - } else { - Err(self.cancel(true, CancelCode::UnknownMethod)) - } + let start_content = self.as_content().into(); + + Ok(SasState { + inner: self.inner, + our_public_key: self.our_public_key, + ids: self.ids, + verification_flow_id: self.verification_flow_id, + creation_time: self.creation_time, + last_event_time: Instant::now().into(), + started_from_request: self.started_from_request, + state: Arc::new(Accepted { + start_content, + commitment: content.commitment.clone(), + request_id: TransactionId::new(), + accepted_protocols, + }), + }) } } diff --git a/crates/matrix-sdk/src/client/builder.rs b/crates/matrix-sdk/src/client/builder.rs index bfb7719e3..cc3fe02e2 100644 --- a/crates/matrix-sdk/src/client/builder.rs +++ b/crates/matrix-sdk/src/client/builder.rs @@ -380,7 +380,7 @@ impl ClientBuilder { if let Some(issuer) = well_known.authentication.map(|auth| auth.issuer) { authentication_issuer = Url::parse(&issuer).ok(); - }; + } well_known.homeserver.base_url } diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 88d3b6b20..6fc0a5297 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -310,11 +310,8 @@ impl Client { /// The OIDC Provider that is trusted by the homeserver. pub async fn authentication_issuer(&self) -> Option { - if let Some(server) = &self.inner.authentication_issuer { - Some(server.read().await.clone()) - } else { - None - } + let server = self.inner.authentication_issuer.as_ref()?; + Some(server.read().await.clone()) } fn session_meta(&self) -> Option<&SessionMeta> { @@ -1776,8 +1773,11 @@ impl Client { Request: OutgoingRequest + Debug, HttpError: From>, { - let homeserver = - if let Some(h) = homeserver { h } else { self.homeserver().await.to_string() }; + let homeserver = match homeserver { + Some(hs) => hs, + None => self.homeserver().await.to_string(), + }; + self.inner .http_client .send( diff --git a/crates/matrix-sdk/src/encryption/mod.rs b/crates/matrix-sdk/src/encryption/mod.rs index 552e37527..d751007d2 100644 --- a/crates/matrix-sdk/src/encryption/mod.rs +++ b/crates/matrix-sdk/src/encryption/mod.rs @@ -479,11 +479,8 @@ impl Encryption { /// This can be used to check which private cross signing keys we have /// stored locally. pub async fn cross_signing_status(&self) -> Option { - if let Some(machine) = self.client.olm_machine() { - Some(machine.cross_signing_status().await) - } else { - None - } + let machine = self.client.olm_machine()?; + Some(machine.cross_signing_status().await) } /// Get all the tracked users we know about @@ -562,12 +559,9 @@ impl Encryption { user_id: &UserId, device_id: &DeviceId, ) -> Result, CryptoStoreError> { - if let Some(machine) = self.client.olm_machine() { - let device = machine.get_device(user_id, device_id, None).await?; - Ok(device.map(|d| Device { inner: d, client: self.client.clone() })) - } else { - Ok(None) - } + let Some(machine) = self.client.olm_machine() else { return Ok(None) }; + let device = machine.get_device(user_id, device_id, None).await?; + Ok(device.map(|d| Device { inner: d, client: self.client.clone() })) } /// Get a map holding all the devices of an user. @@ -643,20 +637,17 @@ impl Encryption { ) -> Result, CryptoStoreError> { use crate::encryption::identities::UserIdentity; - if let Some(olm) = self.client.olm_machine() { - let identity = olm.get_identity(user_id, None).await?; + let Some(olm) = self.client.olm_machine() else { return Ok(None) }; + let identity = olm.get_identity(user_id, None).await?; - Ok(identity.map(|i| match i { - matrix_sdk_base::crypto::UserIdentities::Own(i) => { - UserIdentity::new_own(self.client.clone(), i) - } - matrix_sdk_base::crypto::UserIdentities::Other(i) => { - UserIdentity::new(self.client.clone(), i, self.client.get_dm_room(user_id)) - } - })) - } else { - Ok(None) - } + Ok(identity.map(|i| match i { + matrix_sdk_base::crypto::UserIdentities::Own(i) => { + UserIdentity::new_own(self.client.clone(), i) + } + matrix_sdk_base::crypto::UserIdentities::Other(i) => { + UserIdentity::new(self.client.clone(), i, self.client.get_dm_room(user_id)) + } + })) } /// Create and upload a new cross signing identity. diff --git a/crates/matrix-sdk/src/encryption/verification/requests.rs b/crates/matrix-sdk/src/encryption/verification/requests.rs index 2f395b884..7e3d44622 100644 --- a/crates/matrix-sdk/src/encryption/verification/requests.rs +++ b/crates/matrix-sdk/src/encryption/verification/requests.rs @@ -141,26 +141,20 @@ impl VerificationRequest { /// for the remainder of the verification flow. #[cfg(feature = "qrcode")] pub async fn scan_qr_code(&self, data: QrVerificationData) -> Result> { - if let Some(qr) = self.inner.scan_qr_code(data).await? { - if let Some(request) = qr.reciprocate() { - self.client.send_verification_request(request).await?; - } - - Ok(Some(QrVerification { inner: qr, client: self.client.clone() })) - } else { - Ok(None) + let Some(qr) = self.inner.scan_qr_code(data).await? else { return Ok(None) }; + if let Some(request) = qr.reciprocate() { + self.client.send_verification_request(request).await?; } + + Ok(Some(QrVerification { inner: qr, client: self.client.clone() })) } /// Transition from this verification request into a SAS verification flow. pub async fn start_sas(&self) -> Result> { - if let Some((sas, request)) = self.inner.start_sas().await? { - self.client.send_verification_request(request).await?; + let Some((sas, request)) = self.inner.start_sas().await? else { return Ok(None) }; + self.client.send_verification_request(request).await?; - Ok(Some(SasVerification { inner: sas, client: self.client.clone() })) - } else { - Ok(None) - } + Ok(Some(SasVerification { inner: sas, client: self.client.clone() })) } /// Cancel the verification request diff --git a/crates/matrix-sdk/src/media.rs b/crates/matrix-sdk/src/media.rs index 32620cd62..2ba8657be 100644 --- a/crates/matrix-sdk/src/media.rs +++ b/crates/matrix-sdk/src/media.rs @@ -115,50 +115,47 @@ impl Media { if use_cache { self.client.store().get_media_content(request).await? } else { None }; if let Some(content) = content { - Ok(content) - } else { - let content: Vec = match &request.source { - MediaSource::Encrypted(file) => { - let request = get_content::v3::Request::from_url(&file.url)?; - let content: Vec = self.client.send(request, None).await?.file; - - #[cfg(feature = "e2e-encryption")] - let content = { - let mut cursor = std::io::Cursor::new(content); - let mut reader = matrix_sdk_base::crypto::AttachmentDecryptor::new( - &mut cursor, - file.as_ref().clone().into(), - )?; - - let mut decrypted = Vec::new(); - reader.read_to_end(&mut decrypted)?; - - decrypted - }; - - content - } - MediaSource::Plain(uri) => { - if let MediaFormat::Thumbnail(size) = &request.format { - let request = get_content_thumbnail::v3::Request::from_url( - uri, - size.width, - size.height, - )?; - self.client.send(request, None).await?.file - } else { - let request = get_content::v3::Request::from_url(uri)?; - self.client.send(request, None).await?.file - } - } - }; - - if use_cache { - self.client.store().add_media_content(request, content.clone()).await?; - } - - Ok(content) + return Ok(content); } + + let content: Vec = match &request.source { + MediaSource::Encrypted(file) => { + let request = get_content::v3::Request::from_url(&file.url)?; + let content: Vec = self.client.send(request, None).await?.file; + + #[cfg(feature = "e2e-encryption")] + let content = { + let mut cursor = std::io::Cursor::new(content); + let mut reader = matrix_sdk_base::crypto::AttachmentDecryptor::new( + &mut cursor, + file.as_ref().clone().into(), + )?; + + let mut decrypted = Vec::new(); + reader.read_to_end(&mut decrypted)?; + + decrypted + }; + + content + } + MediaSource::Plain(uri) => { + if let MediaFormat::Thumbnail(size) = &request.format { + let request = + get_content_thumbnail::v3::Request::from_url(uri, size.width, size.height)?; + self.client.send(request, None).await?.file + } else { + let request = get_content::v3::Request::from_url(uri)?; + self.client.send(request, None).await?.file + } + } + }; + + if use_cache { + self.client.store().add_media_content(request, content.clone()).await?; + } + + Ok(content) } /// Remove a media file's content from the store. @@ -200,17 +197,11 @@ impl Media { event_content: impl MediaEventContent, use_cache: bool, ) -> Result>> { - if let Some(source) = event_content.source() { - Ok(Some( - self.get_media_content( - &MediaRequest { source, format: MediaFormat::File }, - use_cache, - ) - .await?, - )) - } else { - Ok(None) - } + let Some(source) = event_content.source() else { return Ok(None) }; + let file = self + .get_media_content(&MediaRequest { source, format: MediaFormat::File }, use_cache) + .await?; + Ok(Some(file)) } /// Remove the file of the given media event content from the cache. @@ -223,7 +214,7 @@ impl Media { /// * `event_content` - The media event content. pub async fn remove_file(&self, event_content: impl MediaEventContent) -> Result<()> { if let Some(source) = event_content.source() { - self.remove_media_content(&MediaRequest { source, format: MediaFormat::File }).await? + self.remove_media_content(&MediaRequest { source, format: MediaFormat::File }).await?; } Ok(()) @@ -253,17 +244,14 @@ impl Media { size: MediaThumbnailSize, use_cache: bool, ) -> Result>> { - if let Some(source) = event_content.thumbnail_source() { - Ok(Some( - self.get_media_content( - &MediaRequest { source, format: MediaFormat::Thumbnail(size) }, - use_cache, - ) - .await?, - )) - } else { - Ok(None) - } + let Some(source) = event_content.thumbnail_source() else { return Ok(None) }; + let thumbnail = self + .get_media_content( + &MediaRequest { source, format: MediaFormat::Thumbnail(size) }, + use_cache, + ) + .await?; + Ok(Some(thumbnail)) } /// Remove the thumbnail of the given media event content from the cache. diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index 2dd9aa9c0..cb3900b0a 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -160,12 +160,9 @@ impl Common { /// # }) /// ``` pub async fn avatar(&self, format: MediaFormat) -> Result>> { - if let Some(url) = self.avatar_url() { - let request = MediaRequest { source: MediaSource::Plain(url.to_owned()), format }; - Ok(Some(self.client.media().get_media_content(&request, true).await?)) - } else { - Ok(None) - } + let Some(url) = self.avatar_url() else { return Ok(None) }; + let request = MediaRequest { source: MediaSource::Plain(url.to_owned()), format }; + Ok(Some(self.client.media().get_media_content(&request, true).await?)) } /// Sends a request to `/_matrix/client/r0/rooms/{room_id}/messages` and diff --git a/crates/matrix-sdk/src/room/member.rs b/crates/matrix-sdk/src/room/member.rs index 06df2a288..3585e51dd 100644 --- a/crates/matrix-sdk/src/room/member.rs +++ b/crates/matrix-sdk/src/room/member.rs @@ -60,11 +60,8 @@ impl RoomMember { /// # }) /// ``` pub async fn avatar(&self, format: MediaFormat) -> Result>> { - if let Some(url) = self.avatar_url() { - let request = MediaRequest { source: MediaSource::Plain(url.to_owned()), format }; - Ok(Some(self.client.media().get_media_content(&request, true).await?)) - } else { - Ok(None) - } + let Some(url) = self.avatar_url() else { return Ok(None) }; + let request = MediaRequest { source: MediaSource::Plain(url.to_owned()), format }; + Ok(Some(self.client.media().get_media_content(&request, true).await?)) } } diff --git a/crates/matrix-sdk/src/sliding_sync.rs b/crates/matrix-sdk/src/sliding_sync.rs index 6774ad4ff..d469d01ff 100644 --- a/crates/matrix-sdk/src/sliding_sync.rs +++ b/crates/matrix-sdk/src/sliding_sync.rs @@ -509,7 +509,7 @@ impl SlidingSync { /// Run this stream to receive new updates from the server. pub async fn stream<'a>( &self, - ) -> Result> + '_, crate::Error> { + ) -> Result> + '_> { let views = self.views.lock_ref().to_vec(); let extensions = self.extensions.clone(); let client = self.client.clone(); @@ -525,7 +525,7 @@ impl SlidingSync { let mut new_remaining_generators = Vec::new(); let mut new_remaining_views = Vec::new(); - for (mut generator, view) in std::iter::zip(remaining_generators, remaining_views) { + for (mut generator, view) in std::iter::zip(remaining_generators, remaining_views) { if let Some(request) = generator.next() { requests.push(request); new_remaining_generators.push(generator); From e0606063314f6119d9087b7735602d77f195de43 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 11:02:44 +0100 Subject: [PATCH 33/78] refactor(crypto): Move some code into a new method --- crates/matrix-sdk-crypto/src/backups/mod.rs | 77 +++++++++++---------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index f8bd1094d..2dfedf053 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -30,8 +30,8 @@ use std::{ use matrix_sdk_common::locks::RwLock; use ruma::{ - api::client::backup::RoomKeyBackup, serde::Raw, DeviceKeyAlgorithm, OwnedDeviceId, OwnedRoomId, - OwnedTransactionId, TransactionId, + api::client::backup::RoomKeyBackup, serde::Raw, DeviceId, DeviceKeyAlgorithm, OwnedDeviceId, + OwnedRoomId, OwnedTransactionId, TransactionId, }; use tracing::{debug, info, instrument, trace, warn}; @@ -241,43 +241,28 @@ impl BackupMachine { if let Some(user_signatures) = signatures.get(self.account.user_id()) { for device_key_id in user_signatures.keys() { - if device_key_id.algorithm() != DeviceKeyAlgorithm::Ed25519 { - continue; - } + if device_key_id.algorithm() == DeviceKeyAlgorithm::Ed25519 { + // No need to check our own device here, we're doing that using + // the check_own_device_signature(). + if device_key_id.device_id() == self.account.device_id() { + continue; + } - // No need to check our own device here, we're doing that using - // the check_own_device_signature(). - if device_key_id.device_id() == self.account.device_id() { - continue; - } + let state = self + .test_ed25519_device_signature( + device_key_id.device_id(), + signatures, + auth_data, + ) + .await?; - // We might iterate over some non-device signatures as well, - // but in this case there's no corresponding device and we get - // `Ok(None)` here, so things still work out. - let device = - self.store.get_device(self.store.user_id(), device_key_id.device_id()).await?; + result.insert(device_key_id.device_id().to_owned(), state); - trace!( - device_id = %device_key_id.device_id(), - "Checking backup auth data for device" - ); - - let state = if let Some(device) = device { - self.backup_signed_by_device(device, signatures, auth_data) - } else { - trace!( - device_id = %device_key_id.device_id(), - "Device not found, can't check signature" - ); - SignatureState::Missing - }; - - result.insert(device_key_id.device_id().to_owned(), state); - - // Abort the loop if we found a trusted and valid signature, - // unless we should check all of them. - if state.trusted() && !compute_all_signatures { - break; + // Abort the loop if we found a trusted and valid signature, + // unless we should check all of them. + if state.trusted() && !compute_all_signatures { + break; + } } } } @@ -285,6 +270,26 @@ impl BackupMachine { Ok(result) } + async fn test_ed25519_device_signature( + &self, + device_id: &DeviceId, + signatures: &Signatures, + auth_data: &str, + ) -> Result { + // We might iterate over some non-device signatures as well, but in this + // case there's no corresponding device and we get `Ok(None)` here, so + // things still work out. + let device = self.store.get_device(self.store.user_id(), device_id).await?; + trace!(%device_id, "Checking backup auth data for device"); + + if let Some(device) = device { + Ok(self.backup_signed_by_device(device, signatures, auth_data)) + } else { + trace!(%device_id, "Device not found, can't check signature"); + Ok(SignatureState::Missing) + } + } + async fn verify_auth_data_v1( &self, auth_data: MegolmV1AuthData, From a7dd6901896e26329dd86992fc1a6ab85396958b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 15 Nov 2022 14:20:44 +0100 Subject: [PATCH 34/78] refactor(crypto): Split out the room key forwarding logic into a separate method --- .../src/gossiping/machine.rs | 97 +++++++++++-------- 1 file changed, 57 insertions(+), 40 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/gossiping/machine.rs b/crates/matrix-sdk-crypto/src/gossiping/machine.rs index 19409cf1d..9897b5060 100644 --- a/crates/matrix-sdk-crypto/src/gossiping/machine.rs +++ b/crates/matrix-sdk-crypto/src/gossiping/machine.rs @@ -305,50 +305,20 @@ impl GossipMachine { }) } - /// Answer a room key request after we found the matching - /// `InboundGroupSession`. - async fn answer_room_key_request( + /// Try to encrypt the given `InboundGroupSession` for the given `Device` as + /// a forwarded room key. + /// + /// This method might fail if we do not share an 1-to-1 Olm session with the + /// given `Device`, in that case we're going to queue up an + /// `/keys/claim` request to be sent out and retry once the 1-to-1 Olm + /// session has been established. + async fn try_to_forward_room_key( &self, event: &RoomKeyRequestEvent, + device: Device, session: InboundGroupSession, + message_index: Option, ) -> OlmResult> { - let device = - self.store.get_device(&event.sender, &event.content.requesting_device_id).await?; - - let Some(device) = device else { - warn!( - user_id = %event.sender, - device_id = %event.content.requesting_device_id, - "Received a key request from an unknown device", - ); - self.store.update_tracked_user(&event.sender, true).await?; - - return Ok(None); - }; - - let message_index = match self.should_share_key(&device, &session).await { - Ok(message_index) => message_index, - Err(e) => { - if let KeyForwardDecision::ChangedSenderKey = e { - warn!( - user_id = device.user_id().as_str(), - device_id = device.device_id().as_str(), - "Received a key request from a device that changed \ - their Curve25519 sender key" - ); - } else { - debug!( - user_id = device.user_id().as_str(), - device_id = device.device_id().as_str(), - reason = ?e, - "Received a key request that we won't serve", - ); - } - - return Ok(None); - } - }; - info!( user_id = %device.user_id(), device_id = %device.device_id(), @@ -386,6 +356,53 @@ impl GossipMachine { } } + /// Answer a room key request after we found the matching + /// `InboundGroupSession`. + async fn answer_room_key_request( + &self, + event: &RoomKeyRequestEvent, + session: InboundGroupSession, + ) -> OlmResult> { + let device = + self.store.get_device(&event.sender, &event.content.requesting_device_id).await?; + + let Some(device) = device else { + warn!( + user_id = %event.sender, + device_id = %event.content.requesting_device_id, + "Received a key request from an unknown device", + ); + self.store.update_tracked_user(&event.sender, true).await?; + + return Ok(None); + }; + + match self.should_share_key(&device, &session).await { + Ok(message_index) => { + self.try_to_forward_room_key(event, device, session, message_index).await + } + Err(e) => { + if let KeyForwardDecision::ChangedSenderKey = e { + warn!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + "Received a key request from a device that changed \ + their Curve25519 sender key" + ); + } else { + debug!( + user_id = device.user_id().as_str(), + device_id = device.device_id().as_str(), + reason = ?e, + "Received a key request that we won't serve", + ); + } + + Ok(None) + } + } + } + async fn handle_supported_key_request( &self, event: &RoomKeyRequestEvent, From bb6cf83a39e50017c69da2c3d653028d9ffa4fe6 Mon Sep 17 00:00:00 2001 From: Johannes Becker <66059836+johannescpk@users.noreply.github.com> Date: Tue, 15 Nov 2022 15:57:31 +0100 Subject: [PATCH 35/78] refactor(appservice)!: Rename virtual user to appservice user --- crates/matrix-sdk-appservice/src/lib.rs | 108 +++++++++--------- .../src/{virtual_user.rs => user.rs} | 26 ++--- examples/appservice_autojoin/src/main.rs | 10 +- 3 files changed, 70 insertions(+), 74 deletions(-) rename crates/matrix-sdk-appservice/src/{virtual_user.rs => user.rs} (87%) diff --git a/crates/matrix-sdk-appservice/src/lib.rs b/crates/matrix-sdk-appservice/src/lib.rs index d83c2de0d..0b7ffe767 100644 --- a/crates/matrix-sdk-appservice/src/lib.rs +++ b/crates/matrix-sdk-appservice/src/lib.rs @@ -20,8 +20,7 @@ //! - [x] ship with functionality to configure your webserver crate or simply //! run the webserver for you //! - [x] receive and validate requests from the homeserver correctly -//! - [x] allow calling the homeserver with proper virtual user identity -//! assertion +//! - [x] allow calling the homeserver with proper user identity assertion //! - [x] have consistent room state by leveraging matrix-sdk's state store //! - [ ] provide E2EE support by leveraging matrix-sdk's crypto store //! @@ -34,7 +33,7 @@ //! //! The crate relies on the appservice registration being always in sync with //! the actual registration used by the homeserver. That's because it's required -//! for the access tokens and because membership states for virtual users are +//! for the access tokens and because membership states for appservice users are //! determined based on the registered namespaces. //! //! # Quickstart @@ -60,7 +59,7 @@ //! ) //! .build() //! .await?; -//! appservice.virtual_user(None).await?.add_event_handler( +//! appservice.user(None).await?.add_event_handler( //! |_ev: SyncRoomMemberEvent| async { //! // do stuff //! }, @@ -109,12 +108,12 @@ use tracing::{debug, info, warn}; mod error; pub mod event_handler; pub mod registration; -pub mod virtual_user; +pub mod user; mod webserver; pub use registration::AppServiceRegistration; use registration::NamespaceCache; -pub use virtual_user::VirtualUserBuilder; +pub use user::UserBuilder; pub use webserver::AppServiceRouter; pub type Result = std::result::Result; @@ -172,13 +171,13 @@ impl AppServiceBuilder { } } - /// Set the client builder to use for the virtual user. + /// Set the client builder to use for the appservice user. pub fn client_builder(mut self, client_builder: ClientBuilder) -> Self { self.client_builder = Some(client_builder); self } - /// Set the default `[RequestConfig]` to use for virtual users. + /// Set the default `[RequestConfig]` to use for appservice users. pub fn default_request_config(mut self, default_request_config: RequestConfig) -> Self { self.default_request_config = Some(default_request_config); self @@ -186,10 +185,10 @@ impl AppServiceBuilder { /// Build the AppService. /// - /// This will also construct a [`virtual_user()`][AppService::virtual_user] - /// for the `sender_localpart` of the given registration. This virtual + /// This will also construct an appservice [`user()`][AppService::user] + /// for the `sender_localpart` of the given registration. This /// user can be used to register an event handler for all incoming - /// events. Other virtual users only receive events if they're known to + /// events. Other appservice users only receive events if they're known to /// be a member of a room. pub async fn build(self) -> Result { let homeserver_url = self.homeserver_url; @@ -212,12 +211,12 @@ impl AppServiceBuilder { }; if let Some(client_builder) = self.client_builder { appservice - .virtual_user_builder(&sender_localpart) + .user_builder(&sender_localpart) .client_builder(client_builder) .build() .await?; } else { - appservice.virtual_user_builder(&sender_localpart).build().await?; + appservice.user_builder(&sender_localpart).build().await?; } Ok(appservice) } @@ -233,7 +232,7 @@ impl AppService { AppServiceBuilder::new(homeserver_url, server_name, registration) } - /// Create a virtual user client. + /// Create an appservice user client. /// /// Will create and return a client that's configured to [assert the /// identity] on outgoing homeserver requests that need authentication. @@ -242,50 +241,50 @@ impl AppService { /// based on the `localpart`. The cached client can be retrieved by calling /// this method again. /// - /// Note that if you want to do actions like joining rooms with a virtual + /// Note that if you want to do actions like joining rooms with a /// user it needs to be registered first. - /// [`register_virtual_user()`][Self::register_virtual_user] can be used + /// [`register_user()`][Self::register_user] can be used /// for that purpose. /// /// # Arguments /// - /// * `localpart` - Used for constructing the virtual user accordingly. If - /// `None` is given it uses the `sender_localpart` from the registration. + /// * `localpart` - Used for constructing the user accordingly. If `None` is + /// given it uses the `sender_localpart` from the registration. /// /// [registration]: https://matrix.org/docs/spec/application_service/r0.1.2#registration /// [assert the identity]: https://matrix.org/docs/spec/application_service/r0.1.2#identity-assertion - pub async fn virtual_user(&self, localpart: Option<&str>) -> Result { + pub async fn user(&self, localpart: Option<&str>) -> Result { let localpart = localpart.unwrap_or_else(|| self.registration.sender_localpart.as_ref()); let builder = match self.default_request_config { Some(config) => self - .virtual_user_builder(localpart) + .user_builder(localpart) .client_builder(Client::builder().request_config(config)), - None => self.virtual_user_builder(localpart), + None => self.user_builder(localpart), }; builder.build().await } - /// Same as [`virtual_user()`][Self::virtual_user] but with + /// Same as [`user()`][Self::user] but with /// the ability to pass in a [`ClientBuilder`]. /// /// Since this method is a singleton follow-up calls with different /// [`ClientBuilder`]s will be ignored. - pub async fn virtual_user_with_client_builder( + pub async fn user_with_client_builder( &self, localpart: Option<&str>, builder: ClientBuilder, ) -> Result { let localpart = localpart.unwrap_or_else(|| self.registration.sender_localpart.as_ref()); - self.virtual_user_builder(localpart).client_builder(builder).build().await + self.user_builder(localpart).client_builder(builder).build().await } - /// Create a new virtual user builder for the given `localpart`. - pub fn virtual_user_builder<'a>(&'a self, localpart: &'a str) -> VirtualUserBuilder<'a> { - VirtualUserBuilder::new(self, localpart) + /// Create a new appservice user builder for the given `localpart`. + pub fn user_builder<'a>(&'a self, localpart: &'a str) -> UserBuilder<'a> { + UserBuilder::new(self, localpart) } - /// Get the map containing all constructed virtual user clients. - pub fn virtual_users(&self) -> Arc> { + /// Get the map containing all constructed appservice user clients. + pub fn users(&self) -> Arc> { self.clients.clone() } @@ -337,8 +336,8 @@ impl AppService { *self.event_handler.rooms.lock().await = Some(handler); } - /// Register a virtual user by sending a [`register::v3::Request`] to the - /// homeserver. + /// Register an appservice user by sending a [`register::v3::Request`] to + /// the homeserver. /// /// # Arguments /// @@ -348,7 +347,7 @@ impl AppService { /// # Returns /// This function may return a UIAA response, which should be checked for /// with [`Error::as_uiaa_response()`]. - pub async fn register_virtual_user<'a>( + pub async fn register_user<'a>( &self, localpart: &'a str, device_id: Option<&'a DeviceId>, @@ -362,7 +361,7 @@ impl AppService { device_id, }); - let client = self.virtual_user(None).await?; + let client = self.user(None).await?; client.register(request).await?; self.set_user_registered(localpart).await?; @@ -371,7 +370,7 @@ impl AppService { /// Add the given localpart to the database of registered localparts. async fn set_user_registered(&self, localpart: impl AsRef) -> Result<()> { - let client = self.virtual_user(None).await?; + let client = self.user(None).await?; client .store() .set_custom_value( @@ -384,7 +383,7 @@ impl AppService { /// Get whether a localpart is listed in the database as registered. async fn is_user_registered(&self, localpart: impl AsRef) -> Result { - let client = self.virtual_user(None).await?; + let client = self.user(None).await?; let key = [USER_KEY, localpart.as_ref().as_bytes()].concat(); let store = client.store().get_custom_value(&key).await?; let registered = @@ -423,14 +422,14 @@ impl AppService { } /// Receive an incoming [transaction], pushing the contained events to - /// active virtual clients. + /// active clients. /// /// [transaction]: https://spec.matrix.org/v1.2/application-service-api/#put_matrixappv1transactionstxnid async fn receive_transaction( &self, transaction: push_events::v1::IncomingRequest, ) -> Result<()> { - let sender_localpart_client = self.virtual_user(None).await?; + let sender_localpart_client = self.user(None).await?; // Find membership events affecting members in our namespace, and update // membership accordingly @@ -461,18 +460,18 @@ impl AppService { // Spawn a task for each client that constructs and pushes a sync event let mut tasks: Vec> = Vec::new(); let transaction = Arc::new(transaction); - for virtual_user_client in self.clients.iter() { + for user_client in self.clients.iter() { let client = sender_localpart_client.clone(); - let virtual_user_client = virtual_user_client.clone(); + let user_client = user_client.clone(); let transaction = transaction.clone(); let sender_localpart = self.registration.sender_localpart.clone(); let task = tokio::spawn(async move { - let Some(user_id) = virtual_user_client.user_id() else { + let Some(user_id) = user_client.user_id() else { // The client is not logged in, skipping return Ok(()); }; - let virtual_user_localpart = user_id.localpart(); + let user_localpart = user_id.localpart(); let mut response = sync_events::v3::Response::new(transaction.txn_id.to_string()); // Clients expect events to be grouped per room, where the @@ -487,15 +486,12 @@ impl AppService { warn!("Transaction contained event with no ID"); continue; }; - let key = - &[USER_MEMBER, room_id.as_bytes(), b".", virtual_user_localpart.as_bytes()] - .concat(); + let key = &[USER_MEMBER, room_id.as_bytes(), b".", user_localpart.as_bytes()] + .concat(); let membership = match client.store().get_custom_value(key).await? { Some(value) => String::from_utf8(value).ok().map(MembershipState::from), // Assume the `sender_localpart` user is in every known room - None if virtual_user_localpart == sender_localpart => { - Some(MembershipState::Join) - } + None if user_localpart == sender_localpart => Some(MembershipState::Join), None => None, }; @@ -515,10 +511,10 @@ impl AppService { response.rooms.invite.entry(room_id).or_default(); } Some(unknown) => debug!("Unknown membership type: {unknown}"), - None => debug!("Assuming {virtual_user_localpart} is not in {room_id}"), + None => debug!("Assuming {user_localpart} is not in {room_id}"), } } - virtual_user_client.receive_transaction(&transaction.txn_id, response).await?; + user_client.receive_transaction(&transaction.txn_id, response).await?; Ok::<_, Error>(()) }); @@ -614,7 +610,7 @@ mod tests { } #[async_test] - async fn test_register_virtual_user() -> Result<()> { + async fn test_register_user() -> Result<()> { let server = MockServer::start().await; let appservice = appservice(Some(server.uri()), None).await?; @@ -637,7 +633,7 @@ mod tests { .mount(&server) .await; - appservice.register_virtual_user(localpart, None).await?; + appservice.register_user(localpart, None).await?; Ok(()) } @@ -680,7 +676,7 @@ mod tests { #[allow(clippy::mutex_atomic)] let on_state_member = Arc::new(Mutex::new(false)); - appservice.virtual_user(None).await?.add_event_handler({ + appservice.user(None).await?.add_event_handler({ let on_state_member = on_state_member.clone(); move |_ev: OriginalSyncRoomMemberEvent| { *on_state_member.lock().unwrap() = true; @@ -832,7 +828,7 @@ mod tests { #[allow(clippy::mutex_atomic)] let on_state_member = Arc::new(Mutex::new(false)); - appservice.virtual_user(None).await?.add_event_handler({ + appservice.user(None).await?.add_event_handler({ let on_state_member = on_state_member.clone(); move |_ev: OriginalSyncRoomMemberEvent| { *on_state_member.lock().unwrap() = true; @@ -901,7 +897,7 @@ mod tests { .unwrap(); let members = appservice - .virtual_user(None) + .user(None) .await? .get_room(room_id) .expect("Expected room to be available") @@ -992,8 +988,8 @@ mod tests { ]; let appservice = appservice(None, None).await?; - let alice = appservice.virtual_user(Some("_appservice_alice")).await?; - let bob = appservice.virtual_user(Some("_appservice_bob")).await?; + let alice = appservice.user(Some("_appservice_alice")).await?; + let bob = appservice.user(Some("_appservice_bob")).await?; appservice .receive_transaction(push_events::v1::IncomingRequest::new("dontcare".into(), json)) .await?; diff --git a/crates/matrix-sdk-appservice/src/virtual_user.rs b/crates/matrix-sdk-appservice/src/user.rs similarity index 87% rename from crates/matrix-sdk-appservice/src/virtual_user.rs rename to crates/matrix-sdk-appservice/src/user.rs index c088a8019..2b1eb687e 100644 --- a/crates/matrix-sdk-appservice/src/virtual_user.rs +++ b/crates/matrix-sdk-appservice/src/user.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Virtual users. +//! AppService users. use matrix_sdk::{config::RequestConfig, Client, ClientBuildError, ClientBuilder, Session}; use ruma::{ @@ -23,9 +23,9 @@ use tracing::warn; use crate::{AppService, Result}; -/// Builder for a virtual user +/// Builder for an appservice user #[derive(Debug)] -pub struct VirtualUserBuilder<'a> { +pub struct UserBuilder<'a> { appservice: &'a AppService, localpart: &'a str, device_id: Option, @@ -34,11 +34,11 @@ pub struct VirtualUserBuilder<'a> { restored_session: Option, } -impl<'a> VirtualUserBuilder<'a> { - /// Create a new virtual user builder +impl<'a> UserBuilder<'a> { + /// Create a new appservice user builder /// # Arguments /// - /// * `localpart` - The localpart of the virtual user + /// * `localpart` - The localpart of the appservice user pub fn new(appservice: &'a AppService, localpart: &'a str) -> Self { Self { appservice, @@ -50,21 +50,21 @@ impl<'a> VirtualUserBuilder<'a> { } } - /// Set the device ID of the virtual user + /// Set the device ID of the appservice user pub fn device_id(mut self, device_id: Option) -> Self { self.device_id = device_id; self } - /// Sets the client builder to use for the virtual user + /// Sets the client builder to use for the appservice user pub fn client_builder(mut self, client_builder: ClientBuilder) -> Self { self.client_builder = client_builder; self } - /// Log in as the virtual user + /// Log in as the appservice user /// - /// In some cases it is necessary to log in as the virtual user, such as to + /// In some cases it is necessary to log in as the user, such as to /// upload device keys pub fn login(mut self) -> Self { self.log_in = true; @@ -74,14 +74,14 @@ impl<'a> VirtualUserBuilder<'a> { /// Restore a persisted session /// /// This is primarily useful if you enable - /// [`VirtualUserBuilder::login()`] and want to restore a session + /// [`UserBuilder::login()`] and want to restore a session /// from a previous run. pub fn restored_session(mut self, session: Session) -> Self { self.restored_session = Some(session); self } - /// Build the virtual user + /// Build the appservice user /// /// # Errors /// This function returns an error if an invalid localpart is provided. @@ -94,7 +94,7 @@ impl<'a> VirtualUserBuilder<'a> { if !(self.appservice.user_id_is_in_namespace(&user_id) || self.localpart == self.appservice.registration.sender_localpart) { - warn!("Virtual client id '{user_id}' is not in the namespace") + warn!("Client id '{user_id}' is not in the namespace") } let mut builder = self.client_builder; diff --git a/examples/appservice_autojoin/src/main.rs b/examples/appservice_autojoin/src/main.rs index eebb3efe5..798225ac5 100644 --- a/examples/appservice_autojoin/src/main.rs +++ b/examples/appservice_autojoin/src/main.rs @@ -23,11 +23,11 @@ pub async fn handle_room_member( trace!("not an appservice user: {}", event.state_key); } else if let MembershipState::Invite = event.content.membership { let user_id = UserId::parse(event.state_key.as_str())?; - if let Err(error) = appservice.register_virtual_user(user_id.localpart(), None).await { + if let Err(error) = appservice.register_user(user_id.localpart(), None).await { error_if_user_not_in_use(error)?; } - let client = appservice.virtual_user(Some(user_id.localpart())).await?; + let client = appservice.user(Some(user_id.localpart())).await?; client.join_room_by_id(room.room_id()).await?; } @@ -64,10 +64,10 @@ pub async fn main() -> anyhow::Result<()> { appservice.register_user_query(Box::new(|_, _| Box::pin(async { true }))).await; - let virtual_user = appservice.virtual_user(None).await?; + let user = appservice.user(None).await?; - virtual_user.add_event_handler_context(appservice.clone()); - virtual_user.add_event_handler( + user.add_event_handler_context(appservice.clone()); + user.add_event_handler( move |event: OriginalSyncRoomMemberEvent, room: Room, Ctx(appservice): Ctx| { handle_room_member(appservice, room, event) }, From 2dd2763d39023e8568a1e5c7f74ecf985a68babc Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 15:01:59 +0100 Subject: [PATCH 36/78] refactor(base): Unify identical branches and erase empty branches --- crates/matrix-sdk-base/src/client.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index c06dc335d..d2467d9b9 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -591,9 +591,7 @@ impl BaseClient { /// Update the internal and cached state accordingly. Return the final Room. pub async fn room_joined(&self, room_id: &RoomId) -> Result { let room = self.store.get_or_create_room(room_id, RoomType::Joined).await; - if room.room_type() == RoomType::Joined { - Ok(room) - } else { + if room.room_type() != RoomType::Joined { let _sync_lock = self.sync_lock().read().await; let mut room_info = room.clone_info(); @@ -604,9 +602,9 @@ impl BaseClient { changes.add_room(room_info.clone()); self.store.save_changes(&changes).await?; // Update the store room.update_summary(room_info); // Update the cached room handle - - Ok(room) } + + Ok(room) } /// User has left a room. @@ -614,9 +612,7 @@ impl BaseClient { /// Update the internal and cached state accordingly. Return the final Room. pub async fn room_left(&self, room_id: &RoomId) -> Result { let room = self.store.get_or_create_room(room_id, RoomType::Left).await; - if room.room_type() == RoomType::Left { - Ok(room) - } else { + if room.room_type() != RoomType::Left { let _sync_lock = self.sync_lock().read().await; let mut room_info = room.clone_info(); @@ -627,9 +623,9 @@ impl BaseClient { changes.add_room(room_info.clone()); self.store.save_changes(&changes).await?; // Update the store room.update_summary(room_info); // Update the cached room handle - - Ok(room) } + + Ok(room) } /// Get access to the store's sync lock. From f9d2d323371d1703d8643369f30604ab4a6463ba Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 15:22:29 +0100 Subject: [PATCH 37/78] fix(xtask): Fix clippy warning --- xtask/src/swift.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/xtask/src/swift.rs b/xtask/src/swift.rs index 2b95c2dea..e65e59ab0 100644 --- a/xtask/src/swift.rs +++ b/xtask/src/swift.rs @@ -1,6 +1,6 @@ use std::{ fs::{create_dir_all, remove_dir_all, remove_file, rename}, - path::PathBuf, + path::{Path, PathBuf}, }; use clap::{Args, Subcommand}; @@ -86,21 +86,21 @@ fn build_library() -> Result<()> { Ok(()) } -fn generate_uniffi(library_file: &PathBuf, ffi_directory: &PathBuf) -> Result<()> { +fn generate_uniffi(library_file: &Path, ffi_directory: &Path) -> Result<()> { let root_directory = workspace::root_path()?; let udl_file = camino::Utf8PathBuf::from_path_buf( root_directory.join("bindings/matrix-sdk-ffi/src/api.udl"), ) .unwrap(); - let outdir_overwrite = camino::Utf8PathBuf::from_path_buf(ffi_directory.clone()).unwrap(); - let library_path = camino::Utf8PathBuf::from_path_buf(library_file.clone()).unwrap(); + let outdir_overwrite = camino::Utf8Path::from_path(ffi_directory).unwrap(); + let library_path = camino::Utf8Path::from_path(library_file).unwrap(); uniffi_bindgen::generate_bindings( udl_file.as_path(), None, vec!["swift"], - Some(outdir_overwrite.as_path()), - Some(library_path.as_path()), + Some(outdir_overwrite), + Some(library_path), false, )?; Ok(()) From 5fa6f34e416e45c93ff6e6516013fe9fd8dcd890 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 15:25:46 +0100 Subject: [PATCH 38/78] refactor(sdk): Use ErrorKind for get_state_events_for_key response check --- crates/matrix-sdk/src/error.rs | 29 ---------------------------- crates/matrix-sdk/src/room/common.rs | 3 ++- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index 3355e6372..dea936e9e 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -76,18 +76,6 @@ impl RumaApiError { _ => None, } } - - /// Return whether the error was a 404 not found response. - pub fn is_not_found(&self) -> bool { - match self { - RumaApiError::ClientApi(err) => err.status_code == StatusCode::NOT_FOUND, - RumaApiError::Uiaa(UiaaResponse::MatrixError(err)) => { - err.status_code == StatusCode::NOT_FOUND - } - RumaApiError::Uiaa(_) => false, - RumaApiError::Other(err) => err.status_code == StatusCode::NOT_FOUND, - } - } } /// An HTTP error, representing either a connection error or an error while @@ -174,23 +162,6 @@ impl HttpError { _ => None, } } - - /// Return whether the error was a 404 not found response. - pub fn is_not_found(&self) -> bool { - match self { - HttpError::Reqwest(err) => err.status() == Some(StatusCode::NOT_FOUND), - HttpError::AuthenticationRequired => false, - HttpError::NotClientRequest => false, - HttpError::Api(FromHttpResponseError::Server(err)) => { - err.is_not_found() - } - HttpError::Api(_) => false, - HttpError::IntoHttp(_) => false, - HttpError::Server(status) => *status == StatusCode::NOT_FOUND, - HttpError::UnableToCloneRequest => false, - HttpError::RefreshToken(_) => false, - } - } } /// Internal representation of errors. diff --git a/crates/matrix-sdk/src/room/common.rs b/crates/matrix-sdk/src/room/common.rs index cb3900b0a..34bbf1c28 100644 --- a/crates/matrix-sdk/src/room/common.rs +++ b/crates/matrix-sdk/src/room/common.rs @@ -14,6 +14,7 @@ use ruma::events::{ use ruma::{ api::client::{ config::set_global_account_data, + error::ErrorKind, filter::RoomEventFilter, membership::{get_member_events, join_room_by_id, leave_room}, message::get_message_events, @@ -353,7 +354,7 @@ impl Common { Ok(response) => { Some(response.content.deserialize_as::()?) } - Err(err) if err.is_not_found() => None, + Err(err) if err.client_api_error_kind() == Some(&ErrorKind::NotFound) => None, Err(err) => return Err(err.into()), }; From 51798e90bdf880ba5a764a9ec19d526843d78bb8 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 17:10:29 +0100 Subject: [PATCH 39/78] chore: Add dbg profile and improve profile comments --- Cargo.toml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b01c7354d..1297870b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,13 +29,14 @@ uniffi_build = { git = "https://github.com/mozilla/uniffi-rs", rev = "779e955f21 vodozemac = "0.3.0" zeroize = "1.3.0" +# Default release profile, select with `--release` [profile.release] lto = true +# Default development profile; default for most Cargo commands, otherwise +# selected with `--debug` [profile.dev] -# Copied from rust-analyzer. Saves a lot of disk space and hopefully -# compilation time / mem usage too, at the expense of potentially having to -# change this setting here when you want to use a debugger. +# Saves a lot of disk space. If symbols are needed, use the dbg profile. debug = 0 [profile.dev.package] @@ -43,3 +44,8 @@ debug = 0 # for the extra time of optimizing it for a clean build of matrix-sdk-ffi. quote = { opt-level = 2 } sha2 = { opt-level = 2 } + +# Custom profile with full debugging info, use `--profile debug` to select +[profile.dbg] +inherits = "dev" +debug = 2 From c3aa03e48691a77efdec282483d38e707704cd99 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 17:11:01 +0100 Subject: [PATCH 40/78] chore: Add reldbg profile and use it for matrix-sdk-ffi iOS builds --- Cargo.toml | 10 +++++ bindings/apple/debug_build_xcframework.sh | 2 +- xtask/src/swift.rs | 45 +++++++++++++---------- 3 files changed, 36 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1297870b1..a5d7e0782 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,3 +49,13 @@ sha2 = { opt-level = 2 } [profile.dbg] inherits = "dev" debug = 2 + +# Custom profile for use in (debug) builds of the binding crates, use +# `--profile release_dbg` to select +[profile.reldbg] +inherits = "dev" +incremental = false + +# Compile all non-workspace crate in the dependency tree with optimizations +[profile.reldbg.package."*"] +opt-level = 3 diff --git a/bindings/apple/debug_build_xcframework.sh b/bindings/apple/debug_build_xcframework.sh index 1c8b89c03..562dabe2d 100755 --- a/bindings/apple/debug_build_xcframework.sh +++ b/bindings/apple/debug_build_xcframework.sh @@ -28,4 +28,4 @@ else fi echo "Active architecture ${ACTIVE_ARCH}" -cargo xtask swift build-framework --sim-only-target=${TARGET} $* +cargo xtask swift build-framework --profile=reldbg --sim-only-target=${TARGET} $* diff --git a/xtask/src/swift.rs b/xtask/src/swift.rs index e65e59ab0..53a470d0d 100644 --- a/xtask/src/swift.rs +++ b/xtask/src/swift.rs @@ -19,14 +19,21 @@ pub struct SwiftArgs { enum SwiftCommand { /// Builds the SDK for Swift as a static lib. BuildLibrary, + /// Builds the SDK for Swift as an XCFramework. BuildFramework { - /// Build in release mode + /// Build with the release profile #[clap(long)] release: bool, + + /// Build with a custom profile, takes precedence over `--release` + #[clap(long)] + profile: Option, + /// Build the given target only #[clap(long)] only_target: Option, + /// Move the generated xcframework and swift sources into the given /// components-folder #[clap(long)] @@ -40,8 +47,9 @@ impl SwiftArgs { match self.cmd { SwiftCommand::BuildLibrary => build_library(), - SwiftCommand::BuildFramework { release, components_path, only_target } => { - build_xcframework(release, only_target, components_path) + SwiftCommand::BuildFramework { release, profile, components_path, only_target } => { + let profile = profile.as_deref().unwrap_or(if release { "release" } else { "dev" }); + build_xcframework(profile, only_target, components_path) } } } @@ -106,20 +114,17 @@ fn generate_uniffi(library_file: &Path, ffi_directory: &Path) -> Result<()> { Ok(()) } -fn build_for_target(target: &str, release: bool) -> Result { - let mut cmd = cmd!("cargo build -p matrix-sdk-ffi --target {target}"); - if release { - cmd = cmd.arg("--release"); - } - cmd.run()?; - Ok(workspace::target_path()? - .join(target) - .join(if release { "release" } else { "debug" }) - .join("libmatrix_sdk_ffi.a")) +fn build_for_target(target: &str, profile: &str) -> Result { + cmd!("cargo build -p matrix-sdk-ffi --target {target} --profile {profile}").run()?; + + // The builtin dev profile has its files stored under target/debug, all + // other targets have matching directory names + let profile_dir_name = if profile == "dev" { "debug" } else { profile }; + Ok(workspace::target_path()?.join(target).join(profile_dir_name).join("libmatrix_sdk_ffi.a")) } fn build_xcframework( - release_mode: bool, + profile: &str, only_target: Option, components_path: Option, ) -> Result<()> { @@ -132,22 +137,22 @@ fn build_xcframework( let (libs, uniff_lib_path) = if let Some(target) = only_target { println!("-- Building for {target} 1/1"); - let build_path = build_for_target(target.as_str(), release_mode)?; + let build_path = build_for_target(target.as_str(), profile)?; (vec![build_path.clone()], build_path) } else { println!("-- Building for iOS [1/5]"); - let ios_path = build_for_target("aarch64-apple-ios", release_mode)?; + let ios_path = build_for_target("aarch64-apple-ios", profile)?; println!("-- Building for macOS (Apple Silicon) [2/5]"); - let darwin_arm_path = build_for_target("aarch64-apple-darwin", release_mode)?; + let darwin_arm_path = build_for_target("aarch64-apple-darwin", profile)?; println!("-- Building for macOS (Intel) [3/5]"); - let darwin_x86_path = build_for_target("x86_64-apple-darwin", release_mode)?; + let darwin_x86_path = build_for_target("x86_64-apple-darwin", profile)?; println!("-- Building for iOS Simulator (Apple Silicon) [4/5]"); - let ios_sim_arm_path = build_for_target("aarch64-apple-ios-sim", release_mode)?; + let ios_sim_arm_path = build_for_target("aarch64-apple-ios-sim", profile)?; println!("-- Building for iOS Simulator (Intel) [5/5]"); - let ios_sim_x86_path = build_for_target("x86_64-apple-ios", release_mode)?; + let ios_sim_x86_path = build_for_target("x86_64-apple-ios", profile)?; println!("-- Running Lipo for MacOs [1/2]"); // # MacOS From 975626a4f8d3e98d2922d2a3cc428ff5fbd6662d Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 16 Nov 2022 13:32:36 +0100 Subject: [PATCH 41/78] refactor(common)!: Remove unused TimelineSlice type --- .../src/deserialized_responses.rs | 33 ------------------- 1 file changed, 33 deletions(-) diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index e573b59dd..629e8aa4a 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -263,39 +263,6 @@ impl Timeline { } } -/// A slice of the timeline in the room. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct TimelineSlice { - /// The `next_batch` or `from` token used to obtain this slice - pub start: String, - - /// The `prev_batch` or `to` token used to obtain this slice - /// If `None` this `TimelineSlice` is the beginning of the room - pub end: Option, - - /// Whether the number of events returned for this slice was limited - /// by a `limit`-filter when requesting - pub limited: bool, - - /// A list of events. - pub events: Vec, - - /// Whether this is a timeline slice obtained from a `SyncResponse` - pub sync: bool, -} - -impl TimelineSlice { - pub fn new( - events: Vec, - start: String, - end: Option, - limited: bool, - sync: bool, - ) -> Self { - Self { start, end, events, limited, sync } - } -} - /// Wrapper around both MemberEvent-Types #[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, Deserialize, Serialize)] From bb6145d581474ce83b89d340a0e7b02481157c85 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Wed, 16 Nov 2022 13:56:51 +0100 Subject: [PATCH 42/78] refactor!: Move large parts of deserialized_responses to matrix-sdk-base --- crates/matrix-sdk-base/src/client.rs | 11 +- .../src/deserialized_responses.rs | 119 +++++++++ crates/matrix-sdk-base/src/lib.rs | 2 + crates/matrix-sdk-base/src/rooms/normal.rs | 2 +- crates/matrix-sdk-base/src/sliding_sync.rs | 5 +- .../src/store/ambiguity_map.rs | 6 +- .../src/store/integration_tests.rs | 4 +- crates/matrix-sdk-base/src/sync.rs | 173 +++++++++++++ .../src/deserialized_responses.rs | 241 +----------------- crates/matrix-sdk/src/client/mod.rs | 4 +- crates/matrix-sdk/src/lib.rs | 2 +- crates/matrix-sdk/src/sliding_sync.rs | 2 +- crates/matrix-sdk/src/sync.rs | 8 +- examples/wasm_command_bot/src/lib.rs | 2 +- 14 files changed, 318 insertions(+), 263 deletions(-) create mode 100644 crates/matrix-sdk-base/src/deserialized_responses.rs create mode 100644 crates/matrix-sdk-base/src/sync.rs diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index d2467d9b9..8d458ffd3 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -22,14 +22,7 @@ use std::{ use std::{ops::Deref, sync::Arc}; use futures_signals::signal::ReadOnlyMutable; -use matrix_sdk_common::{ - deserialized_responses::{ - AmbiguityChanges, JoinedRoom, LeftRoom, MembersResponse, Rooms, SyncResponse, - SyncTimelineEvent, Timeline, - }, - instant::Instant, - locks::RwLock, -}; +use matrix_sdk_common::{instant::Instant, locks::RwLock}; #[cfg(feature = "e2e-encryption")] use matrix_sdk_crypto::{ store::{CryptoStore, MemoryStore as MemoryCryptoStore}, @@ -63,12 +56,14 @@ use tracing::{debug, info, trace, warn}; #[cfg(feature = "e2e-encryption")] use crate::error::Error; use crate::{ + deserialized_responses::{AmbiguityChanges, MembersResponse, SyncTimelineEvent}, error::Result, rooms::{Room, RoomInfo, RoomType}, store::{ ambiguity_map::AmbiguityCache, Result as StoreResult, StateChanges, StateStoreExt, Store, StoreConfig, }, + sync::{JoinedRoom, LeftRoom, Rooms, SyncResponse, Timeline}, Session, SessionMeta, SessionTokens, StateStore, }; diff --git a/crates/matrix-sdk-base/src/deserialized_responses.rs b/crates/matrix-sdk-base/src/deserialized_responses.rs new file mode 100644 index 000000000..f05edd2a9 --- /dev/null +++ b/crates/matrix-sdk-base/src/deserialized_responses.rs @@ -0,0 +1,119 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! SDK-specific variations of response types from Ruma. + +use std::collections::BTreeMap; + +pub use matrix_sdk_common::deserialized_responses::*; +use ruma::{ + events::room::member::{ + MembershipState, RoomMemberEvent, RoomMemberEventContent, StrippedRoomMemberEvent, + SyncRoomMemberEvent, + }, + EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, OwnedUserId, UserId, +}; +use serde::{Deserialize, Serialize}; + +/// A change in ambiguity of room members that an `m.room.member` event +/// triggers. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct AmbiguityChange { + /// Is the member that is contained in the state key of the `m.room.member` + /// event itself ambiguous because of the event. + pub member_ambiguous: bool, + /// Has another user been disambiguated because of this event. + pub disambiguated_member: Option, + /// Has another user become ambiguous because of this event. + pub ambiguated_member: Option, +} + +/// Collection of ambiguioty changes that room member events trigger. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct AmbiguityChanges { + /// A map from room id to a map of an event id to the `AmbiguityChange` that + /// the event with the given id caused. + pub changes: BTreeMap>, +} + +/// A deserialized response for the rooms members API call. +/// +/// [GET /_matrix/client/r0/rooms/{roomId}/members](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-members) +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct MembersResponse { + /// The list of members events. + pub chunk: Vec, + /// Collection of ambiguity changes that room member events trigger. + pub ambiguity_changes: AmbiguityChanges, +} + +/// Wrapper around both MemberEvent-Types +#[allow(clippy::large_enum_variant)] +#[derive(Clone, Debug, Deserialize, Serialize)] +pub enum MemberEvent { + /// A member event from a room in joined or left state. + Sync(SyncRoomMemberEvent), + /// A member event from a room in invited state. + Stripped(StrippedRoomMemberEvent), +} + +impl MemberEvent { + /// The inner Content of the wrapped Event + pub fn original_content(&self) -> Option<&RoomMemberEventContent> { + match self { + MemberEvent::Sync(e) => e.as_original().map(|e| &e.content), + MemberEvent::Stripped(e) => Some(&e.content), + } + } + + /// The sender of this event. + pub fn sender(&self) -> &UserId { + match self { + MemberEvent::Sync(e) => e.sender(), + MemberEvent::Stripped(e) => &e.sender, + } + } + + /// The ID of this event. + pub fn event_id(&self) -> Option<&EventId> { + match self { + MemberEvent::Sync(e) => Some(e.event_id()), + MemberEvent::Stripped(_) => None, + } + } + + /// The Server Timestamp of this event. + pub fn origin_server_ts(&self) -> Option { + match self { + MemberEvent::Sync(e) => Some(e.origin_server_ts()), + MemberEvent::Stripped(_) => None, + } + } + + /// The membership state of the user + pub fn membership(&self) -> &MembershipState { + match self { + MemberEvent::Sync(e) => e.membership(), + MemberEvent::Stripped(e) => &e.content.membership, + } + } + + /// The user id associated to this member event + pub fn user_id(&self) -> &UserId { + match self { + MemberEvent::Sync(e) => e.state_key(), + MemberEvent::Stripped(e) => &e.state_key, + } + } +} diff --git a/crates/matrix-sdk-base/src/lib.rs b/crates/matrix-sdk-base/src/lib.rs index 3861745cd..473c1e517 100644 --- a/crates/matrix-sdk-base/src/lib.rs +++ b/crates/matrix-sdk-base/src/lib.rs @@ -25,6 +25,7 @@ pub use crate::{ }; mod client; +pub mod deserialized_responses; mod error; pub mod media; mod rooms; @@ -32,6 +33,7 @@ mod session; #[cfg(feature = "sliding-sync")] mod sliding_sync; pub mod store; +pub mod sync; mod utils; pub use client::BaseClient; diff --git a/crates/matrix-sdk-base/src/rooms/normal.rs b/crates/matrix-sdk-base/src/rooms/normal.rs index e8ac4e966..19a0279d2 100644 --- a/crates/matrix-sdk-base/src/rooms/normal.rs +++ b/crates/matrix-sdk-base/src/rooms/normal.rs @@ -40,8 +40,8 @@ use tracing::debug; use super::{BaseRoomInfo, DisplayName, RoomMember}; use crate::{ - deserialized_responses::UnreadNotificationsCount, store::{Result as StoreResult, StateStore, StateStoreExt}, + sync::UnreadNotificationsCount, MinimalStateEvent, }; diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index 96794e446..f4297cc50 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -1,18 +1,17 @@ #[cfg(feature = "e2e-encryption")] use std::ops::Deref; -use matrix_sdk_common::deserialized_responses::{ - AmbiguityChanges, JoinedRoom, Rooms, SyncResponse, -}; use ruma::api::client::sync::sync_events::{v3, v4}; #[cfg(feature = "e2e-encryption")] use ruma::UserId; use super::BaseClient; use crate::{ + deserialized_responses::AmbiguityChanges, error::Result, rooms::RoomType, store::{ambiguity_map::AmbiguityCache, StateChanges}, + sync::{JoinedRoom, Rooms, SyncResponse}, }; impl BaseClient { diff --git a/crates/matrix-sdk-base/src/store/ambiguity_map.rs b/crates/matrix-sdk-base/src/store/ambiguity_map.rs index 0455904d6..387b43e4d 100644 --- a/crates/matrix-sdk-base/src/store/ambiguity_map.rs +++ b/crates/matrix-sdk-base/src/store/ambiguity_map.rs @@ -17,7 +17,6 @@ use std::{ sync::Arc, }; -use matrix_sdk_common::deserialized_responses::{AmbiguityChange, MemberEvent}; use ruma::{ events::room::member::{MembershipState, SyncRoomMemberEvent}, OwnedEventId, OwnedRoomId, OwnedUserId, RoomId, UserId, @@ -25,7 +24,10 @@ use ruma::{ use tracing::trace; use super::{Result, StateChanges}; -use crate::StateStore; +use crate::{ + deserialized_responses::{AmbiguityChange, MemberEvent}, + StateStore, +}; #[derive(Debug)] pub(crate) struct AmbiguityCache { diff --git a/crates/matrix-sdk-base/src/store/integration_tests.rs b/crates/matrix-sdk-base/src/store/integration_tests.rs index b72701ae7..a452c74d3 100644 --- a/crates/matrix-sdk-base/src/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/store/integration_tests.rs @@ -703,7 +703,7 @@ macro_rules! statestore_integration_tests { assert!(matches!( store.get_member_event(room_id, user_id).await.unwrap(), - Some(matrix_sdk_common::deserialized_responses::MemberEvent::Sync(_)) + Some($crate::deserialized_responses::MemberEvent::Sync(_)) )); assert_eq!(store.get_room_infos().await.unwrap().len(), 1); assert_eq!(store.get_stripped_room_infos().await.unwrap().len(), 0); @@ -718,7 +718,7 @@ macro_rules! statestore_integration_tests { assert!(matches!( store.get_member_event(room_id, user_id).await.unwrap(), - Some(matrix_sdk_common::deserialized_responses::MemberEvent::Stripped(_)) + Some($crate::deserialized_responses::MemberEvent::Stripped(_)) )); assert_eq!(store.get_room_infos().await.unwrap().len(), 0); assert_eq!(store.get_stripped_room_infos().await.unwrap().len(), 1); diff --git a/crates/matrix-sdk-base/src/sync.rs b/crates/matrix-sdk-base/src/sync.rs new file mode 100644 index 000000000..8cc3277ae --- /dev/null +++ b/crates/matrix-sdk-base/src/sync.rs @@ -0,0 +1,173 @@ +// Copyright 2022 The Matrix.org Foundation C.I.C. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The SDK's representation of the result of a `/sync` request. + +use std::collections::BTreeMap; + +use matrix_sdk_common::deserialized_responses::SyncTimelineEvent; +use ruma::{ + api::client::{ + push::get_notifications::v3::Notification, + sync::sync_events::{ + v3::{Ephemeral, InvitedRoom, Presence, RoomAccountData, State}, + DeviceLists, UnreadNotificationsCount as RumaUnreadNotificationsCount, + }, + }, + events::{AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyToDeviceEvent}, + serde::Raw, + DeviceKeyAlgorithm, OwnedRoomId, +}; +use serde::{Deserialize, Serialize}; + +use crate::deserialized_responses::AmbiguityChanges; + +/// The processed response of a `/sync` request. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct SyncResponse { + /// The batch token to supply in the `since` param of the next `/sync` + /// request. + pub next_batch: String, + /// Updates to rooms. + pub rooms: Rooms, + /// Updates to the presence status of other users. + pub presence: Presence, + /// The global private data created by this user. + pub account_data: Vec>, + /// Messages sent directly between devices. + pub to_device_events: Vec>, + /// Information on E2E device updates. + /// + /// Only present on an incremental sync. + pub device_lists: DeviceLists, + /// For each key algorithm, the number of unclaimed one-time keys + /// currently held on the server for a device. + pub device_one_time_keys_count: BTreeMap, + /// Collection of ambiguity changes that room member events trigger. + pub ambiguity_changes: AmbiguityChanges, + /// New notifications per room. + pub notifications: BTreeMap>, +} + +impl SyncResponse { + /// Creates a new, empty `SyncResponse`. + /// + /// Equivalent to `SyncResponse::default()`. + pub fn new(next_batch: String) -> Self { + Self { next_batch, ..Default::default() } + } +} + +/// Updates to rooms in a [`SyncResponse`]. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct Rooms { + /// The rooms that the user has left or been banned from. + pub leave: BTreeMap, + /// The rooms that the user has joined. + pub join: BTreeMap, + /// The rooms that the user has been invited to. + pub invite: BTreeMap, +} + +/// Updates to joined rooms. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct JoinedRoom { + /// Counts of unread notifications for this room. + pub unread_notifications: UnreadNotificationsCount, + /// The timeline of messages and state changes in the room. + pub timeline: Timeline, + /// Updates to the state, between the time indicated by the `since` + /// parameter, and the start of the `timeline` (or all state up to the + /// start of the `timeline`, if `since` is not given, or `full_state` is + /// true). + pub state: State, + /// The private data that this user has attached to this room. + pub account_data: Vec>, + /// The ephemeral events in the room that aren't recorded in the timeline or + /// state of the room. e.g. typing. + pub ephemeral: Ephemeral, +} + +impl JoinedRoom { + pub(crate) fn new( + timeline: Timeline, + state: State, + account_data: Vec>, + ephemeral: Ephemeral, + unread_notifications: UnreadNotificationsCount, + ) -> Self { + Self { unread_notifications, timeline, state, account_data, ephemeral } + } +} + +/// Counts of unread notifications for a room. +#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)] +pub struct UnreadNotificationsCount { + /// The number of unread notifications for this room with the highlight flag + /// set. + pub highlight_count: u64, + /// The total number of unread notifications for this room. + pub notification_count: u64, +} + +impl From for UnreadNotificationsCount { + fn from(notifications: RumaUnreadNotificationsCount) -> Self { + Self { + highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0), + notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0), + } + } +} + +/// Updates to left rooms. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct LeftRoom { + /// The timeline of messages and state changes in the room up to the point + /// when the user left. + pub timeline: Timeline, + /// Updates to the state, between the time indicated by the `since` + /// parameter, and the start of the `timeline` (or all state up to the + /// start of the `timeline`, if `since` is not given, or `full_state` is + /// true). + pub state: State, + /// The private data that this user has attached to this room. + pub account_data: RoomAccountData, +} + +impl LeftRoom { + pub(crate) fn new(timeline: Timeline, state: State, account_data: RoomAccountData) -> Self { + Self { timeline, state, account_data } + } +} + +/// Events in the room. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct Timeline { + /// True if the number of events returned was limited by the `limit` on the + /// filter. + pub limited: bool, + + /// A token that can be supplied to to the `from` parameter of the + /// `/rooms/{roomId}/messages` endpoint. + pub prev_batch: Option, + + /// A list of events. + pub events: Vec, +} + +impl Timeline { + pub(crate) fn new(limited: bool, prev_batch: Option) -> Self { + Self { limited, prev_batch, ..Default::default() } + } +} diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 629e8aa4a..189c354a2 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -1,48 +1,12 @@ -use std::{borrow::Borrow, collections::BTreeMap}; +use std::collections::BTreeMap; use ruma::{ - api::client::{ - push::get_notifications::v3::Notification, - sync::sync_events::{ - v3::{Ephemeral, InvitedRoom, Presence, RoomAccountData, State}, - DeviceLists, UnreadNotificationsCount as RumaUnreadNotificationsCount, - }, - }, - events::{ - room::member::{ - MembershipState, RoomMemberEvent, RoomMemberEventContent, StrippedRoomMemberEvent, - SyncRoomMemberEvent, - }, - AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncTimelineEvent, AnyTimelineEvent, - AnyToDeviceEvent, - }, + events::{AnySyncTimelineEvent, AnyTimelineEvent}, serde::Raw, - DeviceKeyAlgorithm, EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, - OwnedRoomId, OwnedUserId, UserId, + DeviceKeyAlgorithm, OwnedDeviceId, OwnedEventId, OwnedUserId, }; use serde::{Deserialize, Serialize}; -/// A change in ambiguity of room members that an `m.room.member` event -/// triggers. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct AmbiguityChange { - /// Is the member that is contained in the state key of the `m.room.member` - /// event itself ambiguous because of the event. - pub member_ambiguous: bool, - /// Has another user been disambiguated because of this event. - pub disambiguated_member: Option, - /// Has another user become ambiguous because of this event. - pub ambiguated_member: Option, -} - -/// Collection of ambiguioty changes that room member events trigger. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct AmbiguityChanges { - /// A map from room id to a map of an event id to the `AmbiguityChange` that - /// the event with the given id caused. - pub changes: BTreeMap>, -} - /// The verification state of the device that sent an event to us. #[derive(Clone, Debug, Deserialize, Serialize)] pub enum VerificationState { @@ -121,38 +85,6 @@ impl From for SyncTimelineEvent { } } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct SyncResponse { - /// The batch token to supply in the `since` param of the next `/sync` - /// request. - pub next_batch: String, - /// Updates to rooms. - pub rooms: Rooms, - /// Updates to the presence status of other users. - pub presence: Presence, - /// The global private data created by this user. - pub account_data: Vec>, - /// Messages sent directly between devices. - pub to_device_events: Vec>, - /// Information on E2E device updates. - /// - /// Only present on an incremental sync. - pub device_lists: DeviceLists, - /// For each key algorithm, the number of unclaimed one-time keys - /// currently held on the server for a device. - pub device_one_time_keys_count: BTreeMap, - /// Collection of ambiguity changes that room member events trigger. - pub ambiguity_changes: AmbiguityChanges, - /// New notifications per room. - pub notifications: BTreeMap>, -} - -impl SyncResponse { - pub fn new(next_batch: String) -> Self { - Self { next_batch, ..Default::default() } - } -} - #[derive(Clone, Debug)] pub struct TimelineEvent { /// The actual event. @@ -162,173 +94,6 @@ pub struct TimelineEvent { pub encryption_info: Option, } -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Rooms { - /// The rooms that the user has left or been banned from. - pub leave: BTreeMap, - /// The rooms that the user has joined. - pub join: BTreeMap, - /// The rooms that the user has been invited to. - pub invite: BTreeMap, -} - -/// Updates to joined rooms. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct JoinedRoom { - /// Counts of unread notifications for this room. - pub unread_notifications: UnreadNotificationsCount, - /// The timeline of messages and state changes in the room. - pub timeline: Timeline, - /// Updates to the state, between the time indicated by the `since` - /// parameter, and the start of the `timeline` (or all state up to the - /// start of the `timeline`, if `since` is not given, or `full_state` is - /// true). - pub state: State, - /// The private data that this user has attached to this room. - pub account_data: Vec>, - /// The ephemeral events in the room that aren't recorded in the timeline or - /// state of the room. e.g. typing. - pub ephemeral: Ephemeral, -} - -impl JoinedRoom { - pub fn new( - timeline: Timeline, - state: State, - account_data: Vec>, - ephemeral: Ephemeral, - unread_notifications: UnreadNotificationsCount, - ) -> Self { - Self { unread_notifications, timeline, state, account_data, ephemeral } - } -} - -/// Counts of unread notifications for a room. -#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize)] -pub struct UnreadNotificationsCount { - /// The number of unread notifications for this room with the highlight flag - /// set. - pub highlight_count: u64, - /// The total number of unread notifications for this room. - pub notification_count: u64, -} - -impl From for UnreadNotificationsCount { - fn from(notifications: RumaUnreadNotificationsCount) -> Self { - Self { - highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0), - notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0), - } - } -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct LeftRoom { - /// The timeline of messages and state changes in the room up to the point - /// when the user left. - pub timeline: Timeline, - /// Updates to the state, between the time indicated by the `since` - /// parameter, and the start of the `timeline` (or all state up to the - /// start of the `timeline`, if `since` is not given, or `full_state` is - /// true). - pub state: State, - /// The private data that this user has attached to this room. - pub account_data: RoomAccountData, -} - -impl LeftRoom { - pub fn new(timeline: Timeline, state: State, account_data: RoomAccountData) -> Self { - Self { timeline, state, account_data } - } -} - -/// Events in the room. -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct Timeline { - /// True if the number of events returned was limited by the `limit` on the - /// filter. - pub limited: bool, - - /// A token that can be supplied to to the `from` parameter of the - /// `/rooms/{roomId}/messages` endpoint. - pub prev_batch: Option, - - /// A list of events. - pub events: Vec, -} - -impl Timeline { - pub fn new(limited: bool, prev_batch: Option) -> Self { - Self { limited, prev_batch, ..Default::default() } - } -} - -/// Wrapper around both MemberEvent-Types -#[allow(clippy::large_enum_variant)] -#[derive(Clone, Debug, Deserialize, Serialize)] -pub enum MemberEvent { - Sync(SyncRoomMemberEvent), - Stripped(StrippedRoomMemberEvent), -} - -impl MemberEvent { - /// The inner Content of the wrapped Event - pub fn original_content(&self) -> Option<&RoomMemberEventContent> { - match self { - MemberEvent::Sync(e) => e.as_original().map(|e| &e.content), - MemberEvent::Stripped(e) => Some(&e.content), - } - } - /// The sender of this event. - pub fn sender(&self) -> &UserId { - match self { - MemberEvent::Sync(e) => e.sender(), - MemberEvent::Stripped(e) => e.sender.borrow(), - } - } - /// The ID of this event. - pub fn event_id(&self) -> Option<&EventId> { - match self { - MemberEvent::Sync(e) => Some(e.event_id()), - MemberEvent::Stripped(_) => None, - } - } - /// The Server Timestamp of this event. - pub fn origin_server_ts(&self) -> Option { - match self { - MemberEvent::Sync(e) => Some(e.origin_server_ts()), - MemberEvent::Stripped(_) => None, - } - } - - /// The membership state of the user - pub fn membership(&self) -> &MembershipState { - match self { - MemberEvent::Sync(e) => e.membership(), - MemberEvent::Stripped(e) => &e.content.membership, - } - } - - /// The user id associated to this member event - pub fn user_id(&self) -> &UserId { - match self { - MemberEvent::Sync(e) => e.state_key(), - MemberEvent::Stripped(e) => &e.state_key, - } - } -} - -/// A deserialized response for the rooms members API call. -/// -/// [GET /_matrix/client/r0/rooms/{roomId}/members](https://matrix.org/docs/spec/client_server/r0.6.0#get-matrix-client-r0-rooms-roomid-members) -#[derive(Clone, Debug, Default, Deserialize, Serialize)] -pub struct MembersResponse { - /// The list of members events. - pub chunk: Vec, - /// Collection of ambiguity changes that room member events trigger. - pub ambiguity_changes: AmbiguityChanges, -} - #[cfg(test)] mod tests { use ruma::{ diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 6fc0a5297..d343eb438 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -27,8 +27,8 @@ use dashmap::DashMap; use futures_core::stream::Stream; use futures_signals::signal::Signal; use matrix_sdk_base::{ - deserialized_responses::SyncResponse, BaseClient, RoomType, SendOutsideWasm, Session, - SessionMeta, SessionTokens, StateStore, SyncOutsideWasm, + sync::SyncResponse, BaseClient, RoomType, SendOutsideWasm, Session, SessionMeta, SessionTokens, + StateStore, SyncOutsideWasm, }; use matrix_sdk_common::{ instant::Instant, diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index e0c1a19be..4260ec2a8 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -36,7 +36,7 @@ pub mod event_handler; mod http_client; pub mod media; pub mod room; -mod sync; +pub mod sync; #[cfg(feature = "sliding-sync")] mod sliding_sync; diff --git a/crates/matrix-sdk/src/sliding_sync.rs b/crates/matrix-sdk/src/sliding_sync.rs index d469d01ff..af905370d 100644 --- a/crates/matrix-sdk/src/sliding_sync.rs +++ b/crates/matrix-sdk/src/sliding_sync.rs @@ -17,7 +17,7 @@ use std::{fmt::Debug, sync::Arc}; use futures_core::stream::Stream; use futures_signals::signal::Mutable; -use matrix_sdk_base::deserialized_responses::{SyncResponse, SyncTimelineEvent}; +use matrix_sdk_base::{deserialized_responses::SyncTimelineEvent, sync::SyncResponse}; use ruma::{ api::client::sync::sync_events::v4::{ self, AccountDataConfig, E2EEConfig, ExtensionsConfig, ToDeviceConfig, diff --git a/crates/matrix-sdk/src/sync.rs b/crates/matrix-sdk/src/sync.rs index 41290a4aa..721425000 100644 --- a/crates/matrix-sdk/src/sync.rs +++ b/crates/matrix-sdk/src/sync.rs @@ -1,9 +1,9 @@ +//! The SDK's representation of the result of a `/sync` request. + use std::time::Duration; -use matrix_sdk_base::{ - deserialized_responses::{JoinedRoom, LeftRoom, SyncResponse}, - instant::Instant, -}; +use matrix_sdk_base::instant::Instant; +pub use matrix_sdk_base::sync::*; use ruma::api::client::sync::sync_events; use tracing::{error, warn}; diff --git a/examples/wasm_command_bot/src/lib.rs b/examples/wasm_command_bot/src/lib.rs index 935d82e58..867a2c0f7 100644 --- a/examples/wasm_command_bot/src/lib.rs +++ b/examples/wasm_command_bot/src/lib.rs @@ -1,6 +1,5 @@ use matrix_sdk::{ config::SyncSettings, - deserialized_responses::SyncResponse, ruma::{ events::{ room::message::{MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent}, @@ -9,6 +8,7 @@ use matrix_sdk::{ }, RoomId, }, + sync::SyncResponse, Client, LoopCtrl, }; use url::Url; From 98600e4c1e38bc5b3ad3d400b86ecae1dc658529 Mon Sep 17 00:00:00 2001 From: Benjamin Kampmann Date: Thu, 17 Nov 2022 12:18:27 +0100 Subject: [PATCH 43/78] feat(jack-in): sending messages (#1156) --- Cargo.lock | 55 +++++++++++++++++----- labs/jack-in/Cargo.toml | 7 +-- labs/jack-in/src/app/model.rs | 51 +++++++++++++++++--- labs/jack-in/src/client/state.rs | 4 ++ labs/jack-in/src/components/input.rs | 70 ++++++++++++++++++++++++++++ labs/jack-in/src/components/mod.rs | 2 + labs/jack-in/src/main.rs | 5 +- 7 files changed, 173 insertions(+), 21 deletions(-) create mode 100644 labs/jack-in/src/components/input.rs diff --git a/Cargo.lock b/Cargo.lock index 1a83c107d..55744d7e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1009,9 +1009,9 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.23.2" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2102ea4f781910f8a5b98dd061f4c2023f479ce7bb1236330099ceb5a93cf17" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ "bitflags", "crossterm_winapi", @@ -2281,6 +2281,7 @@ dependencies = [ "tracing-flame", "tracing-subscriber", "tui-logger", + "tui-realm-stdlib", "tuirealm", ] @@ -4404,6 +4405,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +[[package]] +name = "smawk" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" + [[package]] name = "socket2" version = "0.4.6" @@ -4615,6 +4622,11 @@ name = "textwrap" version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] [[package]] name = "thiserror" @@ -4951,9 +4963,9 @@ checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" [[package]] name = "tui" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96fe69244ec2af261bced1d9046a6fee6c8c2a6b0228e59e5ba39bc8ba4ed729" +checksum = "ccdd26cbd674007e649a272da4475fb666d3aa0ad0531da7136db6fab0e5bad1" dependencies = [ "bitflags", "cassowary", @@ -4964,9 +4976,9 @@ dependencies = [ [[package]] name = "tui-logger" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c9564fd9c18a1f9a20fb8613494744778e357aa0cd5c7d85fdf699a4e5b4962" +checksum = "e7a10e7f291d91032ade13ba20e71d4c4c26daa3fa3edf58b9dd5aa7595240f7" dependencies = [ "chrono", "fxhash", @@ -4978,10 +4990,21 @@ dependencies = [ ] [[package]] -name = "tuirealm" -version = "1.7.1" +name = "tui-realm-stdlib" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0241dbec136199389efb5420fc88dd62cf2543a994547726cabcb76d7e94336" +checksum = "66f252bf8b07c6fd708ddd6349b5f044ae5b488b26929c745728d9c7e2cebfa6" +dependencies = [ + "textwrap 0.15.1", + "tuirealm", + "unicode-width", +] + +[[package]] +name = "tuirealm" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "265411b5606f400459af94fbc5aae6a7bc0e98094d08cb5868390c932be88e26" dependencies = [ "bitflags", "crossterm", @@ -5029,6 +5052,16 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +[[package]] +name = "unicode-linebreak" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5faade31a542b8b35855fff6e8def199853b2da8da256da52f52f1316ee3137" +dependencies = [ + "hashbrown", + "regex", +] + [[package]] name = "unicode-normalization" version = "0.1.21" @@ -5046,9 +5079,9 @@ checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" [[package]] name = "unicode-width" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" diff --git a/labs/jack-in/Cargo.toml b/labs/jack-in/Cargo.toml index 05ab5c49b..ef98c0b57 100644 --- a/labs/jack-in/Cargo.toml +++ b/labs/jack-in/Cargo.toml @@ -22,9 +22,10 @@ serde_json = "1.0.85" structopt = "0.3" tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros"] } tracing-flame = "0.2" -tracing-subscriber = "0.3.15" -tui-logger = "0.8.0" -tuirealm = "~1.7.1" +tracing-subscriber = "0.3.15" +tui-logger = "0.8.1" +tuirealm = "~1.8" +tui-realm-stdlib = "1.2.0" # file-logging specials tracing = { version = "0.1.35", features = ["log"] } diff --git a/labs/jack-in/src/app/model.rs b/labs/jack-in/src/app/model.rs index 05e3c374d..da189ba31 100644 --- a/labs/jack-in/src/app/model.rs +++ b/labs/jack-in/src/app/model.rs @@ -4,6 +4,7 @@ use std::time::Duration; +use matrix_sdk::{ruma::events::room::message::RoomMessageEventContent, Client}; use tokio::sync::mpsc; use tracing::warn; use tuirealm::{ @@ -14,7 +15,7 @@ use tuirealm::{ }; use super::{ - components::{Details, Label, Logger, Rooms, StatusBar}, + components::{Details, InputText, Label, Logger, Rooms, StatusBar}, Id, JackInEvent, MatrixPoller, Msg, }; use crate::client::state::SlidingSyncState; @@ -32,6 +33,7 @@ pub struct Model { pub show_logger: bool, sliding_sync: SlidingSyncState, tx: mpsc::Sender, + pub client: Client, } impl Model { @@ -39,6 +41,7 @@ impl Model { sliding_sync: SlidingSyncState, tx: mpsc::Sender, poller: MatrixPoller, + client: Client, ) -> Self { let app = Self::init_app(sliding_sync.clone(), poller); @@ -50,6 +53,7 @@ impl Model { redraw: true, terminal: TerminalBridge::new().expect("Cannot initialize terminal"), show_logger: true, + client, } } } @@ -85,8 +89,14 @@ impl Model { .constraints([Constraint::Length(35), Constraint::Min(23)].as_ref()) .split(chunks[1]); + let details_chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(10), Constraint::Max(3)].as_ref()) + .split(body_chunks[1]); + self.app.view(&Id::Rooms, f, body_chunks[0]); - self.app.view(&Id::Details, f, body_chunks[1]); + self.app.view(&Id::Details, f, details_chunks[0]); + self.app.view(&Id::TextMessage, f, details_chunks[1]); self.app.view(&Id::Status, f, chunks[2]); self.app.view(&Id::Label, f, chunks[0]); if self.show_logger { @@ -133,6 +143,7 @@ impl Model { ) .is_ok()); + assert!(app.mount(Id::TextMessage, Box::::default(), vec![]).is_ok()); assert!(app .mount( Id::Rooms, @@ -174,13 +185,21 @@ impl Update for Model { } Msg::Clock => None, Msg::RoomsBlur => { - // Give focus to letter counter - let _ = self.app.blur(); - assert!(self.app.active(&Id::Details).is_ok()); + // Give focus to room details + if self.sliding_sync.has_selected_room() { + let _ = self.app.blur(); + assert!(self.app.active(&Id::Details).is_ok()); + } None } Msg::DetailsBlur => { - // Give focus to letter counter + // Give focus to room list + let _ = self.app.blur(); + assert!(self.app.active(&Id::TextMessage).is_ok()); + None + } + Msg::TextBlur => { + // Give focus to room list let _ = self.app.blur(); assert!(self.app.active(&Id::Rooms).is_ok()); None @@ -191,6 +210,26 @@ impl Update for Model { let _ = self.tx.try_send(self.sliding_sync.clone()); None } + Msg::SendMessage(m) => { + if let Some(room) = self + .sliding_sync + .selected_room + .lock_ref() + .as_ref() + .and_then(|id| self.client.get_joined_room(id)) + { + tokio::spawn(async move { + // fire and forget + match room.send(RoomMessageEventContent::text_plain(m), None).await { + Ok(_r) => tracing::info!("Message send"), + Err(e) => tracing::error!("Sending message failed: {:}", e), + } + }); + } else { + warn!("asked to send message, but no room is selected"); + } + None + } } } else { None diff --git a/labs/jack-in/src/client/state.rs b/labs/jack-in/src/client/state.rs index 3d90b88a4..9c0e35901 100644 --- a/labs/jack-in/src/client/state.rs +++ b/labs/jack-in/src/client/state.rs @@ -35,6 +35,10 @@ impl SlidingSyncState { &self.started } + pub fn has_selected_room(&self) -> bool { + self.selected_room.lock_ref().is_some() + } + pub fn select_room(&self, r: Option) { self.selected_room.replace(r); } diff --git a/labs/jack-in/src/components/input.rs b/labs/jack-in/src/components/input.rs new file mode 100644 index 000000000..efb4af669 --- /dev/null +++ b/labs/jack-in/src/components/input.rs @@ -0,0 +1,70 @@ +use tui_realm_stdlib::{states::InputStates, Input}; +use tuirealm::{ + command::{Cmd, Direction, Position}, + event::{Key, KeyEvent, KeyModifiers}, + props::{Alignment, BorderType, Borders, Color, InputType, Style}, + Component, Event, MockComponent, +}; + +use super::{JackInEvent, Msg}; + +#[derive(MockComponent)] +pub struct InputText { + component: Input, +} + +impl Default for InputText { + fn default() -> Self { + Self { + component: Input::default() + .borders( + Borders::default().modifiers(BorderType::Rounded).color(Color::LightYellow), + ) + .foreground(Color::LightYellow) + .input_type(InputType::Text) + .title("Send a message", Alignment::Left) + .invalid_style(Style::default().fg(Color::Red)), + } + } +} + +impl Component for InputText { + fn on(&mut self, ev: Event) -> Option { + match ev { + Event::Keyboard(KeyEvent { code: Key::Left, .. }) => { + self.perform(Cmd::Move(Direction::Left)); + } + Event::Keyboard(KeyEvent { code: Key::Right, .. }) => { + self.perform(Cmd::Move(Direction::Right)); + } + Event::Keyboard(KeyEvent { code: Key::Home, .. }) => { + self.perform(Cmd::GoTo(Position::Begin)); + } + Event::Keyboard(KeyEvent { code: Key::End, .. }) => { + self.perform(Cmd::GoTo(Position::End)); + } + Event::Keyboard(KeyEvent { code: Key::Delete, .. }) => { + self.perform(Cmd::Cancel); + } + Event::Keyboard(KeyEvent { code: Key::Backspace, .. }) => { + self.perform(Cmd::Delete); + } + Event::Keyboard(KeyEvent { code: Key::Char(ch), modifiers: KeyModifiers::NONE }) => { + self.perform(Cmd::Type(ch)); + } + Event::Keyboard(KeyEvent { code: Key::Enter, .. }) => { + let input = self.component.states.get_value(); + self.component.states = InputStates::default(); + return Some(Msg::SendMessage(input)); + } + Event::Keyboard(KeyEvent { code: Key::Tab, .. }) => { + return Some(Msg::TextBlur); + } + Event::Keyboard(KeyEvent { code: Key::Esc, .. }) => { + return Some(Msg::AppClose); + } + _ => {} + } + None + } +} diff --git a/labs/jack-in/src/components/mod.rs b/labs/jack-in/src/components/mod.rs index b32c3fd58..c966b4a7e 100644 --- a/labs/jack-in/src/components/mod.rs +++ b/labs/jack-in/src/components/mod.rs @@ -11,6 +11,7 @@ use super::{JackInEvent, Msg}; // -- modules mod details; +mod input; mod label; mod logger; mod rooms; @@ -18,6 +19,7 @@ mod statusbar; // -- export pub use details::Details; +pub use input::InputText; pub use label::Label; pub use logger::Logger; pub use rooms::Rooms; diff --git a/labs/jack-in/src/main.rs b/labs/jack-in/src/main.rs index 9219bc880..41e724dd3 100644 --- a/labs/jack-in/src/main.rs +++ b/labs/jack-in/src/main.rs @@ -35,7 +35,9 @@ pub enum Msg { Clock, RoomsBlur, DetailsBlur, + TextBlur, SelectRoom(Option), + SendMessage(String), } #[allow(clippy::large_enum_variant)] @@ -66,6 +68,7 @@ pub enum Id { DigitCounter, LetterCounter, Label, + TextMessage, Logger, Status, Rooms, @@ -276,7 +279,7 @@ async fn main() -> Result<()> { .map(|s| format!("{s} ({user_id})")) .unwrap_or_else(|| format!("{user_id}")); let poller = MatrixPoller(rx); - let mut model = Model::new(start_sync, model_tx, poller); + let mut model = Model::new(start_sync, model_tx, poller, client); model.set_title(format!("{display_name} via {}", opt.sliding_sync_proxy)); run_ui(model).await; From 02165f7a05d20b9b80e3b7a78a4c014499cb3c25 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 15 Nov 2022 19:09:43 +0100 Subject: [PATCH 44/78] refactor(sdk)!: Move retrying out of HttpSend trait --- crates/matrix-sdk/src/error.rs | 5 - crates/matrix-sdk/src/http_client.rs | 180 +++++++++++++-------------- 2 files changed, 86 insertions(+), 99 deletions(-) diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index dea936e9e..19daa341d 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -16,7 +16,6 @@ use std::io::Error as IoError; -use http::StatusCode; #[cfg(feature = "qrcode")] use matrix_sdk_base::crypto::ScanError; #[cfg(feature = "e2e-encryption")] @@ -103,10 +102,6 @@ pub enum HttpError { #[error(transparent)] IntoHttp(#[from] IntoHttpError), - /// The server returned a status code that should be retried. - #[error("Server returned an error {0}")] - Server(StatusCode), - /// The given request can't be cloned and thus can't be retried. #[error("The request cannot be cloned")] UnableToCloneRequest, diff --git a/crates/matrix-sdk/src/http_client.rs b/crates/matrix-sdk/src/http_client.rs index 350ab3b47..4ea766db2 100644 --- a/crates/matrix-sdk/src/http_client.rs +++ b/crates/matrix-sdk/src/http_client.rs @@ -48,14 +48,13 @@ pub trait HttpSend: AsyncTraitDeps { /// * `request` - The http request that has been converted from a ruma /// `Request`. /// - /// * `request_config` - The config used for this request. - /// + /// * `timeout` - A timeout for the full request > response cycle. /// # Examples /// /// ``` - /// use matrix_sdk::{ - /// async_trait, bytes::Bytes, config::RequestConfig, HttpError, HttpSend, - /// }; + /// use std::time::Duration; + /// + /// use matrix_sdk::{async_trait, bytes::Bytes, HttpError, HttpSend}; /// /// #[derive(Debug)] /// struct Client(reqwest::Client); @@ -75,7 +74,7 @@ pub trait HttpSend: AsyncTraitDeps { /// async fn send_request( /// &self, /// request: http::Request, - /// config: RequestConfig, + /// timeout: Duration, /// ) -> Result, HttpError> { /// Ok(self /// .response_to_http_response( @@ -90,7 +89,7 @@ pub trait HttpSend: AsyncTraitDeps { async fn send_request( &self, request: http::Request, - config: RequestConfig, + timeout: Duration, ) -> Result, HttpError>; } @@ -165,11 +164,63 @@ impl HttpClient { let request = request.map(|body| body.freeze()); trace!("Sending request"); - let response = self.inner.send_request(request, config).await?; - trace!("Got response: {:?}", response); + #[cfg(not(target_arch = "wasm32"))] + let response = { + use std::sync::atomic::{AtomicU64, Ordering}; + + use backoff::{future::retry, Error as RetryError, ExponentialBackoff}; + use ruma::api::client::error::ErrorKind as ClientApiErrorKind; + + let backoff = + ExponentialBackoff { max_elapsed_time: config.retry_timeout, ..Default::default() }; + let retry_count = AtomicU64::new(1); + + let send_request = || async { + let stop = if let Some(retry_limit) = config.retry_limit { + retry_count.fetch_add(1, Ordering::Relaxed) >= retry_limit + } else { + false + }; + + // Turn errors into permanent errors when the retry limit is reached + let error_type = if stop { + RetryError::Permanent + } else { + |err: HttpError| { + let retry_after = err.client_api_error_kind().and_then(|kind| match kind { + ClientApiErrorKind::LimitExceeded { retry_after_ms } => *retry_after_ms, + _ => None, + }); + RetryError::Transient { err, retry_after } + } + }; + + let raw_response = self + .inner + .send_request(clone_request(&request), config.timeout) + .await + .map_err(error_type)?; + + trace!("Got response: {raw_response:?}"); + + let response = Request::IncomingResponse::try_from_http_response(raw_response) + .map_err(|e| error_type(HttpError::from(e)))?; + + Ok(response) + }; + + retry::<_, HttpError, _, _, _>(backoff, send_request).await? + }; + + #[cfg(target_arch = "wasm32")] + let response = { + let raw_response = self.inner.send_request(request, config.timeout).await?; + trace!("Got response: {raw_response:?}"); + + Request::IncomingResponse::try_from_http_response(raw_response)? + }; - let response = Request::IncomingResponse::try_from_http_response(response)?; Ok(response) } } @@ -228,6 +279,18 @@ impl HttpSettings { } } +// Clones all request parts except the extensions which can't be cloned. +// See also https://github.com/hyperium/http/issues/395 +#[cfg(not(target_arch = "wasm32"))] +fn clone_request(request: &http::Request) -> http::Request { + let mut builder = http::Request::builder() + .version(request.version()) + .method(request.method()) + .uri(request.uri()); + *builder.headers_mut().unwrap() = request.headers().clone(); + builder.body(request.body().clone()).unwrap() +} + async fn response_to_http_response( mut response: Response, ) -> Result, reqwest::Error> { @@ -247,96 +310,25 @@ async fn response_to_http_response( Ok(http_builder.body(body).expect("Can't construct a response using the given body")) } -#[cfg(any(target_arch = "wasm32"))] -async fn send_request( - client: &reqwest::Client, - request: http::Request, - _: RequestConfig, -) -> Result, HttpError> { - let request = reqwest::Request::try_from(request)?; - let response = client.execute(request).await?; - - Ok(response_to_http_response(response).await?) -} - -#[cfg(all(not(target_arch = "wasm32")))] -async fn send_request( - client: &reqwest::Client, - request: http::Request, - config: RequestConfig, -) -> Result, HttpError> { - use std::sync::atomic::{AtomicU64, Ordering}; - - use backoff::{future::retry, Error as RetryError, ExponentialBackoff}; - use http::StatusCode; - use ruma::api::client::error::ErrorKind as ClientApiErrorKind; - - let mut backoff = ExponentialBackoff::default(); - let mut request = reqwest::Request::try_from(request)?; - let retry_limit = config.retry_limit; - let retry_count = AtomicU64::new(1); - - *request.timeout_mut() = Some(config.timeout); - - backoff.max_elapsed_time = config.retry_timeout; - - let request = &request; - let retry_count = &retry_count; - - let request = || async move { - let stop = if let Some(retry_limit) = retry_limit { - retry_count.fetch_add(1, Ordering::Relaxed) >= retry_limit - } else { - false - }; - - // Turn errors into permanent errors when the retry limit is reached - let error_type = if stop { - RetryError::Permanent - } else { - |err: HttpError| { - let retry_after = err.client_api_error_kind().and_then(|kind| match kind { - ClientApiErrorKind::LimitExceeded { retry_after_ms } => *retry_after_ms, - _ => None, - }); - RetryError::Transient { err, retry_after } - } - }; - - let request = request.try_clone().ok_or(HttpError::UnableToCloneRequest)?; - - let response = - client.execute(request).await.map_err(|e| error_type(HttpError::Reqwest(e)))?; - - let status_code = response.status(); - // TODO TOO_MANY_REQUESTS will have a retry timeout which we should - // use. - if !stop - && (status_code.is_server_error() || response.status() == StatusCode::TOO_MANY_REQUESTS) - { - return Err(error_type(HttpError::Server(status_code))); - } - - let response = response_to_http_response(response) - .await - .map_err(|e| RetryError::Permanent(HttpError::Reqwest(e)))?; - - Ok(response) - }; - - let response = retry(backoff, request).await?; - - Ok(response) -} - #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] impl HttpSend for reqwest::Client { async fn send_request( &self, request: http::Request, - config: RequestConfig, + _timeout: Duration, ) -> Result, HttpError> { - send_request(self, request, config).await + #[allow(unused_mut)] + let mut request = reqwest::Request::try_from(request)?; + + // reqwest's timeout functionality is not available on WASM + #[cfg(not(target_arch = "wasm32"))] + { + *request.timeout_mut() = Some(_timeout); + } + + let response = self.execute(request).await?; + + Ok(response_to_http_response(response).await?) } } From 1f2cdfc601d43f18a1b738ddbac382726759fa0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Thu, 17 Nov 2022 12:29:33 +0100 Subject: [PATCH 45/78] chore(contrib): Mitmproxy 9 has removed the HTTP prefix from the Response type --- contrib/mitmproxy/failures.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/mitmproxy/failures.py b/contrib/mitmproxy/failures.py index 28b31b11d..be4460928 100644 --- a/contrib/mitmproxy/failures.py +++ b/contrib/mitmproxy/failures.py @@ -30,13 +30,13 @@ def timeout(flow): # hold a tuple containing a function that may or may not create a failure and # the probability weight at which rate this failure should be triggered. # -# The method should return an http.HTTPResponse if it should modify the +# The method should return an http.Response if it should modify the # response or None if the response should be passed as is. FAILURES = { "Success": (lambda x: None, 50), "Gateway error": - (lambda _: http.HTTPResponse.make(500, b"Gateway error"), 20), - "Limit exeeded": (lambda _: http.HTTPResponse.make( + (lambda _: http.Response.make(500, b"Gateway error"), 20), + "Limit exeeded": (lambda _: http.Response.make( 429, json.dumps({ "errcode": "M_LIMIT_EXCEEDED", From 5a108f53cc2c38170fc7cf3e8a3ba14463845300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Wed, 16 Nov 2022 17:56:01 +0100 Subject: [PATCH 46/78] refactor(crypto): Only use the Mutable for the inner SAS object This patch makes the SAS signalling more robust, it ensures that we can't forget to signal changes to the state. --- .../src/verification/sas/mod.rs | 163 ++++++------------ 1 file changed, 56 insertions(+), 107 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/verification/sas/mod.rs b/crates/matrix-sdk-crypto/src/verification/sas/mod.rs index 2acbaf088..62a454f10 100644 --- a/crates/matrix-sdk-crypto/src/verification/sas/mod.rs +++ b/crates/matrix-sdk-crypto/src/verification/sas/mod.rs @@ -16,10 +16,11 @@ mod helpers; mod inner_sas; mod sas_state; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use futures_core::Stream; use futures_signals::signal::{Mutable, SignalExt}; +use futures_util::StreamExt; use inner_sas::InnerSas; use ruma::{ api::client::keys::upload_signatures::v3::Request as SignatureUploadRequest, @@ -48,8 +49,7 @@ use crate::{ /// Short authentication string object. #[derive(Clone, Debug)] pub struct Sas { - inner: Arc>, - state: Arc>, + inner: Arc>, account: ReadOnlyAccount, identities_being_verified: IdentitiesBeingVerified, flow_id: Arc, @@ -268,12 +268,12 @@ impl Sas { /// Does this verification flow support displaying emoji for the short /// authentication string. pub fn supports_emoji(&self) -> bool { - self.inner.lock().unwrap().supports_emoji() + self.inner.lock_ref().supports_emoji() } /// Did this verification flow start from a verification request. pub fn started_from_request(&self) -> bool { - self.inner.lock().unwrap().started_from_request() + self.inner.lock_ref().started_from_request() } /// Is this a verification that is veryfying one of our own devices. @@ -283,18 +283,18 @@ impl Sas { /// Have we confirmed that the short auth string matches. pub fn have_we_confirmed(&self) -> bool { - self.inner.lock().unwrap().have_we_confirmed() + self.inner.lock_ref().have_we_confirmed() } /// Has the verification been accepted by both parties. pub fn has_been_accepted(&self) -> bool { - self.inner.lock().unwrap().has_been_accepted() + self.inner.lock_ref().has_been_accepted() } /// Get info about the cancellation if the verification flow has been /// cancelled. pub fn cancel_info(&self) -> Option { - if let InnerSas::Cancelled(c) = &*self.inner.lock().unwrap() { + if let InnerSas::Cancelled(c) = &*self.inner.lock_ref() { Some(c.state.as_ref().clone().into()) } else { None @@ -309,7 +309,7 @@ impl Sas { #[cfg(test)] #[allow(dead_code)] pub(crate) fn set_creation_time(&self, time: matrix_sdk_common::instant::Instant) { - self.inner.lock().unwrap().set_creation_time(time) + self.inner.lock_mut().set_creation_time(time) } fn start_helper( @@ -327,13 +327,11 @@ impl Sas { request_handle.is_some(), ); - let state = (&inner).into(); let account = identities.store.account.clone(); ( Sas { - inner: Arc::new(Mutex::new(inner)), - state: Mutable::new(state).into(), + inner: Arc::new(Mutable::new(inner)), account, identities_being_verified: identities, flow_id: flow_id.into(), @@ -414,12 +412,10 @@ impl Sas { request_handle.is_some(), )?; - let state = (&inner).into(); let account = identities.store.account.clone(); Ok(Sas { - inner: Arc::new(Mutex::new(inner)), - state: Mutable::new(state).into(), + inner: Arc::new(Mutable::new(inner)), account, identities_being_verified: identities, flow_id: flow_id.into(), @@ -448,40 +444,31 @@ impl Sas { ) -> Option { let old_state = self.state_debug(); - let (request, state) = { - let mut guard = self.inner.lock().unwrap(); + let request = { + let mut guard = self.inner.lock_mut(); let sas: InnerSas = (*guard).clone(); let methods = settings.allowed_methods; if let Some((sas, content)) = sas.accept(methods) { - let state: SasState = (&sas).into(); - *guard = sas; - ( - Some(match content { - OwnedAcceptContent::ToDevice(c) => { - let content = AnyToDeviceEventContent::KeyVerificationAccept(c); - self.content_to_request(content).into() - } - OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest { - room_id, - txn_id: TransactionId::new(), - content: AnyMessageLikeEventContent::KeyVerificationAccept(content), - } - .into(), - }), - Some(state), - ) + Some(match content { + OwnedAcceptContent::ToDevice(c) => { + let content = AnyToDeviceEventContent::KeyVerificationAccept(c); + self.content_to_request(content).into() + } + OwnedAcceptContent::Room(room_id, content) => RoomMessageRequest { + room_id, + txn_id: TransactionId::new(), + content: AnyMessageLikeEventContent::KeyVerificationAccept(content), + } + .into(), + }) } else { - (None, None) + None } }; - if let Some(new_state) = state { - self.update_state(new_state); - } - let new_state = self.state_debug(); trace!( @@ -505,15 +492,14 @@ impl Sas { &self, ) -> Result<(Vec, Option), CryptoStoreError> { - let (contents, done, state) = { - let mut guard = self.inner.lock().unwrap(); + let (contents, done) = { + let mut guard = self.inner.lock_mut(); let sas: InnerSas = (*guard).clone(); let (sas, contents) = sas.confirm(); - let state: SasState = (&sas).into(); *guard = sas; - (contents, guard.is_done(), state) + (contents, guard.is_done()) }; let mac_requests = contents @@ -540,17 +526,10 @@ impl Sas { VerificationResult::Cancel(c) => { Ok((self.cancel_with_code(c).into_iter().collect(), None)) } - VerificationResult::Ok => { - self.update_state(state); - Ok((mac_requests, None)) - } - VerificationResult::SignatureUpload(r) => { - self.update_state(state); - Ok((mac_requests, Some(r))) - } + VerificationResult::Ok => Ok((mac_requests, None)), + VerificationResult::SignatureUpload(r) => Ok((mac_requests, Some(r))), } } else { - self.update_state(state); Ok((mac_requests, None)) } } @@ -585,8 +564,8 @@ impl Sas { /// /// [`cancel()`]: #method.cancel pub fn cancel_with_code(&self, code: CancelCode) -> Option { - let (content, state) = { - let mut guard = self.inner.lock().unwrap(); + let content = { + let mut guard = self.inner.lock_mut(); if let Some(request) = &self.request_handle { request.cancel_with_code(&code); @@ -594,22 +573,16 @@ impl Sas { let sas: InnerSas = (*guard).clone(); let (sas, content) = sas.cancel(true, code); - let state: SasState = (&sas).into(); *guard = sas; - ( - content.map(|c| match c { - OutgoingContent::Room(room_id, content) => { - RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into() - } - OutgoingContent::ToDevice(c) => self.content_to_request(c).into(), - }), - state, - ) + content.map(|c| match c { + OutgoingContent::Room(room_id, content) => { + RoomMessageRequest { room_id, txn_id: TransactionId::new(), content }.into() + } + OutgoingContent::ToDevice(c) => self.content_to_request(c).into(), + }) }; - self.update_state(state); - content } @@ -625,22 +598,22 @@ impl Sas { /// Has the SAS verification flow timed out. pub fn timed_out(&self) -> bool { - self.inner.lock().unwrap().timed_out() + self.inner.lock_ref().timed_out() } /// Are we in a state where we can show the short auth string. pub fn can_be_presented(&self) -> bool { - self.inner.lock().unwrap().can_be_presented() + self.inner.lock_ref().can_be_presented() } /// Is the SAS flow done. pub fn is_done(&self) -> bool { - self.inner.lock().unwrap().is_done() + self.inner.lock_ref().is_done() } /// Is the SAS flow canceled. pub fn is_cancelled(&self) -> bool { - self.inner.lock().unwrap().is_cancelled() + self.inner.lock_ref().is_cancelled() } /// Get the emoji version of the short auth string. @@ -648,7 +621,7 @@ impl Sas { /// Returns None if we can't yet present the short auth string, otherwise /// seven tuples containing the emoji and description. pub fn emoji(&self) -> Option<[Emoji; 7]> { - self.inner.lock().unwrap().emoji() + self.inner.lock_ref().emoji() } /// Get the index of the emoji representing the short auth string @@ -658,7 +631,7 @@ impl Sas { /// converted to an emoji using the /// [relevant spec entry](https://spec.matrix.org/unstable/client-server-api/#sas-method-emoji). pub fn emoji_index(&self) -> Option<[u8; 7]> { - self.inner.lock().unwrap().emoji_index() + self.inner.lock_ref().emoji_index() } /// Get the decimal version of the short auth string. @@ -667,7 +640,7 @@ impl Sas { /// tuple containing three 4-digit integers that represent the short auth /// string. pub fn decimals(&self) -> Option<(u16, u16, u16)> { - self.inner.lock().unwrap().decimals() + self.inner.lock_ref().decimals() } /// Listen for changes in the SAS verification process. @@ -759,29 +732,16 @@ impl Sas { /// # anyhow::Ok(()) }); /// ``` pub fn changes(&self) -> impl Stream { - self.state.signal_cloned().to_stream() + self.inner.signal_cloned().to_stream().map(|s| (&s).into()) } /// Get the current state of the verification process. pub fn state(&self) -> SasState { - self.state.lock_ref().to_owned() - } - - fn update_state(&self, new_state: SasState) { - let mut lock = self.state.lock_mut(); - - // Only update the state if it differs, this is important so clients don't end - // up printing the emoji twice. For example, the internal state might - // change into a MacReceived, because the other side already confirmed, - // but our side still needs to just show the emoji and wait for - // confirmation. - if *lock != new_state { - *lock = new_state; - } + (&*self.inner.lock_ref()).into() } fn state_debug(&self) -> State { - (&*self.inner.lock().unwrap()).into() + (&*self.inner.lock_ref()).into() } pub(crate) fn receive_any_event( @@ -791,16 +751,14 @@ impl Sas { ) -> Option<(OutgoingContent, Option)> { let old_state = self.state_debug(); - let (content, state) = { - let mut guard = self.inner.lock().unwrap(); + let content = { + let mut guard = self.inner.lock_mut(); let sas: InnerSas = (*guard).clone(); let (sas, content) = sas.receive_any_event(sender, content); - let state: SasState = (&sas).into(); - *guard = sas; - (content, state) + content }; let new_state = self.state_debug(); @@ -811,30 +769,25 @@ impl Sas { "SAS received an event and changed its state" ); - self.update_state(state); content } pub(crate) fn mark_request_as_sent(&self, request_id: &TransactionId) { let old_state = self.state_debug(); - let state = { - let mut guard = self.inner.lock().unwrap(); + { + let mut guard = self.inner.lock_mut(); let sas: InnerSas = (*guard).clone(); if let Some(sas) = sas.mark_request_as_sent(request_id) { - let state: SasState = (&sas).into(); *guard = sas; - - Some(state) } else { error!( flow_id = self.flow_id().as_str(), %request_id, "Tried to mark a request as sent, but the request ID didn't match" ); - None } }; @@ -847,18 +800,14 @@ impl Sas { %request_id, "Marked a SAS verification HTTP request as sent" ); - - if let Some(state) = state { - self.update_state(state); - } } pub(crate) fn verified_devices(&self) -> Option> { - self.inner.lock().unwrap().verified_devices() + self.inner.lock_ref().verified_devices() } pub(crate) fn verified_identities(&self) -> Option> { - self.inner.lock().unwrap().verified_identities() + self.inner.lock_ref().verified_identities() } pub(crate) fn content_to_request(&self, content: AnyToDeviceEventContent) -> ToDeviceRequest { From 2bc0ac8fd7204ec2eba94f00b0c259e129a03b64 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 17 Nov 2022 12:27:20 +0100 Subject: [PATCH 47/78] refactor(sdk): Merge mutexes in TimelineInner into one --- .../src/room/timeline/event_handler.rs | 91 +++++++++---------- crates/matrix-sdk/src/room/timeline/mod.rs | 12 ++- 2 files changed, 53 insertions(+), 50 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 4f4d2d1d3..3ac047736 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::Arc; +use std::sync::{Arc, MutexGuard}; +use futures_signals::signal_vec::MutableVecLockMut; use indexmap::map::Entry; use matrix_sdk_base::deserialized_responses::EncryptionInfo; use ruma::{ @@ -38,8 +39,8 @@ use tracing::{debug, error, info, warn}; use super::{ event_item::{BundledReactions, TimelineDetails}, - find_event, find_fully_read, EventTimelineItem, Message, TimelineInner, TimelineItem, - TimelineItemContent, TimelineKey, VirtualTimelineItem, + find_event, find_fully_read, EventTimelineItem, Message, TimelineInner, TimelineInnerMetadata, + TimelineItem, TimelineItemContent, TimelineKey, VirtualTimelineItem, }; impl TimelineInner { @@ -128,47 +129,41 @@ impl TimelineInner { self.set_fully_read_event(fully_read_event); } - pub(super) fn set_fully_read_event(&self, fully_read_event: OwnedEventId) { - { - let mut fully_read_lock = self.fully_read_event.lock().unwrap(); + pub(super) fn set_fully_read_event(&self, fully_read_event_id: OwnedEventId) { + let mut metadata_lock = self.metadata.lock().unwrap(); - if fully_read_lock.as_ref() == Some(&fully_read_event) { - return; - } - - *fully_read_lock = Some(fully_read_event); + if metadata_lock.fully_read_event.as_ref().map_or(false, |id| *id == fully_read_event_id) { + return; } - self.update_fully_read_item(); + metadata_lock.fully_read_event = Some(fully_read_event_id); + + let items_lock = self.items.lock_mut(); + update_fully_read_item(metadata_lock, items_lock); } +} - fn update_fully_read_item(&self) { - let fully_read_lock = self.fully_read_event.lock().unwrap(); - - let fully_read_event = match &*fully_read_lock { - Some(event) => event, - None => return, - }; - - let mut items_lock = self.items.lock_mut(); - let old_idx = find_fully_read(&items_lock); - let new_idx = find_event(&items_lock, fully_read_event).map(|(idx, _)| idx + 1); - - match (old_idx, new_idx) { - (None, None) => {} - (None, Some(idx)) => { - *self.fully_read_event_in_timeline.lock().unwrap() = true; - let item = TimelineItem::Virtual(VirtualTimelineItem::ReadMarker); - items_lock.insert_cloned(idx, item.into()); - } - (Some(_), None) => { - // Keep the current position of the read marker, hopefully we - // should have a new position later. - *self.fully_read_event_in_timeline.lock().unwrap() = false; - } - (Some(from), Some(to)) => { - items_lock.move_from_to(from, to); - } +fn update_fully_read_item( + mut metadata_lock: MutexGuard<'_, TimelineInnerMetadata>, + mut items_lock: MutableVecLockMut<'_, Arc>, +) { + let Some(fully_read_event) = &metadata_lock.fully_read_event else { return }; + let old_idx = find_fully_read(&items_lock); + let new_idx = find_event(&items_lock, fully_read_event).map(|(idx, _)| idx + 1); + match (old_idx, new_idx) { + (None, None) => {} + (None, Some(idx)) => { + metadata_lock.fully_read_event_in_timeline = true; + let item = TimelineItem::Virtual(VirtualTimelineItem::ReadMarker); + items_lock.insert_cloned(idx, item.into()); + } + (Some(_), None) => { + // Keep the current position of the read marker, hopefully we + // should have a new position later. + metadata_lock.fully_read_event_in_timeline = false; + } + (Some(from), Some(to)) => { + items_lock.move_from_to(from, to); } } } @@ -352,7 +347,7 @@ impl<'a> TimelineEventHandler<'a> { // If this is ever run in parallel for some reason though, make sure the // reaction lock is held for the entire time of the timeline items being // locked so these two things can't get out of sync. - let mut lock = self.timeline.reaction_map.lock().unwrap(); + let mut lock = self.timeline.metadata.lock().unwrap(); let did_update = self.maybe_update_timeline_item(event_id, "reaction", |item| { // Handling of reactions on redacted events is an open question. @@ -375,7 +370,7 @@ impl<'a> TimelineEventHandler<'a> { }); if did_update { - lock.insert(self.flow.to_key(), (self.meta.sender.clone(), c.relates_to)); + lock.reaction_map.insert(self.flow.to_key(), (self.meta.sender.clone(), c.relates_to)); } } @@ -397,8 +392,10 @@ impl<'a> TimelineEventHandler<'a> { // Don't release this lock until after update_timeline_item. // See first comment in handle_reaction for why. - let mut lock = self.timeline.reaction_map.lock().unwrap(); - if let Some((sender, rel)) = lock.remove(&TimelineKey::EventId(redacts.clone())) { + let mut lock = self.timeline.metadata.lock().unwrap(); + if let Some((sender, rel)) = + lock.reaction_map.remove(&TimelineKey::EventId(redacts.clone())) + { did_update = self.maybe_update_timeline_item(&rel.event_id, "redaction", |item| { let mut reactions = item.reactions.clone(); @@ -520,11 +517,11 @@ impl<'a> TimelineEventHandler<'a> { drop(lock); + let metadata_lock = self.timeline.metadata.lock().unwrap(); // See if we got the event corresponding to the fully read marker now. - let fully_read_event_in_timeline = - *self.timeline.fully_read_event_in_timeline.lock().unwrap(); - if !fully_read_event_in_timeline { - self.timeline.update_fully_read_item(); + if !metadata_lock.fully_read_event_in_timeline { + let items_lock = self.timeline.items.lock_mut(); + update_fully_read_item(metadata_lock, items_lock); } } diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index 8fcd9a46c..3a87a2540 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -73,10 +73,16 @@ pub struct Timeline { #[derive(Clone, Debug, Default)] struct TimelineInner { items: MutableVec>, + metadata: Arc>, +} + +/// Non-signalling parts of `TimelineInner`. +#[derive(Debug, Default)] +struct TimelineInnerMetadata { // Reaction event / txn ID => sender and reaction data - reaction_map: Arc>>, - fully_read_event: Arc>>, - fully_read_event_in_timeline: Arc>, + reaction_map: HashMap, + fully_read_event: Option, + fully_read_event_in_timeline: bool, } impl Timeline { From ac33ca5fa0623144750ac8b5bae76d58b82818ea Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 17 Nov 2022 13:18:52 +0100 Subject: [PATCH 48/78] refactor(sdk): Hold locks for the full lifetime of TimelineEventHandler Not really necessary now, but required for thread-safe bulk updates like we want for retrying decryption. --- .../src/room/timeline/event_handler.rs | 172 ++++++++++-------- 1 file changed, 93 insertions(+), 79 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 3ac047736..47c2a4b46 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::sync::{Arc, MutexGuard}; +use std::{collections::HashMap, sync::Arc}; use futures_signals::signal_vec::MutableVecLockMut; use indexmap::map::Entry; @@ -20,7 +20,7 @@ use matrix_sdk_base::deserialized_responses::EncryptionInfo; use ruma::{ events::{ fully_read::FullyReadEvent, - reaction::ReactionEventContent, + reaction::{ReactionEventContent, Relation as AnnotationRelation}, room::{ encrypted::{self, RoomEncryptedEventContent}, message::{self, MessageType, Replacement, RoomMessageEventContent}, @@ -59,7 +59,7 @@ impl TimelineInner { content: AnyMessageLikeEventContent, own_user_id: &UserId, ) { - let meta = TimelineEventMetadata { + let event_meta = TimelineEventMetadata { sender: own_user_id.to_owned(), is_own_event: true, relations: None, @@ -70,7 +70,10 @@ impl TimelineInner { let flow = Flow::Local { txn_id }; let kind = TimelineEventKind::Message { content }; - TimelineEventHandler::new(meta, flow, self).handle_event(kind) + let timeline_items = self.items.lock_mut(); + let mut timeline_meta = self.metadata.lock().unwrap(); + TimelineEventHandler::new(event_meta, flow, timeline_items, &mut timeline_meta) + .handle_event(kind); } pub(super) fn handle_back_paginated_event( @@ -100,7 +103,7 @@ impl TimelineInner { let sender = event.sender().to_owned(); let is_own_event = sender == own_user_id; - let meta = TimelineEventMetadata { + let event_meta = TimelineEventMetadata { sender, is_own_event, relations: event.relations().cloned(), @@ -114,7 +117,10 @@ impl TimelineInner { position, }; - TimelineEventHandler::new(meta, flow, self).handle_event(event.into()) + let timeline_items = self.items.lock_mut(); + let mut timeline_meta = self.metadata.lock().unwrap(); + TimelineEventHandler::new(event_meta, flow, timeline_items, &mut timeline_meta) + .handle_event(event.into()) } pub(super) fn handle_fully_read(&self, raw: Raw) { @@ -138,29 +144,35 @@ impl TimelineInner { metadata_lock.fully_read_event = Some(fully_read_event_id); - let items_lock = self.items.lock_mut(); - update_fully_read_item(metadata_lock, items_lock); + let mut items_lock = self.items.lock_mut(); + let metadata = &mut *metadata_lock; + update_fully_read_item( + &mut items_lock, + metadata.fully_read_event.as_deref(), + &mut metadata.fully_read_event_in_timeline, + ); } } fn update_fully_read_item( - mut metadata_lock: MutexGuard<'_, TimelineInnerMetadata>, - mut items_lock: MutableVecLockMut<'_, Arc>, + items_lock: &mut MutableVecLockMut<'_, Arc>, + fully_read_event: Option<&EventId>, + fully_read_event_in_timeline: &mut bool, ) { - let Some(fully_read_event) = &metadata_lock.fully_read_event else { return }; - let old_idx = find_fully_read(&items_lock); - let new_idx = find_event(&items_lock, fully_read_event).map(|(idx, _)| idx + 1); + let Some(fully_read_event) = fully_read_event else { return }; + let old_idx = find_fully_read(items_lock); + let new_idx = find_event(items_lock, fully_read_event).map(|(idx, _)| idx + 1); match (old_idx, new_idx) { (None, None) => {} (None, Some(idx)) => { - metadata_lock.fully_read_event_in_timeline = true; + *fully_read_event_in_timeline = true; let item = TimelineItem::Virtual(VirtualTimelineItem::ReadMarker); items_lock.insert_cloned(idx, item.into()); } (Some(_), None) => { // Keep the current position of the read marker, hopefully we // should have a new position later. - metadata_lock.fully_read_event_in_timeline = false; + *fully_read_event_in_timeline = false; } (Some(from), Some(to)) => { items_lock.move_from_to(from, to); @@ -255,13 +267,29 @@ enum TimelineItemPosition { struct TimelineEventHandler<'a> { meta: TimelineEventMetadata, flow: Flow, - timeline: &'a TimelineInner, + timeline_items: MutableVecLockMut<'a, Arc>, + reaction_map: &'a mut HashMap, + fully_read_event: &'a mut Option, + fully_read_event_in_timeline: &'a mut bool, event_added: bool, } impl<'a> TimelineEventHandler<'a> { - fn new(meta: TimelineEventMetadata, flow: Flow, timeline: &'a TimelineInner) -> Self { - Self { meta, flow, timeline, event_added: false } + fn new( + event_meta: TimelineEventMetadata, + flow: Flow, + timeline_items: MutableVecLockMut<'a, Arc>, + timeline_meta: &'a mut TimelineInnerMetadata, + ) -> Self { + Self { + meta: event_meta, + flow, + timeline_items, + reaction_map: &mut timeline_meta.reaction_map, + fully_read_event: &mut timeline_meta.fully_read_event, + fully_read_event_in_timeline: &mut timeline_meta.fully_read_event_in_timeline, + event_added: false, + } } fn handle_event(mut self, event_kind: TimelineEventKind) { @@ -302,7 +330,7 @@ impl<'a> TimelineEventHandler<'a> { fn handle_room_message_edit(&mut self, replacement: Replacement) { let event_id = &replacement.event_id; - self.maybe_update_timeline_item(event_id, "edit", |item| { + maybe_update_timeline_item(&mut self.timeline_items, event_id, "edit", |item| { if self.meta.sender != item.sender() { info!( %event_id, original_sender = %item.sender(), edit_sender = %self.meta.sender, @@ -343,13 +371,8 @@ impl<'a> TimelineEventHandler<'a> { fn handle_reaction(&mut self, c: ReactionEventContent) { let event_id: &EventId = &c.relates_to.event_id; - // This lock should never be contended, same as the timeline item lock. - // If this is ever run in parallel for some reason though, make sure the - // reaction lock is held for the entire time of the timeline items being - // locked so these two things can't get out of sync. - let mut lock = self.timeline.metadata.lock().unwrap(); - - let did_update = self.maybe_update_timeline_item(event_id, "reaction", |item| { + let items = &mut self.timeline_items; + let did_update = maybe_update_timeline_item(items, event_id, "reaction", |item| { // Handling of reactions on redacted events is an open question. // For now, ignore reactions on redacted events like Element does. if let TimelineItemContent::RedactedMessage = item.content { @@ -370,7 +393,7 @@ impl<'a> TimelineEventHandler<'a> { }); if did_update { - lock.reaction_map.insert(self.flow.to_key(), (self.meta.sender.clone(), c.relates_to)); + self.reaction_map.insert(self.flow.to_key(), (self.meta.sender.clone(), c.relates_to)); } } @@ -390,13 +413,11 @@ impl<'a> TimelineEventHandler<'a> { fn handle_redaction(&mut self, redacts: OwnedEventId, _content: RoomRedactionEventContent) { let mut did_update = false; - // Don't release this lock until after update_timeline_item. - // See first comment in handle_reaction for why. - let mut lock = self.timeline.metadata.lock().unwrap(); if let Some((sender, rel)) = - lock.reaction_map.remove(&TimelineKey::EventId(redacts.clone())) + self.reaction_map.remove(&TimelineKey::EventId(redacts.clone())) { - did_update = self.maybe_update_timeline_item(&rel.event_id, "redaction", |item| { + let items = &mut self.timeline_items; + did_update = maybe_update_timeline_item(items, &rel.event_id, "redaction", |item| { let mut reactions = item.reactions.clone(); let Entry::Occupied(mut details_entry) = reactions.bundled.entry(rel.key) else { @@ -447,7 +468,8 @@ impl<'a> TimelineEventHandler<'a> { // Even if the event being redacted is a reaction (found in // `reaction_map`), it can still be present in the timeline items // directly with the raw event timeline feature (not yet implemented). - did_update |= self.update_timeline_item(&redacts, "redaction", |item| item.to_redacted()); + let items = &mut self.timeline_items; + did_update |= update_timeline_item(items, &redacts, "redaction", |item| item.to_redacted()); if !did_update { // We will want to know this when debugging redaction issues. @@ -472,17 +494,16 @@ impl<'a> TimelineEventHandler<'a> { }; let item = Arc::new(TimelineItem::Event(item)); - let mut lock = self.timeline.items.lock_mut(); match &self.flow { Flow::Local { .. } => { - lock.push_cloned(item); + self.timeline_items.push_cloned(item); } Flow::Remote { txn_id, event_id, position, raw_event, .. } => { if let Some(txn_id) = txn_id { - if let Some((idx, _old_item)) = find_event(&lock, txn_id) { + if let Some((idx, _old_item)) = find_event(&self.timeline_items, txn_id) { // TODO: Check whether anything is different about the // old and new item? - lock.set_cloned(idx, item); + self.timeline_items.set_cloned(idx, item); return; } else { warn!( @@ -493,7 +514,7 @@ impl<'a> TimelineEventHandler<'a> { } } - if let Some((idx, old_item)) = find_event(&lock, event_id) { + if let Some((idx, old_item)) = find_event(&self.timeline_items, event_id) { warn!( ?item, ?old_item, @@ -504,62 +525,55 @@ impl<'a> TimelineEventHandler<'a> { // With /messages and /sync sometimes disagreeing on order // of messages, we might want to change the position in some // circumstances, but for now this should be good enough. - lock.set_cloned(idx, item); + self.timeline_items.set_cloned(idx, item); return; } match position { - TimelineItemPosition::Start => lock.insert_cloned(0, item), - TimelineItemPosition::End => lock.push_cloned(item), + TimelineItemPosition::Start => self.timeline_items.insert_cloned(0, item), + TimelineItemPosition::End => self.timeline_items.push_cloned(item), } } } - drop(lock); - - let metadata_lock = self.timeline.metadata.lock().unwrap(); // See if we got the event corresponding to the fully read marker now. - if !metadata_lock.fully_read_event_in_timeline { - let items_lock = self.timeline.items.lock_mut(); - update_fully_read_item(metadata_lock, items_lock); + if !*self.fully_read_event_in_timeline { + update_fully_read_item( + &mut self.timeline_items, + self.fully_read_event.as_deref(), + self.fully_read_event_in_timeline, + ); } } +} - /// Returns whether an update happened - fn maybe_update_timeline_item( - &self, - event_id: &EventId, - action: &str, - update: impl FnOnce(&EventTimelineItem) -> Option, - ) -> bool { - // No point in trying to update items with relations when back- - // paginating, the event the relation applies to can't be processed yet. - if matches!(self.flow, Flow::Remote { position: TimelineItemPosition::Start, .. }) { - return false; +/// Returns whether an update happened +fn maybe_update_timeline_item( + timeline_items: &mut MutableVecLockMut<'_, Arc>, + event_id: &EventId, + action: &str, + update: impl FnOnce(&EventTimelineItem) -> Option, +) -> bool { + if let Some((idx, item)) = find_event(timeline_items, event_id) { + if let Some(new_item) = update(item) { + timeline_items.set_cloned(idx, Arc::new(TimelineItem::Event(new_item))); + return true; } - - let mut lock = self.timeline.items.lock_mut(); - if let Some((idx, item)) = find_event(&lock, event_id) { - if let Some(new_item) = update(item) { - lock.set_cloned(idx, Arc::new(TimelineItem::Event(new_item))); - return true; - } - } else { - debug!(%event_id, "Timeline item not found, discarding {action}"); - } - - false + } else { + debug!(%event_id, "Timeline item not found, discarding {action}"); } - /// Returns whether an update happened - fn update_timeline_item( - &self, - event_id: &EventId, - action: &str, - update: impl FnOnce(&EventTimelineItem) -> EventTimelineItem, - ) -> bool { - self.maybe_update_timeline_item(event_id, action, move |item| Some(update(item))) - } + false +} + +/// Returns whether an update happened +fn update_timeline_item( + timeline_items: &mut MutableVecLockMut<'_, Arc>, + event_id: &EventId, + action: &str, + update: impl FnOnce(&EventTimelineItem) -> EventTimelineItem, +) -> bool { + maybe_update_timeline_item(timeline_items, event_id, action, move |item| Some(update(item))) } struct NewEventTimelineItem { From da76a2700d7a25cb40e99c9f5c9f7b5b26b75104 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 17 Nov 2022 15:55:25 +0100 Subject: [PATCH 49/78] refactor(sdk): Use an async lock for TimelineInnerMetadata --- .../src/room/timeline/event_handler.rs | 22 ++--- crates/matrix-sdk/src/room/timeline/mod.rs | 34 +++---- crates/matrix-sdk/src/room/timeline/tests.rs | 88 ++++++++++--------- 3 files changed, 77 insertions(+), 67 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 47c2a4b46..b85595d5a 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -44,16 +44,17 @@ use super::{ }; impl TimelineInner { - pub(super) fn handle_live_event( + pub(super) async fn handle_live_event( &self, raw: Raw, encryption_info: Option, own_user_id: &UserId, ) { self.handle_remote_event(raw, encryption_info, own_user_id, TimelineItemPosition::End) + .await; } - pub(super) fn handle_local_event( + pub(super) async fn handle_local_event( &self, txn_id: OwnedTransactionId, content: AnyMessageLikeEventContent, @@ -70,22 +71,23 @@ impl TimelineInner { let flow = Flow::Local { txn_id }; let kind = TimelineEventKind::Message { content }; + let mut timeline_meta = self.metadata.lock().await; let timeline_items = self.items.lock_mut(); - let mut timeline_meta = self.metadata.lock().unwrap(); TimelineEventHandler::new(event_meta, flow, timeline_items, &mut timeline_meta) .handle_event(kind); } - pub(super) fn handle_back_paginated_event( + pub(super) async fn handle_back_paginated_event( &self, raw: Raw, encryption_info: Option, own_user_id: &UserId, ) { self.handle_remote_event(raw, encryption_info, own_user_id, TimelineItemPosition::Start) + .await; } - fn handle_remote_event( + async fn handle_remote_event( &self, raw: Raw, encryption_info: Option, @@ -117,13 +119,13 @@ impl TimelineInner { position, }; + let mut timeline_meta = self.metadata.lock().await; let timeline_items = self.items.lock_mut(); - let mut timeline_meta = self.metadata.lock().unwrap(); TimelineEventHandler::new(event_meta, flow, timeline_items, &mut timeline_meta) .handle_event(event.into()) } - pub(super) fn handle_fully_read(&self, raw: Raw) { + pub(super) async fn handle_fully_read(&self, raw: Raw) { let fully_read_event = match raw.deserialize() { Ok(ev) => ev.content.event_id, Err(error) => { @@ -132,11 +134,11 @@ impl TimelineInner { } }; - self.set_fully_read_event(fully_read_event); + self.set_fully_read_event(fully_read_event).await; } - pub(super) fn set_fully_read_event(&self, fully_read_event_id: OwnedEventId) { - let mut metadata_lock = self.metadata.lock().unwrap(); + pub(super) async fn set_fully_read_event(&self, fully_read_event_id: OwnedEventId) { + let mut metadata_lock = self.metadata.lock().await; if metadata_lock.fully_read_event.as_ref().map_or(false, |id| *id == fully_read_event_id) { return; diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index 3a87a2540..af87b1a7b 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -18,12 +18,12 @@ use std::{ collections::HashMap, - sync::{Arc, Mutex}, + sync::{Arc, Mutex as StdMutex}, }; use futures_core::Stream; use futures_signals::signal_vec::{MutableVec, SignalVec, SignalVecExt, VecDiff}; -use matrix_sdk_base::deserialized_responses::EncryptionInfo; +use matrix_sdk_base::{deserialized_responses::EncryptionInfo, locks::Mutex}; use ruma::{ assign, events::{ @@ -64,8 +64,8 @@ pub use self::{ pub struct Timeline { inner: TimelineInner, room: room::Common, - start_token: Mutex>, - _end_token: Mutex>, + start_token: StdMutex>, + _end_token: StdMutex>, _timeline_event_handler_guard: EventHandlerDropGuard, _fully_read_handler_guard: EventHandlerDropGuard, } @@ -91,7 +91,7 @@ impl Timeline { match room.account_data_static::().await { Ok(Some(fully_read)) => match fully_read.deserialize() { - Ok(fully_read) => inner.set_fully_read_event(fully_read.content.event_id), + Ok(fully_read) => inner.set_fully_read_event(fully_read.content.event_id).await, Err(error) => { error!(?error, "Failed to deserialize `m.fully_read` account data") } @@ -107,7 +107,7 @@ impl Timeline { move |event, encryption_info: Option, room: Room| { let inner = inner.clone(); async move { - inner.handle_live_event(event, encryption_info, room.own_user_id()); + inner.handle_live_event(event, encryption_info, room.own_user_id()).await; } } }); @@ -119,7 +119,7 @@ impl Timeline { move |event| { let inner = inner.clone(); async move { - inner.handle_fully_read(event); + inner.handle_fully_read(event).await; } } }); @@ -128,8 +128,8 @@ impl Timeline { Timeline { inner, room: room.clone(), - start_token: Mutex::new(None), - _end_token: Mutex::new(None), + start_token: StdMutex::new(None), + _end_token: StdMutex::new(None), _timeline_event_handler_guard, _fully_read_handler_guard, } @@ -152,11 +152,13 @@ impl Timeline { let own_user_id = self.room.own_user_id(); for room_ev in messages.chunk { - self.inner.handle_back_paginated_event( - room_ev.event.cast(), - room_ev.encryption_info, - own_user_id, - ); + self.inner + .handle_back_paginated_event( + room_ev.event.cast(), + room_ev.encryption_info, + own_user_id, + ) + .await; } Ok(outcome) @@ -213,7 +215,9 @@ impl Timeline { txn_id: Option<&TransactionId>, ) -> Result<()> { let txn_id = txn_id.map_or_else(TransactionId::new, ToOwned::to_owned); - self.inner.handle_local_event(txn_id.clone(), content.clone(), self.room.own_user_id()); + self.inner + .handle_local_event(txn_id.clone(), content.clone(), self.room.own_user_id()) + .await; // If this room isn't actually in joined state, we'll get a server error. // Not ideal, but works for now. diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index 6e9c1691f..82a61a4db 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -53,7 +53,7 @@ async fn reaction_redaction() { let timeline = TestTimeline::new(&ALICE); let mut stream = timeline.stream(); - timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("hi!")); + timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("hi!")).await; let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); let event = item.as_event().unwrap(); assert_eq!(event.reactions().len(), 0); @@ -61,7 +61,7 @@ async fn reaction_redaction() { let msg_event_id = event.event_id().unwrap(); let rel = reaction::Relation::new(msg_event_id.to_owned(), "+1".to_owned()); - timeline.handle_live_message_event(&BOB, ReactionEventContent::new(rel)); + timeline.handle_live_message_event(&BOB, ReactionEventContent::new(rel)).await; let item = assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { index: 0, value }) => value); let event = item.as_event().unwrap(); @@ -71,7 +71,7 @@ async fn reaction_redaction() { let reaction_event_id = event.event_id().unwrap(); - timeline.handle_live_redaction(&BOB, reaction_event_id); + timeline.handle_live_redaction(&BOB, reaction_event_id).await; let item = assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { index: 0, value }) => value); let event = item.as_event().unwrap(); @@ -83,7 +83,7 @@ async fn invalid_edit() { let timeline = TestTimeline::new(&ALICE); let mut stream = timeline.stream(); - timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("test")); + timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("test")).await; let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); let event = item.as_event().unwrap(); let msg = event.content.as_message().unwrap(); @@ -98,7 +98,7 @@ async fn invalid_edit() { ))), }); // Edit is from a different user than the previous event - timeline.handle_live_message_event(&BOB, edit); + timeline.handle_live_message_event(&BOB, edit).await; // Can't easily test the non-arrival of an item using the stream. Instead // just assert that there is still just a single item in the timeline. @@ -111,23 +111,25 @@ async fn edit_redacted() { let mut stream = timeline.stream(); // Ruma currently fails to serialize most redacted events correctly - timeline.handle_live_custom_event(json!({ - "content": {}, - "event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I", - "origin_server_ts": 10, - "sender": "@alice:example.org", - "type": "m.room.message", - "unsigned": { - "redacted_because": { - "content": {}, - "redacts": "$eeG0HA0FAZ37wP8kXlNkxx3K", - "event_id": "$N6eUCBc3vu58PL8TobGaVQzM", - "sender": "@alice:example.org", - "origin_server_ts": 5, - "type": "m.room.redaction", + timeline + .handle_live_custom_event(json!({ + "content": {}, + "event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I", + "origin_server_ts": 10, + "sender": "@alice:example.org", + "type": "m.room.message", + "unsigned": { + "redacted_because": { + "content": {}, + "redacts": "$eeG0HA0FAZ37wP8kXlNkxx3K", + "event_id": "$N6eUCBc3vu58PL8TobGaVQzM", + "sender": "@alice:example.org", + "origin_server_ts": 5, + "type": "m.room.redaction", + }, }, - }, - })); + })) + .await; let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); let redacted_event_id = item.as_event().unwrap().event_id().unwrap(); @@ -138,7 +140,7 @@ async fn edit_redacted() { MessageType::text_plain("test"), ))), }); - timeline.handle_live_message_event(&ALICE, edit); + timeline.handle_live_message_event(&ALICE, edit).await; assert_eq!(timeline.inner.items.lock_ref().len(), 1); } @@ -146,21 +148,23 @@ async fn edit_redacted() { #[async_test] async fn unable_to_decrypt() { let timeline = TestTimeline::new(&ALICE); - timeline.handle_live_message_event( - &BOB, - RoomEncryptedEventContent::new( - EncryptedEventScheme::MegolmV1AesSha2( - MegolmV1AesSha2ContentInit { - ciphertext: "This can't be decrypted".to_owned(), - sender_key: "whatever".to_owned(), - device_id: "MyDevice".into(), - session_id: "MySession".into(), - } - .into(), + timeline + .handle_live_message_event( + &BOB, + RoomEncryptedEventContent::new( + EncryptedEventScheme::MegolmV1AesSha2( + MegolmV1AesSha2ContentInit { + ciphertext: "This can't be decrypted".to_owned(), + sender_key: "whatever".to_owned(), + device_id: "MyDevice".into(), + session_id: "MySession".into(), + } + .into(), + ), + None, ), - None, - ), - ); + ) + .await; let timeline_items = timeline.inner.items.lock_ref(); assert_eq!(timeline_items.len(), 1); let event = timeline_items[0].as_event().unwrap(); @@ -187,7 +191,7 @@ impl TestTimeline { self.inner.items.signal_vec_cloned().to_stream() } - fn handle_live_message_event(&self, sender: &UserId, content: C) + async fn handle_live_message_event(&self, sender: &UserId, content: C) where C: MessageLikeEventContent, { @@ -199,15 +203,15 @@ impl TestTimeline { unsigned: Default::default(), }; let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, &self.own_user_id); + self.inner.handle_live_event(raw, None, &self.own_user_id).await; } - fn handle_live_custom_event(&self, event: JsonValue) { + async fn handle_live_custom_event(&self, event: JsonValue) { let raw = Raw::new(&event).unwrap().cast(); - self.inner.handle_live_event(raw, None, &self.own_user_id); + self.inner.handle_live_event(raw, None, &self.own_user_id).await; } - fn handle_live_redaction(&self, sender: &UserId, redacts: &EventId) { + async fn handle_live_redaction(&self, sender: &UserId, redacts: &EventId) { let ev = OriginalSyncRoomRedactionEvent { content: Default::default(), redacts: redacts.to_owned(), @@ -217,7 +221,7 @@ impl TestTimeline { unsigned: Default::default(), }; let raw = Raw::new(&ev).unwrap().cast(); - self.inner.handle_live_event(raw, None, &self.own_user_id); + self.inner.handle_live_event(raw, None, &self.own_user_id).await; } } From 74e9209e95697a72e1352b2dcad193168e0e46a6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 17 Nov 2022 16:20:46 +0100 Subject: [PATCH 50/78] feat(sdk): Retry decryption of UTD timeline items --- .../src/room/timeline/event_handler.rs | 202 +++++++++++++----- .../src/room/timeline/event_item.rs | 9 + crates/matrix-sdk/src/room/timeline/mod.rs | 38 +++- 3 files changed, 192 insertions(+), 57 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index b85595d5a..cd91929b4 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -16,7 +16,11 @@ use std::{collections::HashMap, sync::Arc}; use futures_signals::signal_vec::MutableVecLockMut; use indexmap::map::Entry; -use matrix_sdk_base::deserialized_responses::EncryptionInfo; +#[cfg(feature = "e2e-encryption")] +use matrix_sdk_base::crypto::OlmMachine; +use matrix_sdk_base::{deserialized_responses::EncryptionInfo, locks::MutexGuard}; +#[cfg(feature = "e2e-encryption")] +use ruma::RoomId; use ruma::{ events::{ fully_read::FullyReadEvent, @@ -50,8 +54,15 @@ impl TimelineInner { encryption_info: Option, own_user_id: &UserId, ) { - self.handle_remote_event(raw, encryption_info, own_user_id, TimelineItemPosition::End) - .await; + let mut timeline_meta = self.metadata.lock().await; + handle_remote_event( + raw, + own_user_id, + encryption_info, + TimelineItemPosition::End, + &mut self.items.lock_mut(), + &mut timeline_meta, + ); } pub(super) async fn handle_local_event( @@ -72,8 +83,8 @@ impl TimelineInner { let kind = TimelineEventKind::Message { content }; let mut timeline_meta = self.metadata.lock().await; - let timeline_items = self.items.lock_mut(); - TimelineEventHandler::new(event_meta, flow, timeline_items, &mut timeline_meta) + let mut timeline_items = self.items.lock_mut(); + TimelineEventHandler::new(event_meta, flow, &mut timeline_items, &mut timeline_meta) .handle_event(kind); } @@ -83,46 +94,14 @@ impl TimelineInner { encryption_info: Option, own_user_id: &UserId, ) { - self.handle_remote_event(raw, encryption_info, own_user_id, TimelineItemPosition::Start) - .await; - } - - async fn handle_remote_event( - &self, - raw: Raw, - encryption_info: Option, - own_user_id: &UserId, - position: TimelineItemPosition, - ) { - let event = match raw.deserialize() { - Ok(ev) => ev, - Err(_e) => { - // TODO: Add some sort of error timeline item - return; - } - }; - - let sender = event.sender().to_owned(); - let is_own_event = sender == own_user_id; - - let event_meta = TimelineEventMetadata { - sender, - is_own_event, - relations: event.relations().cloned(), + handle_remote_event( + raw, + own_user_id, encryption_info, - }; - let flow = Flow::Remote { - event_id: event.event_id().to_owned(), - origin_server_ts: event.origin_server_ts(), - raw_event: raw, - txn_id: event.transaction_id().map(ToOwned::to_owned), - position, - }; - - let mut timeline_meta = self.metadata.lock().await; - let timeline_items = self.items.lock_mut(); - TimelineEventHandler::new(event_meta, flow, timeline_items, &mut timeline_meta) - .handle_event(event.into()) + TimelineItemPosition::Start, + &mut self.items.lock_mut(), + &mut self.metadata.lock().await, + ); } pub(super) async fn handle_fully_read(&self, raw: Raw) { @@ -154,6 +133,117 @@ impl TimelineInner { &mut metadata.fully_read_event_in_timeline, ); } + + #[cfg(feature = "e2e-encryption")] + pub(super) async fn retry_event_decryption( + &self, + room_id: &RoomId, + olm_machine: &OlmMachine, + for_session_id: &str, + own_user_id: &UserId, + ) { + use super::EncryptedMessage; + + let utds_for_session: Vec<_> = self + .items + .lock_ref() + .iter() + .enumerate() + .filter_map(|(idx, item)| { + let event_item = &item.as_event()?; + let utd = event_item.content.as_unable_to_decrypt()?; + + match utd { + EncryptedMessage::MegolmV1AesSha2 { session_id, .. } + if session_id == for_session_id => + { + let TimelineKey::EventId(event_id) = &event_item.key else { + error!("Key for unable-to-decrypt timeline item is not an event ID"); + return None; + }; + let Some(raw) = event_item.raw.clone() else { + error!("No raw event in unable-to-decrypt timeline item"); + return None; + }; + + Some((idx, event_id.to_owned(), raw)) + } + EncryptedMessage::MegolmV1AesSha2 { .. } + | EncryptedMessage::OlmV1Curve25519AesSha2 { .. } + | EncryptedMessage::Unknown => None, + } + }) + .collect(); + + if utds_for_session.is_empty() { + return; + } + + let mut metadata_lock = self.metadata.lock().await; + for (idx, event_id, utd) in utds_for_session.iter().rev() { + let event = match olm_machine.decrypt_room_event(utd.cast_ref(), room_id).await { + Ok(ev) => ev, + Err(e) => { + info!( + %event_id, session_id = %for_session_id, + "Failed to decrypt event after receiving room key: {e}" + ); + continue; + } + }; + + // Because metadata is always locked before we attempt to lock the + // items, this will never be contended. + // Because there is an `.await` in this loop, we have to re-lock + // this mutex every iteration because holding it across `.await` + // makes the future `!Send`, which makes it not event-handler-safe. + let mut items_lock = self.items.lock_mut(); + handle_remote_event( + event.event.cast(), + own_user_id, + event.encryption_info, + TimelineItemPosition::Update(*idx), + &mut items_lock, + &mut metadata_lock, + ); + } + } +} + +fn handle_remote_event( + raw: Raw, + own_user_id: &UserId, + encryption_info: Option, + position: TimelineItemPosition, + timeline_items: &mut MutableVecLockMut<'_, Arc>, + timeline_meta: &mut MutexGuard<'_, TimelineInnerMetadata>, +) { + let event = match raw.deserialize() { + Ok(ev) => ev, + Err(_e) => { + // TODO: Add some sort of error timeline item + return; + } + }; + + let sender = event.sender().to_owned(); + let is_own_event = sender == own_user_id; + let event_meta = TimelineEventMetadata { + sender, + is_own_event, + relations: event.relations().cloned(), + encryption_info, + }; + let flow = Flow::Remote { + event_id: event.event_id().to_owned(), + origin_server_ts: event.origin_server_ts(), + raw_event: raw, + txn_id: event.transaction_id().map(ToOwned::to_owned), + position, + }; + + TimelineEventHandler::new(event_meta, flow, timeline_items, timeline_meta) + .handle_event(event.into()) } fn update_fully_read_item( @@ -260,27 +350,28 @@ enum TimelineEventKind { enum TimelineItemPosition { Start, End, + Update(usize), } // Bundles together a few things that are needed throughout the different stages // of handling an event (figuring out whether it should update an existing // timeline item, transforming that item or creating a new one, updating the // reactive Vec). -struct TimelineEventHandler<'a> { +struct TimelineEventHandler<'a, 'i> { meta: TimelineEventMetadata, flow: Flow, - timeline_items: MutableVecLockMut<'a, Arc>, + timeline_items: &'a mut MutableVecLockMut<'i, Arc>, reaction_map: &'a mut HashMap, fully_read_event: &'a mut Option, fully_read_event_in_timeline: &'a mut bool, event_added: bool, } -impl<'a> TimelineEventHandler<'a> { +impl<'a, 'i> TimelineEventHandler<'a, 'i> { fn new( event_meta: TimelineEventMetadata, flow: Flow, - timeline_items: MutableVecLockMut<'a, Arc>, + timeline_items: &'a mut MutableVecLockMut<'i, Arc>, timeline_meta: &'a mut TimelineInnerMetadata, ) -> Self { Self { @@ -332,7 +423,7 @@ impl<'a> TimelineEventHandler<'a> { fn handle_room_message_edit(&mut self, replacement: Replacement) { let event_id = &replacement.event_id; - maybe_update_timeline_item(&mut self.timeline_items, event_id, "edit", |item| { + maybe_update_timeline_item(self.timeline_items, event_id, "edit", |item| { if self.meta.sender != item.sender() { info!( %event_id, original_sender = %item.sender(), edit_sender = %self.meta.sender, @@ -373,7 +464,7 @@ impl<'a> TimelineEventHandler<'a> { fn handle_reaction(&mut self, c: ReactionEventContent) { let event_id: &EventId = &c.relates_to.event_id; - let items = &mut self.timeline_items; + let items = &mut *self.timeline_items; let did_update = maybe_update_timeline_item(items, event_id, "reaction", |item| { // Handling of reactions on redacted events is an open question. // For now, ignore reactions on redacted events like Element does. @@ -418,7 +509,7 @@ impl<'a> TimelineEventHandler<'a> { if let Some((sender, rel)) = self.reaction_map.remove(&TimelineKey::EventId(redacts.clone())) { - let items = &mut self.timeline_items; + let items = &mut *self.timeline_items; did_update = maybe_update_timeline_item(items, &rel.event_id, "redaction", |item| { let mut reactions = item.reactions.clone(); @@ -470,7 +561,7 @@ impl<'a> TimelineEventHandler<'a> { // Even if the event being redacted is a reaction (found in // `reaction_map`), it can still be present in the timeline items // directly with the raw event timeline feature (not yet implemented). - let items = &mut self.timeline_items; + let items = &mut *self.timeline_items; did_update |= update_timeline_item(items, &redacts, "redaction", |item| item.to_redacted()); if !did_update { @@ -502,7 +593,7 @@ impl<'a> TimelineEventHandler<'a> { } Flow::Remote { txn_id, event_id, position, raw_event, .. } => { if let Some(txn_id) = txn_id { - if let Some((idx, _old_item)) = find_event(&self.timeline_items, txn_id) { + if let Some((idx, _old_item)) = find_event(self.timeline_items, txn_id) { // TODO: Check whether anything is different about the // old and new item? self.timeline_items.set_cloned(idx, item); @@ -516,7 +607,7 @@ impl<'a> TimelineEventHandler<'a> { } } - if let Some((idx, old_item)) = find_event(&self.timeline_items, event_id) { + if let Some((idx, old_item)) = find_event(self.timeline_items, event_id) { warn!( ?item, ?old_item, @@ -534,6 +625,7 @@ impl<'a> TimelineEventHandler<'a> { match position { TimelineItemPosition::Start => self.timeline_items.insert_cloned(0, item), TimelineItemPosition::End => self.timeline_items.push_cloned(item), + TimelineItemPosition::Update(idx) => self.timeline_items.set_cloned(*idx, item), } } } @@ -541,7 +633,7 @@ impl<'a> TimelineEventHandler<'a> { // See if we got the event corresponding to the fully read marker now. if !*self.fully_read_event_in_timeline { update_fully_read_item( - &mut self.timeline_items, + self.timeline_items, self.fully_read_event.as_deref(), self.fully_read_event_in_timeline, ); diff --git a/crates/matrix-sdk/src/room/timeline/event_item.rs b/crates/matrix-sdk/src/room/timeline/event_item.rs index cd56bc263..1d5420909 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item.rs +++ b/crates/matrix-sdk/src/room/timeline/event_item.rs @@ -310,6 +310,15 @@ impl TimelineItemContent { _ => None, } } + + /// If `self` is of the [`UnableToDecrypt`][Self::UnableToDecrypt] variant, + /// return the inner [`EncryptedMessage`]. + pub fn as_unable_to_decrypt(&self) -> Option<&EncryptedMessage> { + match self { + Self::UnableToDecrypt(v) => Some(v), + _ => None, + } + } } /// An `m.room.message` event or extensible event, including edits. diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index af87b1a7b..c04e605c2 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -28,7 +28,7 @@ use ruma::{ assign, events::{ fully_read::FullyReadEventContent, reaction::Relation as AnnotationRelation, - AnyMessageLikeEventContent, + room_key::ToDeviceRoomKeyEvent, AnyMessageLikeEventContent, }, OwnedEventId, OwnedUserId, TransactionId, UInt, }; @@ -38,7 +38,7 @@ use super::{Joined, Room}; use crate::{ event_handler::EventHandlerDropGuard, room::{self, MessagesOptions}, - Result, + Client, Result, }; mod event_handler; @@ -68,6 +68,7 @@ pub struct Timeline { _end_token: StdMutex>, _timeline_event_handler_guard: EventHandlerDropGuard, _fully_read_handler_guard: EventHandlerDropGuard, + _room_key_handler_guard: EventHandlerDropGuard, } #[derive(Clone, Debug, Default)] @@ -125,6 +126,38 @@ impl Timeline { }); let _fully_read_handler_guard = room.client.event_handler_drop_guard(fully_read_handle); + // Not using room.add_event_handler here because RoomKey events are + // to-device events that are not received in the context of a room. + let room_id = room.room_id().to_owned(); + let room_key_handle = room.client.add_event_handler({ + let inner = inner.clone(); + move |event: ToDeviceRoomKeyEvent, client: Client| { + let inner = inner.clone(); + let room_id = room_id.clone(); + async move { + if event.content.room_id != room_id { + return; + } + + let Some(olm_machine) = client.olm_machine() else { + error!("The olm machine isn't yet available"); + return; + }; + + let session_id = event.content.session_id; + let Some(own_user_id) = client.user_id() else { + error!("The user's own ID isn't available"); + return; + }; + + inner + .retry_event_decryption(&room_id, olm_machine, &session_id, own_user_id) + .await; + } + } + }); + let _room_key_handler_guard = room.client.event_handler_drop_guard(room_key_handle); + Timeline { inner, room: room.clone(), @@ -132,6 +165,7 @@ impl Timeline { _end_token: StdMutex::new(None), _timeline_event_handler_guard, _fully_read_handler_guard, + _room_key_handler_guard, } } From 317965995a069b7144aac56c2cbcf32553101c9e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 18 Nov 2022 12:03:13 +0100 Subject: [PATCH 51/78] test(sdk): Add a test for retrying decryption of timeline items --- crates/matrix-sdk/src/room/timeline/tests.rs | 74 +++++++++++++++++--- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index 82a61a4db..7a35d3362 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -23,6 +23,7 @@ use assert_matches::assert_matches; use futures_core::Stream; use futures_signals::signal_vec::{SignalVecExt, VecDiff}; use futures_util::StreamExt; +use matrix_sdk_base::crypto::OlmMachine; use matrix_sdk_test::async_test; use once_cell::sync::Lazy; use ruma::{ @@ -38,6 +39,7 @@ use ruma::{ }, MessageLikeEventContent, OriginalSyncMessageLikeEvent, }, + room_id, serde::Raw, server_name, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId, }; @@ -145,19 +147,48 @@ async fn edit_redacted() { assert_eq!(timeline.inner.items.lock_ref().len(), 1); } +#[cfg(not(target_arch = "wasm32"))] #[async_test] async fn unable_to_decrypt() { + use std::io::Cursor; + + use matrix_sdk_base::crypto::decrypt_room_key_export; + + const SESSION_ID: &str = "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU"; + const SESSION_KEY: &[u8] = b"\ + -----BEGIN MEGOLM SESSION DATA-----\n\ + ASKcWoiAVUM97482UAi83Avce62hSLce7i5JhsqoF6xeAAAACqt2Cg3nyJPRWTTMXxXH7TXnkfdlmBXbQtq5\ + bpHo3LRijcq2Gc6TXilESCmJN14pIsfKRJrWjZ0squ/XsoTFytuVLWwkNaW3QF6obeg2IoVtJXLMPdw3b2vO\ + vgwGY3OMP0XafH13j1vcb6YLzvgLkZQLnYvd47hv3yK/9GmKS9tokuaQ7dCVYckYcIOS09EDTs70YdxUd5WG\ + rQynATCLFP1p/NAGv70r9MK7Cy/mNpjD0r4qC7UEDIoi1kOWzHgnLo19wtvwsb8Fg8ATxcs3Wmtj8hIUYpDx\ + ia4sM10zbytUuaPUAfCDf42IyxdmOnGe1CueXhgI71y+RW0s0argNqUt7jB70JT0o9CyX6UBGRaqLk2MPY9T\ + hUu5J8X3UgIa6rcbWigzohzWm9rdbEHFrSWqjpfQYMaAKQQgETrjSy4XTrp2RhC2oNqG/hylI4ab+F4X6fpH\ + DYP1NqNMP5g36xNu7LhDnrUB5qsPjYOmWORxGLfudpF3oLYCSlr3DgHqEIB6HjQblLZ3KQuPBse3zxyROTnS\ + AhdPH4a/z1wioFtKNVph3hecsiKEdqnz4Y2coSIdhz58mJ9JWNQoFAENE5CSsoEZAGvafYZVpW4C75YY2zq1\ + wIeiFi1dT43/jLAUGkslsi1VvnyfUu8qO404RxYO3XHoGLMFoFLOO+lZ+VGci2Vz10AhxJhEBHxRKxw4k2uB\ + HztoSJUr/2Y\n\ + -----END MEGOLM SESSION DATA-----"; + let timeline = TestTimeline::new(&ALICE); + let mut stream = timeline.stream(); + timeline .handle_live_message_event( &BOB, RoomEncryptedEventContent::new( EncryptedEventScheme::MegolmV1AesSha2( MegolmV1AesSha2ContentInit { - ciphertext: "This can't be decrypted".to_owned(), - sender_key: "whatever".to_owned(), - device_id: "MyDevice".into(), - session_id: "MySession".into(), + ciphertext: "\ + AwgAEtABPRMavuZMDJrPo6pGQP4qVmpcuapuXtzKXJyi3YpEsjSWdzuRKIgJzD4P\ + cSqJM1A8kzxecTQNJsC5q22+KSFEPxPnI4ltpm7GFowSoPSW9+bFdnlfUzEP1jPq\ + YevHAsMJp2fRKkzQQbPordrUk1gNqEpGl4BYFeRqKl9GPdKFwy45huvQCLNNueql\ + CFZVoYMuhxrfyMiJJAVNTofkr2um2mKjDTlajHtr39pTG8k0eOjSXkLOSdZvNOMz\ + hGhSaFNeERSA2G2YbeknOvU7MvjiO0AKuxaAe1CaVhAI14FCgzrJ8g0y5nly+n7x\ + QzL2G2Dn8EoXM5Iqj8W99iokQoVsSrUEnaQ1WnSIfewvDDt4LCaD/w7PGETMCQ" + .to_owned(), + sender_key: "DeHIg4gwhClxzFYcmNntPNF9YtsdZbmMy8+3kzCMXHA".to_owned(), + device_id: "NLAZCWIOCO".into(), + session_id: SESSION_ID.into(), } .into(), ), @@ -165,16 +196,43 @@ async fn unable_to_decrypt() { ), ) .await; - let timeline_items = timeline.inner.items.lock_ref(); - assert_eq!(timeline_items.len(), 1); - let event = timeline_items[0].as_event().unwrap(); + + assert_eq!(timeline.inner.items.lock_ref().len(), 1); + + let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); + let event = item.as_event().unwrap(); let session_id = assert_matches!( event.content(), TimelineItemContent::UnableToDecrypt( EncryptedMessage::MegolmV1AesSha2 { session_id, .. }, ) => session_id ); - assert_eq!(session_id, "MySession"); + assert_eq!(session_id, SESSION_ID); + + let own_user_id = user_id!("@example:morheus.localhost"); + let exported_keys = decrypt_room_key_export(Cursor::new(SESSION_KEY), "1234").unwrap(); + + let olm_machine = OlmMachine::new(own_user_id, "SomeDeviceId".into()).await; + olm_machine.import_room_keys(exported_keys, false, |_, _| {}).await.unwrap(); + + timeline + .inner + .retry_event_decryption( + room_id!("!DovneieKSTkdHKpIXy:morpheus.localhost"), + &olm_machine, + SESSION_ID, + own_user_id, + ) + .await; + + assert_eq!(timeline.inner.items.lock_ref().len(), 1); + + let item = + assert_matches!(stream.next().await, Some(VecDiff::UpdateAt { index: 0, value }) => value); + let event = item.as_event().unwrap(); + assert_matches!(&event.encryption_info, Some(_)); + let text = assert_matches!(event.content(), TimelineItemContent::Message(msg) => msg.body()); + assert_eq!(text, "It's a secret to everybody"); } struct TestTimeline { From 4bafb3818b9bceeed5f3f6c90ef9c3bf21253c34 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 18 Nov 2022 13:12:09 +0100 Subject: [PATCH 52/78] feat(sdk): Add Timeline::retry_decryption --- .../src/room/timeline/event_handler.rs | 12 +++-- crates/matrix-sdk/src/room/timeline/mod.rs | 49 ++++++++++++++++++- crates/matrix-sdk/src/room/timeline/tests.rs | 4 +- 3 files changed, 57 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index cd91929b4..eb037d457 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +#[cfg(feature = "e2e-encryption")] +use std::collections::BTreeSet; use std::{collections::HashMap, sync::Arc}; use futures_signals::signal_vec::MutableVecLockMut; @@ -139,7 +141,7 @@ impl TimelineInner { &self, room_id: &RoomId, olm_machine: &OlmMachine, - for_session_id: &str, + session_ids: BTreeSet<&str>, own_user_id: &UserId, ) { use super::EncryptedMessage; @@ -155,7 +157,7 @@ impl TimelineInner { match utd { EncryptedMessage::MegolmV1AesSha2 { session_id, .. } - if session_id == for_session_id => + if session_ids.contains(session_id.as_str()) => { let TimelineKey::EventId(event_id) = &event_item.key else { error!("Key for unable-to-decrypt timeline item is not an event ID"); @@ -166,7 +168,7 @@ impl TimelineInner { return None; }; - Some((idx, event_id.to_owned(), raw)) + Some((idx, event_id.to_owned(), session_id.to_owned(), raw)) } EncryptedMessage::MegolmV1AesSha2 { .. } | EncryptedMessage::OlmV1Curve25519AesSha2 { .. } @@ -180,12 +182,12 @@ impl TimelineInner { } let mut metadata_lock = self.metadata.lock().await; - for (idx, event_id, utd) in utds_for_session.iter().rev() { + for (idx, event_id, session_id, utd) in utds_for_session.iter().rev() { let event = match olm_machine.decrypt_room_event(utd.cast_ref(), room_id).await { Ok(ev) => ev, Err(e) => { info!( - %event_id, session_id = %for_session_id, + %event_id, %session_id, "Failed to decrypt event after receiving room key: {e}" ); continue; diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index c04e605c2..5e15c0746 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -18,6 +18,7 @@ use std::{ collections::HashMap, + iter, sync::{Arc, Mutex as StdMutex}, }; @@ -151,7 +152,12 @@ impl Timeline { }; inner - .retry_event_decryption(&room_id, olm_machine, &session_id, own_user_id) + .retry_event_decryption( + &room_id, + olm_machine, + iter::once(session_id.as_str()).collect(), + own_user_id, + ) .await; } } @@ -198,6 +204,47 @@ impl Timeline { Ok(outcome) } + /// Retry decryption of previously un-decryptable events given a list of + /// session IDs whose keys have been imported. + /// + /// # Example + /// + /// ```no_run + /// # use std::{path::PathBuf, time::Duration}; + /// # use matrix_sdk::{ + /// # Client, config::SyncSettings, + /// # room::timeline::Timeline, ruma::room_id, + /// # }; + /// # async { + /// # let mut client: Client = todo!(); + /// # let room_id = ruma::room_id!("!example:example.org"); + /// # let timeline: Timeline = todo!(); + /// let path = PathBuf::from("/home/example/e2e-keys.txt"); + /// let result = + /// client.encryption().import_room_keys(path, "secret-passphrase").await?; + /// + /// // Given a timeline for a specific room_id + /// if let Some(keys_for_users) = result.keys.get(room_id) { + /// let session_ids = keys_for_users.values().flatten(); + /// timeline.retry_decryption(session_ids).await; + /// } + /// # anyhow::Ok(()) }; + /// ``` + #[cfg(feature = "e2e-encryption")] + pub async fn retry_decryption<'a, S: AsRef + 'a>( + &'a self, + session_ids: impl IntoIterator, + ) { + self.inner + .retry_event_decryption( + self.room.room_id(), + self.room.client.olm_machine().expect("Olm machine wasn't started"), + session_ids.into_iter().map(AsRef::as_ref).collect(), + self.room.own_user_id(), + ) + .await; + } + /// Get a signal of the timeline's items. /// /// You can poll this signal to receive updates, the first of which will diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index 7a35d3362..33d63e508 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -150,7 +150,7 @@ async fn edit_redacted() { #[cfg(not(target_arch = "wasm32"))] #[async_test] async fn unable_to_decrypt() { - use std::io::Cursor; + use std::{io::Cursor, iter}; use matrix_sdk_base::crypto::decrypt_room_key_export; @@ -220,7 +220,7 @@ async fn unable_to_decrypt() { .retry_event_decryption( room_id!("!DovneieKSTkdHKpIXy:morpheus.localhost"), &olm_machine, - SESSION_ID, + iter::once(SESSION_ID).collect(), own_user_id, ) .await; From 6b363120ef18d0ae0fe43c73de487df949ddf185 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 18 Nov 2022 13:18:07 +0100 Subject: [PATCH 53/78] fix(sdk): Fix errors & warnings w/ experimental-timeline, w/o e2e-encryption --- .../src/room/timeline/event_handler.rs | 2 ++ crates/matrix-sdk/src/room/timeline/mod.rs | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index eb037d457..891f4ce3f 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -352,6 +352,7 @@ enum TimelineEventKind { enum TimelineItemPosition { Start, End, + #[cfg(feature = "e2e-encryption")] Update(usize), } @@ -627,6 +628,7 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { match position { TimelineItemPosition::Start => self.timeline_items.insert_cloned(0, item), TimelineItemPosition::End => self.timeline_items.push_cloned(item), + #[cfg(feature = "e2e-encryption")] TimelineItemPosition::Update(idx) => self.timeline_items.set_cloned(*idx, item), } } diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index 5e15c0746..d3e918a17 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -18,7 +18,6 @@ use std::{ collections::HashMap, - iter, sync::{Arc, Mutex as StdMutex}, }; @@ -29,7 +28,7 @@ use ruma::{ assign, events::{ fully_read::FullyReadEventContent, reaction::Relation as AnnotationRelation, - room_key::ToDeviceRoomKeyEvent, AnyMessageLikeEventContent, + AnyMessageLikeEventContent, }, OwnedEventId, OwnedUserId, TransactionId, UInt, }; @@ -39,7 +38,7 @@ use super::{Joined, Room}; use crate::{ event_handler::EventHandlerDropGuard, room::{self, MessagesOptions}, - Client, Result, + Result, }; mod event_handler; @@ -69,6 +68,7 @@ pub struct Timeline { _end_token: StdMutex>, _timeline_event_handler_guard: EventHandlerDropGuard, _fully_read_handler_guard: EventHandlerDropGuard, + #[cfg(feature = "e2e-encryption")] _room_key_handler_guard: EventHandlerDropGuard, } @@ -129,8 +129,16 @@ impl Timeline { // Not using room.add_event_handler here because RoomKey events are // to-device events that are not received in the context of a room. + #[cfg(feature = "e2e-encryption")] let room_id = room.room_id().to_owned(); + #[cfg(feature = "e2e-encryption")] let room_key_handle = room.client.add_event_handler({ + use std::iter; + + use ruma::events::room_key::ToDeviceRoomKeyEvent; + + use crate::Client; + let inner = inner.clone(); move |event: ToDeviceRoomKeyEvent, client: Client| { let inner = inner.clone(); @@ -162,6 +170,7 @@ impl Timeline { } } }); + #[cfg(feature = "e2e-encryption")] let _room_key_handler_guard = room.client.event_handler_drop_guard(room_key_handle); Timeline { @@ -171,6 +180,7 @@ impl Timeline { _end_token: StdMutex::new(None), _timeline_event_handler_guard, _fully_read_handler_guard, + #[cfg(feature = "e2e-encryption")] _room_key_handler_guard, } } From ec5306978e0672de6b83320397f88045b56d88d2 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 17 Nov 2022 18:02:12 +0100 Subject: [PATCH 54/78] refactor(sdk): Simplify signature of handle_sync_response --- crates/matrix-sdk/src/sliding_sync.rs | 3 ++- crates/matrix-sdk/src/sync.rs | 12 +++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk/src/sliding_sync.rs b/crates/matrix-sdk/src/sliding_sync.rs index af905370d..cf3a69980 100644 --- a/crates/matrix-sdk/src/sliding_sync.rs +++ b/crates/matrix-sdk/src/sliding_sync.rs @@ -1103,6 +1103,7 @@ impl Client { ) -> Result { let response = self.base_client().process_sliding_sync(response).await?; tracing::debug!("done processing on base_client"); - self.handle_sync_response(response).await + self.handle_sync_response(&response).await?; + Ok(response) } } diff --git a/crates/matrix-sdk/src/sync.rs b/crates/matrix-sdk/src/sync.rs index 721425000..5f1b9b793 100644 --- a/crates/matrix-sdk/src/sync.rs +++ b/crates/matrix-sdk/src/sync.rs @@ -17,14 +17,12 @@ impl Client { response: sync_events::v3::Response, ) -> Result { let response = self.base_client().receive_sync_response(response).await?; - self.handle_sync_response(response).await + self.handle_sync_response(&response).await?; + Ok(response) } #[tracing::instrument(skip(self, response))] - pub(crate) async fn handle_sync_response( - &self, - response: SyncResponse, - ) -> Result { + pub(crate) async fn handle_sync_response(&self, response: &SyncResponse) -> Result<()> { let SyncResponse { next_batch: _, rooms, @@ -35,7 +33,7 @@ impl Client { device_one_time_keys_count: _, ambiguity_changes: _, notifications, - } = &response; + } = response; self.handle_sync_events(HandlerKind::GlobalAccountData, &None, account_data).await?; self.handle_sync_events(HandlerKind::Presence, &None, &presence.events).await?; @@ -110,7 +108,7 @@ impl Client { fut.await; } - Ok(response) + Ok(()) } async fn sleep() { From 6329dbe6c2e159a36b0562bba227980525654eb7 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 17 Nov 2022 18:07:43 +0100 Subject: [PATCH 55/78] refactor!: Split SyncResponse into two types - matrix_sdk_base::sync::SyncResponse is the internal representation that we can update to account for sliding sync - matrix_sdk::sync::SyncResponse is `/v3/`-specific and should not change --- crates/matrix-sdk-base/src/client.rs | 3 +- crates/matrix-sdk-base/src/sliding_sync.rs | 1 - crates/matrix-sdk-base/src/sync.rs | 17 ++--- crates/matrix-sdk/src/client/mod.rs | 11 +-- crates/matrix-sdk/src/sync.rs | 79 ++++++++++++++++++++-- 5 files changed, 84 insertions(+), 27 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 8d458ffd3..26a9b9586 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -654,7 +654,7 @@ impl BaseClient { // that case we already received this response and there's nothing to // do. if self.store.sync_token.read().await.as_ref() == Some(&next_batch) { - return Ok(SyncResponse::new(next_batch)); + return Ok(SyncResponse::default()); } let now = Instant::now(); @@ -845,7 +845,6 @@ impl BaseClient { info!("Processed a sync response in {:?}", now.elapsed()); let response = SyncResponse { - next_batch, rooms: new_rooms, presence, account_data: account_data.events, diff --git a/crates/matrix-sdk-base/src/sliding_sync.rs b/crates/matrix-sdk-base/src/sliding_sync.rs index f4297cc50..cacd6cd60 100644 --- a/crates/matrix-sdk-base/src/sliding_sync.rs +++ b/crates/matrix-sdk-base/src/sliding_sync.rs @@ -232,7 +232,6 @@ impl BaseClient { tracing::debug!("applied changes"); Ok(SyncResponse { - next_batch: "test".into(), rooms: new_rooms, ambiguity_changes: AmbiguityChanges { changes: ambiguity_cache.changes }, notifications: changes.notifications, diff --git a/crates/matrix-sdk-base/src/sync.rs b/crates/matrix-sdk-base/src/sync.rs index 8cc3277ae..91473b575 100644 --- a/crates/matrix-sdk-base/src/sync.rs +++ b/crates/matrix-sdk-base/src/sync.rs @@ -33,12 +33,12 @@ use serde::{Deserialize, Serialize}; use crate::deserialized_responses::AmbiguityChanges; -/// The processed response of a `/sync` request. +/// Internal representation of a `/sync` response. +/// +/// This type is intended to be applicable regardless of the endpoint used for +/// syncing. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct SyncResponse { - /// The batch token to supply in the `since` param of the next `/sync` - /// request. - pub next_batch: String, /// Updates to rooms. pub rooms: Rooms, /// Updates to the presence status of other users. @@ -60,15 +60,6 @@ pub struct SyncResponse { pub notifications: BTreeMap>, } -impl SyncResponse { - /// Creates a new, empty `SyncResponse`. - /// - /// Equivalent to `SyncResponse::default()`. - pub fn new(next_batch: String) -> Self { - Self { next_batch, ..Default::default() } - } -} - /// Updates to rooms in a [`SyncResponse`]. #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Rooms { diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index d343eb438..4a811e6ea 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -27,8 +27,8 @@ use dashmap::DashMap; use futures_core::stream::Stream; use futures_signals::signal::Signal; use matrix_sdk_base::{ - sync::SyncResponse, BaseClient, RoomType, SendOutsideWasm, Session, SessionMeta, SessionTokens, - StateStore, SyncOutsideWasm, + BaseClient, RoomType, SendOutsideWasm, Session, SessionMeta, SessionTokens, StateStore, + SyncOutsideWasm, }; use matrix_sdk_common::{ instant::Instant, @@ -81,7 +81,9 @@ use crate::{ EventHandler, EventHandlerDropGuard, EventHandlerHandle, EventHandlerStore, SyncEvent, }, http_client::HttpClient, - room, Account, Error, Media, RefreshTokenError, Result, RumaApiError, + room, + sync::SyncResponse, + Account, Error, Media, RefreshTokenError, Result, RumaApiError, }; mod builder; @@ -2030,6 +2032,7 @@ impl Client { } let response = self.send(request, Some(request_config)).await?; + let next_batch = response.next_batch.clone(); let response = self.process_sync(response).await?; #[cfg(feature = "e2e-encryption")] @@ -2039,7 +2042,7 @@ impl Client { self.inner.sync_beat.notify(usize::MAX); - Ok(response) + Ok(SyncResponse::new(next_batch, response)) } /// Repeatedly synchronize the client state with the server. diff --git a/crates/matrix-sdk/src/sync.rs b/crates/matrix-sdk/src/sync.rs index 5f1b9b793..1f4d3bde5 100644 --- a/crates/matrix-sdk/src/sync.rs +++ b/crates/matrix-sdk/src/sync.rs @@ -1,30 +1,95 @@ //! The SDK's representation of the result of a `/sync` request. -use std::time::Duration; +use std::{collections::BTreeMap, time::Duration}; -use matrix_sdk_base::instant::Instant; pub use matrix_sdk_base::sync::*; -use ruma::api::client::sync::sync_events; +use matrix_sdk_base::{ + deserialized_responses::AmbiguityChanges, instant::Instant, + sync::SyncResponse as BaseSyncResponse, +}; +use ruma::{ + api::client::{ + push::get_notifications::v3::Notification, + sync::sync_events::{self, v3::Presence, DeviceLists}, + }, + events::{AnyGlobalAccountDataEvent, AnyToDeviceEvent}, + serde::Raw, + DeviceKeyAlgorithm, OwnedRoomId, +}; +use serde::{Deserialize, Serialize}; use tracing::{error, warn}; use crate::{event_handler::HandlerKind, Client, Result}; +/// The processed response of a `/sync` request. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct SyncResponse { + /// The batch token to supply in the `since` param of the next `/sync` + /// request. + pub next_batch: String, + /// Updates to rooms. + pub rooms: Rooms, + /// Updates to the presence status of other users. + pub presence: Presence, + /// The global private data created by this user. + pub account_data: Vec>, + /// Messages sent directly between devices. + pub to_device_events: Vec>, + /// Information on E2E device updates. + /// + /// Only present on an incremental sync. + pub device_lists: DeviceLists, + /// For each key algorithm, the number of unclaimed one-time keys + /// currently held on the server for a device. + pub device_one_time_keys_count: BTreeMap, + /// Collection of ambiguity changes that room member events trigger. + pub ambiguity_changes: AmbiguityChanges, + /// New notifications per room. + pub notifications: BTreeMap>, +} + +impl SyncResponse { + pub(crate) fn new(next_batch: String, base_response: BaseSyncResponse) -> Self { + let BaseSyncResponse { + rooms, + presence, + account_data, + to_device_events, + device_lists, + device_one_time_keys_count, + ambiguity_changes, + notifications, + } = base_response; + + Self { + next_batch, + rooms, + presence, + account_data, + to_device_events, + device_lists, + device_one_time_keys_count, + ambiguity_changes, + notifications, + } + } +} + /// Internal functionality related to getting events from the server /// (`sync_events` endpoint) impl Client { pub(crate) async fn process_sync( &self, response: sync_events::v3::Response, - ) -> Result { + ) -> Result { let response = self.base_client().receive_sync_response(response).await?; self.handle_sync_response(&response).await?; Ok(response) } #[tracing::instrument(skip(self, response))] - pub(crate) async fn handle_sync_response(&self, response: &SyncResponse) -> Result<()> { - let SyncResponse { - next_batch: _, + pub(crate) async fn handle_sync_response(&self, response: &BaseSyncResponse) -> Result<()> { + let BaseSyncResponse { rooms, presence, account_data, From 785a3349ab53851d71b2951113c335d96808f57b Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 17 Nov 2022 18:47:26 +0100 Subject: [PATCH 56/78] refactor(sdk)!: Make sync_token accessor private --- crates/matrix-sdk/src/client/mod.rs | 2 +- crates/matrix-sdk/tests/integration/client.rs | 5 +-- .../tests/integration/room/common.rs | 40 +++++++++---------- examples/command_bot/src/main.rs | 4 +- examples/custom_events/src/main.rs | 4 +- examples/getting_started/src/main.rs | 4 +- examples/image_bot/src/main.rs | 4 +- examples/wasm_command_bot/src/lib.rs | 4 +- .../src/tests/redaction.rs | 22 +++++----- 9 files changed, 42 insertions(+), 47 deletions(-) diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index 4a811e6ea..017288037 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -2338,7 +2338,7 @@ impl Client { /// Get the current, if any, sync token of the client. /// This will be None if the client didn't sync at least once. - pub async fn sync_token(&self) -> Option { + pub(crate) async fn sync_token(&self) -> Option { self.inner.base_client.sync_token().await } diff --git a/crates/matrix-sdk/tests/integration/client.rs b/crates/matrix-sdk/tests/integration/client.rs index f81eed9fa..4c9fdb60d 100644 --- a/crates/matrix-sdk/tests/integration/client.rs +++ b/crates/matrix-sdk/tests/integration/client.rs @@ -263,8 +263,6 @@ async fn sync() { let response = client.sync_once(sync_settings).await.unwrap(); assert_ne!(response.next_batch, ""); - - assert!(client.sync_token().await.is_some()); } #[async_test] @@ -370,7 +368,7 @@ async fn join_leave_room() { let room = client.get_joined_room(room_id); assert!(room.is_none()); - client.sync_once(SyncSettings::default()).await.unwrap(); + let sync_token = client.sync_once(SyncSettings::default()).await.unwrap().next_batch; let room = client.get_left_room(room_id); assert!(room.is_none()); @@ -378,7 +376,6 @@ async fn join_leave_room() { let room = client.get_joined_room(room_id); assert!(room.is_some()); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, &*test_json::LEAVE_SYNC_EVENT, Some(sync_token.clone())).await; client.sync_once(SyncSettings::default().token(sync_token)).await.unwrap(); diff --git a/crates/matrix-sdk/tests/integration/room/common.rs b/crates/matrix-sdk/tests/integration/room/common.rs index b1e88e673..3219926aa 100644 --- a/crates/matrix-sdk/tests/integration/room/common.rs +++ b/crates/matrix-sdk/tests/integration/room/common.rs @@ -62,14 +62,13 @@ async fn room_names() { let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000)); - let _response = client.sync_once(sync_settings).await.unwrap(); + let sync_token = client.sync_once(sync_settings).await.unwrap().next_batch; assert_eq!(client.rooms().len(), 1); let room = client.get_joined_room(&test_json::DEFAULT_SYNC_ROOM_ID).unwrap(); assert_eq!(DisplayName::Aliased("tutorial".to_owned()), room.display_name().await.unwrap()); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, &*test_json::INVITE_SYNC, Some(sync_token.clone())).await; let _response = client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); @@ -203,7 +202,7 @@ async fn room_route() { ); mock_sync(&server, ev_builder.build_json_sync_response(), None).await; - client.sync_once(SyncSettings::new()).await.unwrap(); + let sync_token = client.sync_once(SyncSettings::new()).await.unwrap().next_batch; let room = client.get_room(room_id).unwrap(); let route = room.route().await.unwrap(); @@ -214,9 +213,9 @@ async fn room_route() { ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk( bulk_room_members(batch, 0..1, "localhost", &MembershipState::Join), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; - client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); + let sync_token = + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap().next_batch; let route = room.route().await.unwrap(); assert_eq!(route.len(), 1); @@ -227,9 +226,9 @@ async fn room_route() { ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk( bulk_room_members(batch, 0..15, "notarealhs", &MembershipState::Join), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; - client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); + let sync_token = + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap().next_batch; let route = room.route().await.unwrap(); assert_eq!(route.len(), 2); @@ -241,9 +240,9 @@ async fn room_route() { ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk( bulk_room_members(batch, 0..5, "mymatrix", &MembershipState::Join), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; - client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); + let sync_token = + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap().next_batch; let route = room.route().await.unwrap(); assert_eq!(route.len(), 3); @@ -256,9 +255,9 @@ async fn room_route() { ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_state_bulk( bulk_room_members(batch, 0..10, "yourmatrix", &MembershipState::Join), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; - client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); + let sync_token = + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap().next_batch; let route = room.route().await.unwrap(); assert_eq!(route.len(), 3); @@ -281,9 +280,9 @@ async fn room_route() { "type": "m.room.power_levels", })), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; - client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); + let sync_token = + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap().next_batch; let route = room.route().await.unwrap(); assert_eq!(route.len(), 3); @@ -307,9 +306,9 @@ async fn room_route() { "type": "m.room.power_levels", })), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; - client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); + let sync_token = + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap().next_batch; let route = room.route().await.unwrap(); assert_eq!(route.len(), 3); @@ -332,7 +331,6 @@ async fn room_route() { "type": "m.room.server_acl", })), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); @@ -366,7 +364,7 @@ async fn room_permalink() { )), ); mock_sync(&server, ev_builder.build_json_sync_response(), None).await; - client.sync_once(SyncSettings::new()).await.unwrap(); + let sync_token = client.sync_once(SyncSettings::new()).await.unwrap().next_batch; let room = client.get_room(room_id).unwrap(); assert_eq!( @@ -391,9 +389,9 @@ async fn room_permalink() { "type": "m.room.canonical_alias", })), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; - client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); + let sync_token = + client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap().next_batch; assert_eq!( room.matrix_to_permalink().await.unwrap().to_string(), @@ -415,7 +413,6 @@ async fn room_permalink() { "type": "m.room.canonical_alias", })), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); @@ -457,7 +454,7 @@ async fn room_event_permalink() { )), ); mock_sync(&server, ev_builder.build_json_sync_response(), None).await; - client.sync_once(SyncSettings::new()).await.unwrap(); + let sync_token = client.sync_once(SyncSettings::new()).await.unwrap().next_batch; let room = client.get_room(room_id).unwrap(); assert_eq!( @@ -483,7 +480,6 @@ async fn room_event_permalink() { "type": "m.room.canonical_alias", })), )); - let sync_token = client.sync_token().await.unwrap(); mock_sync(&server, ev_builder.build_json_sync_response(), Some(sync_token.clone())).await; client.sync_once(SyncSettings::new().token(sync_token)).await.unwrap(); diff --git a/examples/command_bot/src/main.rs b/examples/command_bot/src/main.rs index 07e85e28e..676dd7293 100644 --- a/examples/command_bot/src/main.rs +++ b/examples/command_bot/src/main.rs @@ -62,14 +62,14 @@ async fn login_and_sync( // An initial sync to set up state and so our bot doesn't respond to old // messages. If the `StateStore` finds saved state in the location given the // initial sync will be skipped in favor of loading state from the store - client.sync_once(SyncSettings::default()).await.unwrap(); + let response = client.sync_once(SyncSettings::default()).await.unwrap(); // add our CommandBot to be notified of incoming messages, we do this after the // initial sync to avoid responding to messages before the bot was running. client.add_event_handler(on_room_message); // since we called `sync_once` before we entered our sync loop we must pass // that sync token to `sync` - let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + let settings = SyncSettings::default().token(response.next_batch); // this keeps state from the server streaming in to CommandBot via the // EventHandler trait client.sync(settings).await?; diff --git a/examples/custom_events/src/main.rs b/examples/custom_events/src/main.rs index 3b9315bd8..d3745aebb 100644 --- a/examples/custom_events/src/main.rs +++ b/examples/custom_events/src/main.rs @@ -82,7 +82,7 @@ async fn on_ping_event(event: SyncPingEvent, room: Room) { async fn sync_loop(client: Client) -> anyhow::Result<()> { // invite acceptance as in the getting-started-client client.add_event_handler(on_stripped_state_member); - client.sync_once(SyncSettings::default()).await.unwrap(); + let response = client.sync_once(SyncSettings::default()).await.unwrap(); // our customisation: // - send `PingEvent` on `!ping` in any room @@ -90,7 +90,7 @@ async fn sync_loop(client: Client) -> anyhow::Result<()> { // - send `AckEvent` on `PingEvent` in any room client.add_event_handler(on_ping_event); - let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + let settings = SyncSettings::default().token(response.next_batch); client.sync(settings).await?; // this essentially loops until we kill the bot Ok(()) diff --git a/examples/getting_started/src/main.rs b/examples/getting_started/src/main.rs index c342ed673..475569a3c 100644 --- a/examples/getting_started/src/main.rs +++ b/examples/getting_started/src/main.rs @@ -90,7 +90,7 @@ async fn login_and_sync( // An initial sync to set up state and so our bot doesn't respond to old // messages. If the `StateStore` finds saved state in the location given the // initial sync will be skipped in favor of loading state from the store - client.sync_once(SyncSettings::default()).await.unwrap(); + let sync_token = client.sync_once(SyncSettings::default()).await.unwrap().next_batch; // now that we've synced, let's attach a handler for incoming room messages, so // we can react on it @@ -98,7 +98,7 @@ async fn login_and_sync( // since we called `sync_once` before we entered our sync loop we must pass // that sync token to `sync` - let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + let settings = SyncSettings::default().token(sync_token); // this keeps state from the server streaming in to the bot via the // EventHandler trait client.sync(settings).await?; // this essentially loops until we kill the bot diff --git a/examples/image_bot/src/main.rs b/examples/image_bot/src/main.rs index 9e506023c..755a9c50e 100644 --- a/examples/image_bot/src/main.rs +++ b/examples/image_bot/src/main.rs @@ -39,11 +39,11 @@ async fn login_and_sync( .send() .await?; - client.sync_once(SyncSettings::default()).await.unwrap(); + let response = client.sync_once(SyncSettings::default()).await.unwrap(); client.add_event_handler(move |ev, room| on_room_message(ev, room, image.clone())); - let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + let settings = SyncSettings::default().token(response.next_batch); client.sync(settings).await?; Ok(()) diff --git a/examples/wasm_command_bot/src/lib.rs b/examples/wasm_command_bot/src/lib.rs index 867a2c0f7..14a687a5e 100644 --- a/examples/wasm_command_bot/src/lib.rs +++ b/examples/wasm_command_bot/src/lib.rs @@ -79,9 +79,9 @@ pub async fn run() -> Result { let bot = WasmBot(client.clone()); - client.sync_once(SyncSettings::default()).await.unwrap(); + let response = client.sync_once(SyncSettings::default()).await.unwrap(); - let settings = SyncSettings::default().token(client.sync_token().await.unwrap()); + let settings = SyncSettings::default().token(response.next_batch); client.sync_with_callback(settings, |response| bot.on_sync_response(response)).await.unwrap(); Ok(JsValue::NULL) diff --git a/testing/matrix-sdk-integration-testing/src/tests/redaction.rs b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs index f8877266f..95ca7af17 100644 --- a/testing/matrix-sdk-integration-testing/src/tests/redaction.rs +++ b/testing/matrix-sdk-integration-testing/src/tests/redaction.rs @@ -14,13 +14,13 @@ use matrix_sdk::{ use crate::helpers::get_client_for_user; -async fn sync_once(client: &Client) -> Result<()> { - let settings = match client.sync_token().await { +async fn sync_once(client: &Client, sync_token: Option) -> Result { + let settings = match sync_token { Some(token) => SyncSettings::default().token(token), None => SyncSettings::default(), }; - client.sync_once(settings).await?; - Ok(()) + let sync_token = client.sync_once(settings).await?.next_batch; + Ok(sync_token) } #[ignore = "Broken since synapse update, see #1069"] @@ -32,10 +32,11 @@ async fn test_redacting_name() -> Result<()> { is_direct: true, }); + let mut sync_token = None; let room = tamatoa.create_room(request).await?; let room_id = room.room_id().to_owned(); for _ in 0..=10 { - sync_once(&tamatoa).await?; + sync_token = Some(sync_once(&tamatoa, sync_token).await?); if tamatoa.get_joined_room(&room_id).is_some() { break; } @@ -51,7 +52,7 @@ async fn test_redacting_name() -> Result<()> { for _ in 0..=10 { // we call sync up to ten times to give the server time to flush other // messages over and send us the new state event - sync_once(&tamatoa).await?; + sync_token = Some(sync_once(&tamatoa, sync_token).await?); if room.name().is_some() { break; @@ -74,7 +75,7 @@ async fn test_redacting_name() -> Result<()> { for _ in 0..=10 { // we call sync up to ten times to give the server time to flush other // messages over and send us the new state ev - sync_once(&tamatoa).await?; + sync_token = Some(sync_once(&tamatoa, sync_token).await?); if room.name().is_none() { break; @@ -102,10 +103,11 @@ async fn test_redacting_name_static() -> Result<()> { is_direct: true, }); + let mut sync_token = None; let room = tamatoa.create_room(request).await?; let room_id = room.room_id().to_owned(); for _ in 0..=10 { - sync_once(&tamatoa).await?; + sync_token = Some(sync_once(&tamatoa, sync_token).await?); if tamatoa.get_joined_room(&room_id).is_some() { break; } @@ -121,7 +123,7 @@ async fn test_redacting_name_static() -> Result<()> { for _ in 0..=10 { // we call sync up to ten times to give the server time to flush other // messages over and send us the new state event - sync_once(&tamatoa).await?; + sync_token = Some(sync_once(&tamatoa, sync_token).await?); if room.name().is_some() { break; @@ -142,7 +144,7 @@ async fn test_redacting_name_static() -> Result<()> { for _ in 0..=10 { // we call sync up to ten times to give the server time to flush other // messages over and send us the new state ev - sync_once(&tamatoa).await?; + sync_token = Some(sync_once(&tamatoa, sync_token).await?); if room.name().is_none() { break; From fa71122e7da52130b5a262933bcf002ccff36005 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Fri, 18 Nov 2022 14:43:25 +0100 Subject: [PATCH 57/78] ci: Add sliding-sync and experimental-timeline to clippy check --- xtask/src/ci.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xtask/src/ci.rs b/xtask/src/ci.rs index 9cb92b1b2..bcfa29471 100644 --- a/xtask/src/ci.rs +++ b/xtask/src/ci.rs @@ -173,7 +173,8 @@ fn check_clippy() -> Result<()> { cmd!( "rustup run nightly cargo clippy --workspace --all-targets --exclude matrix-sdk-crypto --exclude xtask - --no-default-features --features native-tls,sso-login + --no-default-features + --features native-tls,sliding-sync,sso-login,experimental-timeline -- -D warnings" ) .run()?; From eb20abe7b80952b8cbfc1dfe92b1ce6647910295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 18 Nov 2022 19:33:33 +0100 Subject: [PATCH 58/78] chore: The encryption feature was renamed --- crates/matrix-sdk/src/docs/encryption.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/matrix-sdk/src/docs/encryption.md b/crates/matrix-sdk/src/docs/encryption.md index 66437feeb..74438c2fb 100644 --- a/crates/matrix-sdk/src/docs/encryption.md +++ b/crates/matrix-sdk/src/docs/encryption.md @@ -164,9 +164,9 @@ Please note that, unless a client is specifically set up to ignore unverified devices, verifying devices is **not** necessary for encryption to work. -1. Make sure the `encryption` feature is enabled. +1. Make sure the `e2e-encryption` feature is enabled. 2. To persist the encryption keys, you can use [`ClientBuilder::store_config`] - or of the other `_store` methods on [`ClientBuilder`]. + or one of the other `_store` methods on [`ClientBuilder`]. ## Restoring a client @@ -215,7 +215,7 @@ is **not** supported using the default store. | Failure | Cause | Fix | | ------------------- | ----- | ----------- | -| No messages get encrypted nor decrypted | The `encryption` feature is disabled | [Enable the feature in your `Cargo.toml` file] | +| No messages get encrypted nor decrypted | The `e2e-encryption` feature is disabled | [Enable the feature in your `Cargo.toml` file] | | Messages that were decryptable aren't after a restart | Storage isn't setup to be persistent | Ensure you've activated the persistent storage backend feature, e.g. `sled` | | Messages are encrypted but can't be decrypted | The access token that the client is using is tied to another device | Clear storage to create a new device, read the [Restoring a Client] section | | Messages don't get encrypted but get decrypted | The `m.room.encryption` event is missing | Make sure encryption is [enabled] for the room and the event isn't [filtered] out, otherwise it might be a deserialization bug | From 2ab697328fd435aa0d0532b32167c6fced23be23 Mon Sep 17 00:00:00 2001 From: bitfriend Date: Sat, 19 Nov 2022 04:57:36 +0800 Subject: [PATCH 59/78] fix(sdk): Make handle_back_paginated_event future Send --- crates/matrix-sdk/src/room/timeline/event_handler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 891f4ce3f..82e8bb152 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -96,13 +96,14 @@ impl TimelineInner { encryption_info: Option, own_user_id: &UserId, ) { + let mut metadata_lock = self.metadata.lock().await; handle_remote_event( raw, own_user_id, encryption_info, TimelineItemPosition::Start, &mut self.items.lock_mut(), - &mut self.metadata.lock().await, + &mut metadata_lock, ); } From fcb37b6962286ffcdae705c4ee1eb0c8274b2901 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 21 Nov 2022 12:30:11 +0100 Subject: [PATCH 60/78] feat: Support creating Timeline with initial items from sliding sync Co-authored-by: Benjamin Kampmann --- Cargo.lock | 5 +- bindings/matrix-sdk-ffi/Cargo.toml | 4 -- bindings/matrix-sdk-ffi/src/sliding_sync.rs | 27 ++------ crates/matrix-sdk/Cargo.toml | 1 - .../src/room/timeline/event_item.rs | 33 ---------- crates/matrix-sdk/src/room/timeline/mod.rs | 27 +++++++- crates/matrix-sdk/src/sliding_sync.rs | 17 +++++- labs/jack-in/Cargo.toml | 5 +- labs/jack-in/src/client/state.rs | 61 ++++++++++++++++++- labs/jack-in/src/components/details.rs | 50 ++++++--------- labs/jack-in/src/main.rs | 3 + 11 files changed, 133 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55744d7e0..d61483611 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -624,9 +624,9 @@ checksum = "17cc5e6b5ab06331c33589842070416baa137e8b0eb912b008cfd4a78ada7919" [[package]] name = "chrono" -version = "0.4.22" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ "iana-time-zone", "js-sys", @@ -2265,6 +2265,7 @@ name = "jack-in" version = "0.2.0" dependencies = [ "app_dirs2", + "chrono", "dialoguer", "eyre", "futures", diff --git a/bindings/matrix-sdk-ffi/Cargo.toml b/bindings/matrix-sdk-ffi/Cargo.toml index 173fb3ea9..d210bfaf8 100644 --- a/bindings/matrix-sdk-ffi/Cargo.toml +++ b/bindings/matrix-sdk-ffi/Cargo.toml @@ -12,10 +12,6 @@ repository = "https://github.com/matrix-org/matrix-rust-sdk" [lib] crate-type = ["cdylib", "staticlib"] -[features] -default = ["experimental-room-preview"] # the whole crate is still very experimental, so this is fine -experimental-room-preview = ["matrix-sdk/experimental-room-preview"] - [build-dependencies] uniffi_build = { workspace = true, features = ["builtin-bindgen"] } diff --git a/bindings/matrix-sdk-ffi/src/sliding_sync.rs b/bindings/matrix-sdk-ffi/src/sliding_sync.rs index ec256f87d..6a432cb6e 100644 --- a/bindings/matrix-sdk-ffi/src/sliding_sync.rs +++ b/bindings/matrix-sdk-ffi/src/sliding_sync.rs @@ -5,10 +5,6 @@ use futures_signals::{ signal_vec::{SignalVecExt, VecDiff}, }; use futures_util::{pin_mut, StreamExt}; -#[cfg(feature = "experimental-room-preview")] -use matrix_sdk::ruma::events::{ - room::message::SyncRoomMessageEvent, AnySyncMessageLikeEvent, AnySyncTimelineEvent, -}; use matrix_sdk::ruma::{ api::client::sync::sync_events::{ v4::RoomSubscription as RumaRoomSubscription, @@ -23,9 +19,7 @@ pub use matrix_sdk::{ use tokio::task::JoinHandle; use super::{Client, Room, RUNTIME}; -use crate::helpers::unwrap_or_clone_arc; -#[cfg(feature = "experimental-room-preview")] -use crate::EventTimelineItem; +use crate::{helpers::unwrap_or_clone_arc, EventTimelineItem}; pub struct StoppableSpawn { handle: Arc>>>, @@ -126,25 +120,14 @@ impl SlidingSyncRoom { } } -#[cfg(feature = "experimental-room-preview")] #[uniffi::export] impl SlidingSyncRoom { #[allow(clippy::significant_drop_in_scrutinee)] pub fn latest_room_message(&self) -> Option> { - let messages = self.inner.timeline(); - // room is having the latest events at the end, - let lock = messages.lock_ref(); - for ev in lock.iter().rev() { - if let Ok(AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage( - SyncRoomMessageEvent::Original(o), - ))) = ev.event.deserialize() - { - let inner = - matrix_sdk::room::timeline::EventTimelineItem::_new(o, ev.event.clone()); - return Some(Arc::new(EventTimelineItem(inner))); - } - } - None + RUNTIME.block_on(async { + let item = self.inner.timeline().await.latest()?.as_event()?.to_owned(); + Some(Arc::new(EventTimelineItem(item))) + }) } } diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index 9363882a2..2f30f045d 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -42,7 +42,6 @@ appservice = ["ruma/appservice-api-s"] image-proc = ["dep:image"] image-rayon = ["image-proc", "image?/jpeg_rayon"] -experimental-room-preview = [] experimental-timeline = ["ruma/unstable-msc2677"] sliding-sync = [ diff --git a/crates/matrix-sdk/src/room/timeline/event_item.rs b/crates/matrix-sdk/src/room/timeline/event_item.rs index 1d5420909..8bacadc36 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item.rs +++ b/crates/matrix-sdk/src/room/timeline/event_item.rs @@ -16,8 +16,6 @@ use std::fmt; use indexmap::IndexMap; use matrix_sdk_base::deserialized_responses::EncryptionInfo; -#[cfg(feature = "experimental-room-preview")] -use ruma::events::room::message::{OriginalSyncRoomMessageEvent, Relation}; use ruma::{ events::{ relation::{AnnotationChunk, AnnotationType}, @@ -85,37 +83,6 @@ macro_rules! build { } impl EventTimelineItem { - #[cfg(feature = "experimental-room-preview")] - #[doc(hidden)] // FIXME: Remove. Used for matrix-sdk-ffi temporarily. - pub fn _new(ev: OriginalSyncRoomMessageEvent, raw: Raw) -> Self { - let edited = ev.unsigned.relations.as_ref().map_or(false, |r| r.replace.is_some()); - let reactions = ev - .unsigned - .relations - .and_then(|r| r.annotation) - .map(BundledReactions::from) - .unwrap_or_default(); - - Self { - key: TimelineKey::EventId(ev.event_id), - event_id: None, - sender: ev.sender, - content: TimelineItemContent::Message(Message { - msgtype: ev.content.msgtype, - in_reply_to: ev.content.relates_to.and_then(|rel| match rel { - Relation::Reply { in_reply_to } => Some(in_reply_to.event_id), - _ => None, - }), - edited, - }), - reactions, - origin_server_ts: Some(ev.origin_server_ts), - is_own: false, // FIXME: Potentially wrong - encryption_info: None, // FIXME: Potentially wrong - raw: Some(raw), - } - } - /// Get the [`TimelineKey`] of this item. pub fn key(&self) -> &TimelineKey { &self.key diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index d3e918a17..b0b2dc8fa 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -23,7 +23,10 @@ use std::{ use futures_core::Stream; use futures_signals::signal_vec::{MutableVec, SignalVec, SignalVecExt, VecDiff}; -use matrix_sdk_base::{deserialized_responses::EncryptionInfo, locks::Mutex}; +use matrix_sdk_base::{ + deserialized_responses::{EncryptionInfo, SyncTimelineEvent}, + locks::Mutex, +}; use ruma::{ assign, events::{ @@ -89,7 +92,22 @@ struct TimelineInnerMetadata { impl Timeline { pub(super) async fn new(room: &room::Common) -> Self { + Self::with_events(room, None, Vec::new()).await + } + + pub(crate) async fn with_events( + room: &room::Common, + prev_token: Option, + events: Vec, + ) -> Self { let inner = TimelineInner::default(); + let own_user_id = room.own_user_id(); + + for ev in events.into_iter().rev() { + inner + .handle_back_paginated_event(ev.event.cast(), ev.encryption_info, own_user_id) + .await; + } match room.account_data_static::().await { Ok(Some(fully_read)) => match fully_read.deserialize() { @@ -176,7 +194,7 @@ impl Timeline { Timeline { inner, room: room.clone(), - start_token: StdMutex::new(None), + start_token: StdMutex::new(prev_token), _end_token: StdMutex::new(None), _timeline_event_handler_guard, _fully_read_handler_guard, @@ -255,6 +273,11 @@ impl Timeline { .await; } + /// Get the latest of the timeline's items. + pub fn latest(&self) -> Option> { + self.inner.items.lock_ref().last().cloned() + } + /// Get a signal of the timeline's items. /// /// You can poll this signal to receive updates, the first of which will diff --git a/crates/matrix-sdk/src/sliding_sync.rs b/crates/matrix-sdk/src/sliding_sync.rs index cf3a69980..85531960f 100644 --- a/crates/matrix-sdk/src/sliding_sync.rs +++ b/crates/matrix-sdk/src/sliding_sync.rs @@ -29,6 +29,8 @@ use ruma::{ use thiserror::Error; use url::Url; +#[cfg(feature = "experimental-timeline")] +use crate::room::timeline::Timeline; use crate::{Client, Result}; /// Internal representation of errors in Sliding Sync @@ -106,6 +108,7 @@ pub type AliveRoomTimeline = Arc, @@ -115,6 +118,7 @@ pub struct SlidingSyncRoom { impl SlidingSyncRoom { fn from( + client: Client, room_id: OwnedRoomId, mut inner: v4::SlidingSyncRoom, timeline: Vec, @@ -122,6 +126,7 @@ impl SlidingSyncRoom { // we overwrite to only keep one copy inner.timeline = vec![]; Self { + client, room_id, is_loading_more: Mutable::new(false), prev_batch: Mutable::new(inner.prev_batch.clone()), @@ -146,10 +151,20 @@ impl SlidingSyncRoom { } /// `AliveTimeline` of this room + #[cfg(not(feature = "experimental-timeline"))] pub fn timeline(&self) -> AliveRoomTimeline { self.timeline.clone() } + /// `Timeline` of this room + #[cfg(feature = "experimental-timeline")] + pub async fn timeline(&self) -> Timeline { + let current_timeline = self.timeline.lock_ref().to_vec(); + let prev_batch = self.prev_batch.lock_ref().clone(); + let room = self.client.get_room(&self.room_id).unwrap(); + Timeline::with_events(&room, prev_batch, current_timeline).await + } + /// This rooms name as calculated by the server, if any pub fn name(&self) -> Option<&str> { self.inner.name.as_deref() @@ -490,7 +505,7 @@ impl SlidingSync { } else { rooms_map.insert_cloned( id.clone(), - SlidingSyncRoom::from(id.clone(), room_data, timeline), + SlidingSyncRoom::from(self.client.clone(), id.clone(), room_data, timeline), ); rooms.push(id); } diff --git a/labs/jack-in/Cargo.toml b/labs/jack-in/Cargo.toml index ef98c0b57..311374f1f 100644 --- a/labs/jack-in/Cargo.toml +++ b/labs/jack-in/Cargo.toml @@ -10,11 +10,12 @@ file-logging = ["dep:log4rs"] [dependencies] app_dirs2 = "2" +chrono = "0.4.23" dialoguer = "0.10.2" eyre = "0.6" futures = { version = "0.3.1" } futures-signals = "0.3.24" -matrix-sdk = { path = "../../crates/matrix-sdk", default-features = false, features = ["e2e-encryption", "anyhow", "native-tls", "sled", "sliding-sync"], version = "0.6.0" } +matrix-sdk = { path = "../../crates/matrix-sdk", default-features = false, features = ["e2e-encryption", "anyhow", "native-tls", "sled", "sliding-sync", "experimental-timeline"], version = "0.6.0" } matrix-sdk-common = { path = "../../crates/matrix-sdk-common", version = "0.6.0" } matrix-sdk-sled = { path = "../../crates/matrix-sdk-sled", features = ["state-store", "crypto-store"], version = "0.2.0" } sanitize-filename-reader-friendly = "2.2.1" @@ -22,7 +23,7 @@ serde_json = "1.0.85" structopt = "0.3" tokio = { version = "1", features = ["rt-multi-thread", "sync", "macros"] } tracing-flame = "0.2" -tracing-subscriber = "0.3.15" +tracing-subscriber = "0.3.15" tui-logger = "0.8.1" tuirealm = "~1.8" tui-realm-stdlib = "1.2.0" diff --git a/labs/jack-in/src/client/state.rs b/labs/jack-in/src/client/state.rs index 9c0e35901..d0a8bfacd 100644 --- a/labs/jack-in/src/client/state.rs +++ b/labs/jack-in/src/client/state.rs @@ -1,7 +1,15 @@ -use std::time::{Duration, Instant}; +use std::{ + sync::Arc, + time::{Duration, Instant}, +}; -use futures_signals::signal::Mutable; -use matrix_sdk::{ruma::OwnedRoomId, SlidingSyncView}; +use futures::{pin_mut, StreamExt}; +use futures_signals::{ + signal::Mutable, + signal_vec::{MutableVec, VecDiff}, +}; +use matrix_sdk::{room::timeline::TimelineItem, ruma::OwnedRoomId, SlidingSyncView}; +use tokio::task::JoinHandle; #[derive(Clone, Default)] pub struct CurrentRoomSummary { @@ -17,7 +25,9 @@ pub struct SlidingSyncState { /// the current list selector for the room first_render: Option, full_sync: Option, + tl_handle: Mutable>>, pub selected_room: Mutable>, + pub current_timeline: MutableVec>, } impl SlidingSyncState { @@ -27,7 +37,9 @@ impl SlidingSyncState { view, first_render: None, full_sync: None, + tl_handle: Default::default(), selected_room: Default::default(), + current_timeline: Default::default(), } } @@ -40,6 +52,49 @@ impl SlidingSyncState { } pub fn select_room(&self, r: Option) { + self.current_timeline.lock_mut().clear(); + if let Some(c) = self.tl_handle.lock_mut().take() { + c.abort(); + } + if let Some(room) = + r.as_ref().and_then(|room_id| self.view.rooms.lock_ref().get(room_id).cloned()) + { + let current_timeline = self.current_timeline.clone(); + let handle = tokio::spawn(async move { + let timeline = room.timeline().await; + let listener = timeline.stream(); + pin_mut!(listener); + while let Some(diff) = listener.next().await { + match diff { + VecDiff::Clear {} => { + current_timeline.lock_mut().clear(); + } + VecDiff::InsertAt { index, value } => { + current_timeline.lock_mut().insert_cloned(index, value); + } + VecDiff::Move { old_index, new_index } => { + current_timeline.lock_mut().move_from_to(old_index, new_index); + } + VecDiff::Pop {} => { + current_timeline.lock_mut().pop(); + } + VecDiff::Push { value } => { + current_timeline.lock_mut().push_cloned(value); + } + VecDiff::RemoveAt { index } => { + current_timeline.lock_mut().remove(index); + } + VecDiff::Replace { values } => { + current_timeline.lock_mut().replace_cloned(values); + } + VecDiff::UpdateAt { index, value } => { + current_timeline.lock_mut().set_cloned(index, value); + } + } + } + }); + *self.tl_handle.lock_mut() = Some(handle); + } self.selected_room.replace(r); } diff --git a/labs/jack-in/src/components/details.rs b/labs/jack-in/src/components/details.rs index bfea43336..dc8b2c570 100644 --- a/labs/jack-in/src/components/details.rs +++ b/labs/jack-in/src/components/details.rs @@ -1,6 +1,6 @@ use std::collections::BTreeMap; -use matrix_sdk::ruma::events::{AnyMessageLikeEvent, AnyTimelineEvent, MessageLikeEvent}; +use chrono::{offset::Local, DateTime}; use tuirealm::{ command::{Cmd, CmdResult}, event::{Key, KeyEvent, KeyModifiers}, @@ -23,7 +23,7 @@ pub struct Details { liststate: ListState, name: Option, state_events_counts: Vec<(String, usize)>, - current_room_timeline: Vec, + current_room_timeline: Vec, } impl Details { @@ -68,15 +68,25 @@ impl Details { state_events.iter().map(|(k, l)| (k.clone(), l.len())).collect(); state_events_counts.sort_by_key(|(_, count)| *count); - let mut timeline: Vec = room_data - .timeline() + let timeline: Vec = self + .sstate + .current_timeline .lock_ref() .iter() - .filter_map(|d| d.event.deserialize().ok()) - .map(|e| e.into_full_event(room_id.clone())) + .filter_map(|t| t.as_event()) // we ignore virtual events + .filter_map(|e| e.content().as_message().map(|m| (e, m))) + .map(|(e, m)| { + format!( + "[{}] {}: {}", + e.origin_server_ts() + .and_then(|r| r.to_system_time()) + .map(|s| DateTime::::from(s).format("%Y-%m-%dT%T").to_string()) + .unwrap_or_default(), + e.sender(), + m.body() + ) + }) .collect(); - timeline.reverse(); - self.current_room_timeline = timeline; self.name = Some(name); self.state_events_counts = state_events_counts; @@ -166,28 +176,8 @@ impl MockComponent for Details { let title = (name.to_owned(), Alignment::Left); let focus = self.props.get_or(Attribute::Focus, AttrValue::Flag(false)).unwrap_flag(); - let mut details = vec![]; - - for e in self.current_room_timeline.iter() { - let body = { - match e { - AnyTimelineEvent::MessageLike(m) => { - if let AnyMessageLikeEvent::RoomMessage(MessageLikeEvent::Original(m)) = m { - m.content.body().to_owned() - } else { - m.event_type().to_string() - } - } - AnyTimelineEvent::State(s) => s.event_type().to_string(), - } - }; - details.push(ListItem::new(format!( - "[{}] {}: {}", - e.origin_server_ts().as_secs(), - e.sender().as_str(), - body - ))); - } + let details: Vec<_> = + self.current_room_timeline.iter().map(|e| ListItem::new(e.clone())).collect(); frame.render_stateful_widget( List::new(details) diff --git a/labs/jack-in/src/main.rs b/labs/jack-in/src/main.rs index 41e724dd3..9bad51eed 100644 --- a/labs/jack-in/src/main.rs +++ b/labs/jack-in/src/main.rs @@ -7,7 +7,9 @@ use std::path::{Path, PathBuf}; use app_dirs2::{app_root, AppDataType, AppInfo}; use dialoguer::{theme::ColorfulTheme, Password}; use eyre::{eyre, Result}; +use futures_signals::signal_vec::VecDiff; use matrix_sdk::{ + room::timeline::TimelineItem, ruma::{OwnedRoomId, OwnedUserId}, Client, }; @@ -45,6 +47,7 @@ pub enum Msg { pub enum JackInEvent { Any, // match all SyncUpdate(client::state::SlidingSyncState), + RoomDataUpdate(VecDiff), } impl PartialOrd for JackInEvent { From 1025d4262415e86bc730a47ed806ae7ebc2e8184 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 22 Nov 2022 10:55:46 +0200 Subject: [PATCH 61/78] fix(ffi): Fix xcframework release script, add missing module map --- xtask/src/swift.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xtask/src/swift.rs b/xtask/src/swift.rs index 53a470d0d..fb74c5e40 100644 --- a/xtask/src/swift.rs +++ b/xtask/src/swift.rs @@ -179,6 +179,13 @@ fn build_xcframework( rename(generated_dir.join("matrix_sdk_ffiFFI.h"), headers_dir.join("matrix_sdk_ffiFFI.h"))?; + // Move and rename the module map to `module.modulemap` to match what + // the xcframework expects + rename( + generated_dir.join("matrix_sdk_ffiFFI.modulemap"), + headers_dir.join("module.modulemap"), + )?; + rename(generated_dir.join("matrix_sdk_ffi.swift"), swift_dir.join("matrix_sdk_ffi.swift"))?; println!("-- Generate MatrixSDKFFI.xcframework framework"); From 57a17435664a19fea97802917ad3a5b4264c5a79 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 21 Nov 2022 13:17:37 +0100 Subject: [PATCH 62/78] chore: Update Cargo.lock --- Cargo.lock | 859 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 495 insertions(+), 364 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d61483611..edbc5701c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,14 +30,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ "generic-array", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] name = "aes" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfe0133578c0986e1fe3dfcd4af1cc5b2dd6c3dbf534d69916ce16a2701d40ba" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ "cfg-if", "cipher 0.4.3", @@ -50,16 +50,16 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", "once_cell", "version_check", ] [[package]] name = "aho-corasick" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" dependencies = [ "memchr", ] @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.65" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" +checksum = "216261ddc8289130e551ddcd5ce8a064710c0d064a4d2895c67151c92b5443f6" [[package]] name = "anymap2" @@ -142,15 +142,6 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" -[[package]] -name = "arrayvec" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -dependencies = [ - "nodrop", -] - [[package]] name = "arrayvec" version = "0.7.2" @@ -240,11 +231,12 @@ dependencies = [ [[package]] name = "async-lock" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" dependencies = [ "event-listener", + "futures-lite", ] [[package]] @@ -276,9 +268,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.57" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76464446b8bc32758d7e88ee1a804d9914cd9b1cb264c029899680b0be29826f" +checksum = "1e805d94e6b5001b651426cf4cd446b1ab5f319d27bab5c644f61de0a804360c" dependencies = [ "proc-macro2", "quote", @@ -313,9 +305,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "axum" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e3356844c4d6a6d6467b8da2cffb4a2820be256f50a3a386c9d152bab31043" +checksum = "acee9fd5073ab6b045a275b3e709c163dd36c90685219cb21804a147b58dba43" dependencies = [ "async-trait", "axum-core", @@ -325,7 +317,7 @@ dependencies = [ "http", "http-body", "hyper", - "itoa 1.0.3", + "itoa", "matchit", "memchr", "mime", @@ -343,9 +335,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9f0c0a60006f2a293d82d571f635042a72edf927539b7685bd62d361963839b" +checksum = "37e5939e02c56fecd5c017c37df4238c0a839fa76b7f97acdd7efb804fd181cc" dependencies = [ "async-trait", "bytes", @@ -364,7 +356,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" dependencies = [ "futures-core", - "getrandom 0.2.7", + "getrandom 0.2.8", "instant", "pin-project-lite", "rand 0.8.5", @@ -381,7 +373,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide 0.5.3", + "miniz_oxide 0.5.4", "object", "rustc-demangle", ] @@ -394,9 +386,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64ct" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2b2456fd614d856680dcd9fcc660a51a820fa09daef2e49772b56a193c8474" +checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] name = "benchmarks" @@ -445,16 +437,16 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blake3" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08e53fc5a564bb15bfe6fae56bd71522205f1f91893f9c0116edad6496c183f" +checksum = "895adc16c8b3273fbbc32685a7d55227705eda08c01e77704020f3491924b44b" dependencies = [ "arrayref", - "arrayvec 0.7.2", + "arrayvec", "cc", "cfg-if", "constant_time_eq", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -468,9 +460,9 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.10.2" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" dependencies = [ "generic-array", ] @@ -492,15 +484,15 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bumpalo" -version = "3.11.0" +version = "3.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d" +checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" [[package]] name = "bytemuck" -version = "1.12.1" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" [[package]] name = "byteorder" @@ -510,9 +502,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] name = "cache-padded" @@ -575,9 +567,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.73" +version = "1.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +checksum = "e9f73505338f7d905b19d18738976aae232eb46b8efc15554ffc56deb5d9ebe4" [[package]] name = "cesu8" @@ -700,9 +692,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.22" +version = "3.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" dependencies = [ "atty", "bitflags", @@ -712,18 +704,18 @@ dependencies = [ "once_cell", "strsim 0.10.0", "termcolor", - "textwrap 0.15.1", + "textwrap 0.16.0", ] [[package]] name = "clap" -version = "4.0.18" +version = "4.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335867764ed2de42325fafe6d18b8af74ba97ee0c590fa016f157535b42ab04b" +checksum = "2148adefda54e14492fb9bddcc600b4344c5d1a3123bd666dcb939c6f0e0e57e" dependencies = [ "atty", "bitflags", - "clap_derive 4.0.18", + "clap_derive 4.0.21", "clap_lex 0.3.0", "once_cell", "strsim 0.10.0", @@ -745,9 +737,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.0.18" +version = "4.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a1b0f6422af32d5da0c58e2703320f379216ee70198241c84173a8c5ac28f3" +checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" dependencies = [ "heck 0.4.0", "proc-macro-error", @@ -787,13 +779,23 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.48" +version = "0.1.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ad8cef104ac57b68b89df3208164d228503abbdce70f6880ffa3d970e7443a" +checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" dependencies = [ "cc", ] +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -821,13 +823,13 @@ dependencies = [ [[package]] name = "console" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847" +checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" dependencies = [ "encode_unicode", + "lazy_static", "libc", - "once_cell", "terminal_size", "unicode-width", "winapi", @@ -851,18 +853,18 @@ checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "const_format" -version = "0.2.26" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495" +checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.22" +version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d" +checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650" dependencies = [ "proc-macro2", "quote", @@ -871,9 +873,9 @@ dependencies = [ [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" [[package]] name = "convert_case" @@ -899,18 +901,18 @@ checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpp_demangle" -version = "0.3.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +checksum = "b446fd40bcc17eddd6a4a78f24315eb90afdb3334999ddfd4909985c47722442" dependencies = [ "cfg-if", ] [[package]] name = "cpufeatures" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc948ebb96241bb40ab73effeb80d9f93afaad49359d159a5e61be51619fe813" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" dependencies = [ "libc", ] @@ -934,7 +936,7 @@ dependencies = [ "atty", "cast", "ciborium", - "clap 3.2.22", + "clap 3.2.23", "criterion-plot", "futures", "itertools", @@ -985,26 +987,24 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "045ebe27666471bb549370b4b0b3e51b07f56325befa4284db65fc89c02511b1" +checksum = "96bf8df95e795db1a4aca2957ad884a2df35413b24bbeb3114422f3cc21498e8" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", "memoffset", - "once_cell", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.11" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" +checksum = "422f23e724af1240ec469ea1e834d87a4b59ce2efe2c6a96256b0c47e2fd86aa" dependencies = [ "cfg-if", - "once_cell", ] [[package]] @@ -1044,9 +1044,9 @@ dependencies = [ [[package]] name = "ctor" -version = "0.1.23" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdffe87e1d521a10f9696f833fe502293ea446d7f256c06128293a4119bdf4cb" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", "syn", @@ -1054,9 +1054,9 @@ dependencies = [ [[package]] name = "ctr" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d14f329cfbaf5d0e06b5e87fff7e265d2673c5ea7d2c27691a2c107db1442a0" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher 0.4.3", ] @@ -1076,10 +1076,54 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.14.1" +name = "cxx" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" +checksum = "d4a41a86530d0fe7f5d9ea779916b7cadd2d4f9add748b99c2c029cbbdfaf453" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06416d667ff3e3ad2df1cd8cd8afae5da26cf9cec4d0825040f88b5ca659a2f0" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "820a9a2af1669deeef27cb271f476ffd196a2c4b6731336011e0ba63e2c7cf71" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a08a6e2fcc370a089ad3b4aaf54db3b1b4cee38ddabce5896b33eb693275f470" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" dependencies = [ "darling_core", "darling_macro", @@ -1087,9 +1131,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" +checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" dependencies = [ "fnv", "ident_case", @@ -1101,9 +1145,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" +checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" dependencies = [ "darling_core", "quote", @@ -1120,7 +1164,7 @@ dependencies = [ "hashbrown", "lock_api", "once_cell", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.4", ] [[package]] @@ -1148,7 +1192,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "uuid 1.1.2", + "uuid 1.2.2", ] [[package]] @@ -1161,15 +1205,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "deflate" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f" -dependencies = [ - "adler32", -] - [[package]] name = "der" version = "0.5.1" @@ -1243,11 +1278,11 @@ dependencies = [ [[package]] name = "digest" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ - "block-buffer 0.10.2", + "block-buffer 0.10.3", "crypto-common", "subtle", ] @@ -1469,7 +1504,7 @@ name = "example-emoji-verification" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.18", + "clap 4.0.26", "futures", "matrix-sdk", "tokio", @@ -1527,7 +1562,7 @@ name = "example-timeline" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.0.18", + "clap 4.0.26", "futures", "futures-signals", "matrix-sdk", @@ -1578,13 +1613,13 @@ dependencies = [ [[package]] name = "fd-lock" -version = "3.0.6" +version = "3.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" +checksum = "bb21c69b9fea5e15dbc1049e4b77145dd0ba1c84019c488102de0dc4ea4b0a27" dependencies = [ "cfg-if", "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -1606,7 +1641,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide 0.5.3", + "miniz_oxide 0.5.4", ] [[package]] @@ -1641,9 +1676,9 @@ dependencies = [ [[package]] name = "fs-err" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64db3e262960f0662f43a6366788d5f10f7f244b8f7d7d987f560baf5ded5c50" +checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" [[package]] name = "fs2" @@ -1663,9 +1698,9 @@ checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" [[package]] name = "futures" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" dependencies = [ "futures-channel", "futures-core", @@ -1678,9 +1713,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" dependencies = [ "futures-core", "futures-sink", @@ -1688,15 +1723,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" [[package]] name = "futures-executor" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab" +checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" dependencies = [ "futures-core", "futures-task", @@ -1705,9 +1740,9 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" [[package]] name = "futures-lite" @@ -1726,9 +1761,9 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17" +checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", @@ -1752,15 +1787,15 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" [[package]] name = "futures-task" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" [[package]] name = "futures-timer" @@ -1770,9 +1805,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.24" +version = "0.3.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" dependencies = [ "futures-channel", "futures-core", @@ -1820,9 +1855,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" dependencies = [ "cfg-if", "js-sys", @@ -1879,9 +1914,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca32592cf21ac7ccab1825cd87f6c9b3d9022c44d086172ed0966bec8af30be" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" dependencies = [ "bytes", "fnv", @@ -1950,7 +1985,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -1961,7 +1996,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa 1.0.3", + "itoa", ] [[package]] @@ -2016,9 +2051,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" [[package]] name = "hyper" -version = "0.14.20" +version = "0.14.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02c929dc5c39e335a03c405292728118860721b10190d98c2a0f0efd5baafbac" +checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" dependencies = [ "bytes", "futures-channel", @@ -2029,7 +2064,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.3", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -2040,9 +2075,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d" dependencies = [ "http", "hyper", @@ -2066,18 +2101,28 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.47" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c495f162af0bf17656d0014a0eded5f3cd2f365fdd204548c2869db89359dc7" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", + "iana-time-zone-haiku", "js-sys", - "once_cell", "wasm-bindgen", "winapi", ] +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -2115,20 +2160,20 @@ dependencies = [ [[package]] name = "image" -version = "0.24.3" +version = "0.24.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" dependencies = [ "bytemuck", "byteorder", "color_quant", "gif", - "jpeg-decoder 0.2.6", + "jpeg-decoder 0.3.0", "num-rational 0.4.1", "num-traits", - "png 0.17.5", + "png 0.17.7", "scoped_threadpool", - "tiff 0.7.3", + "tiff 0.8.0", ] [[package]] @@ -2166,9 +2211,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.9.1" +version = "1.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", @@ -2189,14 +2234,14 @@ checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" [[package]] name = "inferno" -version = "0.11.7" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9709543bd6c25fdc748da2bed0f6855b07b7e93a203ae31332ac2101ab2f4782" +checksum = "bd2fa5a9ad16dedcfabbc87f048ee6dd40d4944736fe4c5d362fb01df1209de1" dependencies = [ "ahash", "atty", "indexmap", - "itoa 1.0.3", + "itoa", "log", "num-format", "once_cell", @@ -2229,36 +2274,34 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "0.7.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" +checksum = "a7d367024b3f3414d8e01f437f704f41a9f64ab36f9067fa73e526ad4c763c87" +dependencies = [ + "libc", + "windows-sys 0.42.0", +] [[package]] name = "ipnet" -version = "2.5.0" +version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" +checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745" [[package]] name = "itertools" -version = "0.10.3" +version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" +checksum = "4217ad341ebadf8d8e724e264f13e593e0648f5b3e94b3896a5df283be015ecc" [[package]] name = "jack-in" @@ -2317,9 +2360,9 @@ dependencies = [ [[package]] name = "jpeg-decoder" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" dependencies = [ "rayon", ] @@ -2375,9 +2418,9 @@ checksum = "984e109462d46ad18314f10e392c286c3d47bce203088a09012de1015b45b737" [[package]] name = "lazy-regex" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6b12f2eb6ed7d39405c5eb25a034b4c106a9ad87a6d9be3298de6c5f32fd57d" +checksum = "ae9656bf98b413727b974a451039bc00ce546c3de9440cb4a7b65222b71e17cc" dependencies = [ "lazy-regex-proc_macros", "once_cell", @@ -2386,9 +2429,9 @@ dependencies = [ [[package]] name = "lazy-regex-proc_macros" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2496e5264069bc726ccf37eb76b9cd89406ae110d836c3f76729f99c8a23293" +checksum = "da3c3042a5f73640f091fda4175798f2b51c2107deeab18e3017873a4772dd36" dependencies = [ "proc-macro2", "quote", @@ -2404,31 +2447,40 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.132" +version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "libloading" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] -name = "linux-raw-sys" -version = "0.0.46" +name = "link-cplusplus" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb68f22743a3fb35785f1e7f844ca5a3de2dde5bd0c0ef5b372065814699b121" [[package]] name = "lock_api" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f80bf5aacaf25cbfc8210d1cfb718f2bf3b11c4c54e5afe36c236853a8ec390" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" dependencies = [ "autocfg", "scopeguard", @@ -2461,9 +2513,9 @@ dependencies = [ [[package]] name = "log4rs" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "893eaf59f4bef8e2e94302adf56385db445a0306b9823582b0b8d5a06d8822f3" +checksum = "d36ca1786d9e79b8193a68d480a0907b612f109537115c6ff655a3a1967533fd" dependencies = [ "anyhow", "arc-swap", @@ -2479,9 +2531,9 @@ dependencies = [ [[package]] name = "lru" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936d98d2ddd79c18641c6709e7bb09981449694e402d1a0f0f657ea8d61f4a51" +checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" dependencies = [ "hashbrown", ] @@ -2535,10 +2587,10 @@ dependencies = [ "futures-core", "futures-signals", "futures-util", - "getrandom 0.2.7", + "getrandom 0.2.8", "http", "hyper", - "image 0.24.3", + "image 0.24.5", "indexmap", "matches", "matrix-sdk-base", @@ -2674,7 +2726,7 @@ dependencies = [ "ruma", "serde", "serde_json", - "sha2 0.10.3", + "sha2 0.10.6", "thiserror", "tokio", "tracing", @@ -2699,7 +2751,7 @@ dependencies = [ "ruma", "serde", "serde_json", - "sha2 0.10.3", + "sha2 0.10.6", "tempfile", "thiserror", "tokio", @@ -2786,7 +2838,7 @@ dependencies = [ "base64", "dashmap", "derive_builder", - "getrandom 0.2.7", + "getrandom 0.2.8", "gloo-utils", "indexed_db_futures 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "indexed_db_futures 0.2.3 (git+https://github.com/Hywan/rust-indexed-db?branch=feat-factory-nodejs)", @@ -2801,7 +2853,7 @@ dependencies = [ "serde_json", "thiserror", "tracing", - "uuid 1.1.2", + "uuid 1.2.2", "wasm-bindgen", "wasm-bindgen-test", "web-sys", @@ -2871,13 +2923,13 @@ dependencies = [ "blake3", "chacha20poly1305", "displaydoc", - "getrandom 0.2.7", + "getrandom 0.2.8", "hmac", "pbkdf2", "rand 0.8.5", "serde", "serde_json", - "sha2 0.10.3", + "sha2 0.10.6", "thiserror", "zeroize", ] @@ -2913,18 +2965,18 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memmap2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498" +checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" dependencies = [ "libc", ] [[package]] name = "memoffset" -version = "0.6.5" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" dependencies = [ "autocfg", ] @@ -2972,30 +3024,39 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "napi" -version = "2.9.1" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743fece4c26c5132f8559080145fde9ba88700c0f1aa30a1ab3e057ab105814d" +checksum = "466b16c759694cb07fbb023b0bde55afcc2ae35e8c0264b070c86a3e9a18cb6c" dependencies = [ "bitflags", "ctor", @@ -3049,9 +3110,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" dependencies = [ "lazy_static", "libc", @@ -3091,12 +3152,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nom" version = "7.1.1" @@ -3119,12 +3174,12 @@ dependencies = [ [[package]] name = "num-format" -version = "0.4.0" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bafe4179722c2894288ee77a9f044f02811c86af699344c498b0840c698a2465" +checksum = "54b862ff8df690cf089058c98b183676a7ed0f974cc08b426800093227cbff3b" dependencies = [ - "arrayvec 0.4.12", - "itoa 0.4.8", + "arrayvec", + "itoa", ] [[package]] @@ -3181,23 +3236,14 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" dependencies = [ "hermit-abi", "libc", ] -[[package]] -name = "num_threads" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" -dependencies = [ - "libc", -] - [[package]] name = "object" version = "0.29.0" @@ -3213,7 +3259,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6c2c7054110ce4d7b4756d7b7fe507fea9413968ad0ef8f1d043d504aec725" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", "olm-sys", "serde", "serde_json", @@ -3232,9 +3278,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.13.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "oorandom" @@ -3250,9 +3296,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.41" +version = "0.10.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618febf65336490dfcf20b73f885f5651a0c89c64c2d4a8c3662585a70bf5bd0" +checksum = "12fc0523e3bd51a692c8850d075d74dc062ccf251c0110668cbd921917118a13" dependencies = [ "bitflags", "cfg-if", @@ -3282,9 +3328,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.75" +version = "0.9.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5f9bd0c2710541a3cda73d6f9ac4f1b240de4ae261065d309dbe73d9dceb42f" +checksum = "b03b84c3b2d099b81f0953422b4d4ad58761589d0229b5506356afca05a3670a" dependencies = [ "autocfg", "cc", @@ -3295,9 +3341,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.3.0" +version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] name = "overload" @@ -3329,7 +3375,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.3", + "parking_lot_core 0.9.4", ] [[package]] @@ -3348,15 +3394,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +checksum = "4dc9e0dc2adc1c69d09143aff38d3d30c5c3f0df0dad82e6d25547af174ebec0" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -3366,7 +3412,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" dependencies = [ "base64ct", - "rand_core 0.6.3", + "rand_core 0.6.4", "subtle", ] @@ -3382,10 +3428,10 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.3", + "digest 0.10.6", "hmac", "password-hash", - "sha2 0.10.3", + "sha2 0.10.6", ] [[package]] @@ -3438,9 +3484,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.25" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "plain" @@ -3450,9 +3496,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plotters" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" dependencies = [ "num-traits", "plotters-backend", @@ -3484,20 +3530,20 @@ checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" dependencies = [ "bitflags", "crc32fast", - "deflate 0.8.6", + "deflate", "miniz_oxide 0.3.7", ] [[package]] name = "png" -version = "0.17.5" +version = "0.17.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" dependencies = [ "bitflags", "crc32fast", - "deflate 1.0.0", - "miniz_oxide 0.5.3", + "flate2", + "miniz_oxide 0.6.2", ] [[package]] @@ -3535,9 +3581,9 @@ dependencies = [ [[package]] name = "ppv-lite86" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro-crate" @@ -3576,9 +3622,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.43" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" dependencies = [ "unicode-ident", ] @@ -3602,9 +3648,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c3c31cdec40583bb68f0b18403400d01ec4289c383aa047560439952c4dd7" +checksum = "a0841812012b2d4a6145fae9a6af1534873c32aa67fff26bd09f8fa42c83f95a" dependencies = [ "bytes", "prost-derive", @@ -3612,9 +3658,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.11.0" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7345d5f0e08c0536d7ac7229952590239e77abf0a0100a1b1d890add6ea96364" +checksum = "164ae68b6587001ca506d3bf7f1000bfa248d0e1217b618108fba4ec1d0cc306" dependencies = [ "anyhow", "itertools", @@ -3652,9 +3698,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9279fbdacaad3baf559d8cabe0acc3d06e30ea14931af31af79578ac0946decc" +checksum = "11bafc859c6815fbaffbbbf4229ecb767ac913fecb27f9ad4343662e9ef099ea" dependencies = [ "memchr", ] @@ -3699,7 +3745,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha 0.3.1", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -3719,7 +3765,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] @@ -3733,11 +3779,11 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", ] [[package]] @@ -3755,16 +3801,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ - "rand_core 0.6.3", + "rand_core 0.6.4", ] [[package]] name = "rayon" -version = "1.5.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +checksum = "1e060280438193c554f654141c9ea9417886713b7acd75974c85b18a69a88e0b" dependencies = [ - "autocfg", "crossbeam-deque", "either", "rayon-core", @@ -3772,9 +3817,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.9.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -3797,16 +3842,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", "redox_syscall", "thiserror", ] [[package]] name = "regex" -version = "1.6.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" dependencies = [ "aho-corasick", "memchr", @@ -3824,9 +3869,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.27" +version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] name = "remove_dir_all" @@ -3839,9 +3884,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.11" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75aa69a3f06bbcc66ede33af2af253c6f7a86b1ca0033f60c580a27074fbf92" +checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c" dependencies = [ "base64", "bytes", @@ -3856,10 +3901,10 @@ dependencies = [ "hyper-tls", "ipnet", "js-sys", - "lazy_static", "log", "mime", "native-tls", + "once_cell", "percent-encoding", "pin-project-lite", "rustls", @@ -3888,9 +3933,9 @@ checksum = "4389f1d5789befaf6029ebd9f7dac4af7f7e3d61b69d4f30e2ac02b57e7712b0" [[package]] name = "rgb" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3b221de559e4a29df3b957eec92bc0de6bc8eaf6ca9cfed43e5e1d67ff65a34" +checksum = "3603b7d71ca82644f79b5a06d1220e9a58ede60bd32255f698cb1af8838b8db3" dependencies = [ "bytemuck", ] @@ -3960,10 +4005,10 @@ dependencies = [ "base64", "bytes", "form_urlencoded", - "getrandom 0.2.7", + "getrandom 0.2.8", "http", "indexmap", - "itoa 1.0.3", + "itoa", "js-sys", "js_int", "js_option", @@ -3979,7 +4024,7 @@ dependencies = [ "thiserror", "tracing", "url", - "uuid 1.1.2", + "uuid 1.2.2", "wildmatch", ] @@ -4026,23 +4071,23 @@ checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustix" -version = "0.35.9" +version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c825b8aa8010eb9ee99b75f05e10180b9278d161583034d7574c9d617aeada" +checksum = "203974af07ea769452490ee8de3e5947971efc3a090dca8a779dd432d3fa46a7" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] name = "rustls" -version = "0.20.6" +version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" +checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" dependencies = [ "log", "ring", @@ -4124,14 +4169,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" dependencies = [ "lazy_static", - "windows-sys", + "windows-sys 0.36.1", ] [[package]] name = "scoped-tls" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scoped_threadpool" @@ -4145,6 +4190,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + [[package]] name = "scroll" version = "0.11.0" @@ -4200,9 +4251,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711" +checksum = "e25dfac463d778e353db5be2449d1cce89bd6fd23c9f1ea21310ce6e5a1b29c4" dependencies = [ "serde", ] @@ -4238,11 +4289,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.87" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce777b7b150d76b9cf60d28b55f5847135a003f7d7350c6be7a773508ce7d45" +checksum = "8e8b3801309262e8184d9687fb697586833e939767aea0dda89f5a8e650e8bd7" dependencies = [ - "itoa 1.0.3", + "itoa", "ryu", "serde", ] @@ -4265,19 +4316,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.3", + "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" -version = "0.9.10" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a09f551ccc8210268ef848f0bab37b306e87b85b2e017b899e7fb815f5aed62" +checksum = "6d232d893b10de3eb7258ff01974d6ee20663d8e833263c99409d4b13a0209da" dependencies = [ "indexmap", - "itoa 1.0.3", + "itoa", "ryu", "serde", "unsafe-libyaml", @@ -4298,13 +4349,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.3" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899bf02746a2c92bf1053d9327dadb252b01af1f81f90cdb902411f518bc7215" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.3", + "digest 0.10.6", ] [[package]] @@ -4348,9 +4399,9 @@ dependencies = [ [[package]] name = "signature" -version = "1.6.0" +version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0ea32af43239f0d353a7dd75a22d94c329c8cdaafdcb4c1c1335aa10c298a4a" +checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" [[package]] name = "slab" @@ -4382,7 +4433,7 @@ name = "sled-state-inspector" version = "0.1.0" dependencies = [ "atty", - "clap 3.2.22", + "clap 3.2.23", "futures", "matrix-sdk-base", "matrix-sdk-sled", @@ -4402,9 +4453,9 @@ checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" [[package]] name = "smallvec" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" [[package]] name = "smawk" @@ -4414,9 +4465,9 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043" [[package]] name = "socket2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10c98bba371b9b22a71a9414e420f92ddeb2369239af08200816169d5e2dd7aa" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" dependencies = [ "libc", "winapi", @@ -4505,21 +4556,21 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "symbolic-common" -version = "10.1.1" +version = "10.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac457d054f793cedfde6f32d21d692b8351cfec9084fefd0470c0373f6d799bc" +checksum = "278004260fdbbef71cae967d46099881a4cd3c523fc4b27c254f6b9a6b0be810" dependencies = [ "debugid", "memmap2", "stable_deref_trait", - "uuid 1.1.2", + "uuid 1.2.2", ] [[package]] name = "symbolic-demangle" -version = "10.1.1" +version = "10.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48808b846eef84e0ac06365dc620f028ae632355e5dcffc007bf1b2bf5eab17b" +checksum = "db66f92614753daa97af9c0c564cd712f8ea3af435e0216ac7e97390ad723439" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -4528,9 +4579,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.99" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", @@ -4620,15 +4671,21 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" +checksum = "b7b3e525a49ec206798b40326a44121291b530c963cfb01018f63e135bac543d" dependencies = [ "smawk", "unicode-linebreak", "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "thiserror" version = "1.0.37" @@ -4682,12 +4739,12 @@ dependencies = [ [[package]] name = "tiff" -version = "0.7.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7259662e32d1e219321eb309d5f9d898b779769d81b76e762c07c8e5d38fcb65" +checksum = "f17def29300a156c19ae30814710d9c63cd50288a49c6fd3a10ccfbe4cf886fd" dependencies = [ "flate2", - "jpeg-decoder 0.2.6", + "jpeg-decoder 0.3.0", "weezl", ] @@ -4704,13 +4761,29 @@ dependencies = [ [[package]] name = "time" -version = "0.3.14" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3f9a28b618c3a6b9251b6908e9c99e04b9e5c02e6581ccbb67d59c34ef7f9b" +checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa 1.0.3", - "libc", - "num_threads", + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +dependencies = [ + "time-core", ] [[package]] @@ -4740,9 +4813,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.21.2" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e03c497dc955702ba729190dc4aac6f2a0ce97f913e5b1b5912fc5039d9099" +checksum = "d76ce4a75fb488c605c54bf610f221cea8b0dafb53333c1a67e8ee199dcd2ae3" dependencies = [ "autocfg", "bytes", @@ -4802,9 +4875,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54d54117d6fdc4e4fea40fe1e4e566b3505700e148a6827e59b34b0d2600d9" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" dependencies = [ "futures-core", "pin-project-lite", @@ -4813,9 +4886,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc463cd8deddc3770d20f9852143d50bf6094e640b485cb2e189a2099085ff45" +checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" dependencies = [ "bytes", "futures-core", @@ -4950,7 +5023,7 @@ dependencies = [ "sharded-slab", "smallvec", "thread_local", - "time 0.3.14", + "time 0.3.17", "tracing", "tracing-core", "tracing-log", @@ -4996,7 +5069,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66f252bf8b07c6fd708ddd6349b5f044ae5b488b26929c745728d9c7e2cebfa6" dependencies = [ - "textwrap 0.15.1", + "textwrap 0.15.2", "tuirealm", "unicode-width", ] @@ -5049,9 +5122,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" [[package]] name = "unicode-ident" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "unicode-linebreak" @@ -5065,18 +5138,18 @@ dependencies = [ [[package]] name = "unicode-normalization" -version = "0.1.21" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854cbdc4f7bc6ae19c820d44abdc3277ac3e1b2b93db20a636825d9322fb60e6" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" +checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a" [[package]] name = "unicode-width" @@ -5086,9 +5159,9 @@ checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" [[package]] name = "unicode-xid" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "uniffi" @@ -5115,7 +5188,7 @@ dependencies = [ "askama", "bincode", "camino", - "clap 3.2.22", + "clap 3.2.23", "fs-err", "goblin", "heck 0.4.0", @@ -5176,9 +5249,9 @@ dependencies = [ [[package]] name = "unsafe-libyaml" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "931179334a56395bcf64ba5e0ff56781381c1a5832178280c7d7f91d1679aeb0" +checksum = "c1e5fa573d8ac5f1a856f8d7be41d390ee973daf97c806b2c1a465e4e1406e68" [[package]] name = "untrusted" @@ -5210,16 +5283,17 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", ] [[package]] name = "uuid" -version = "1.1.2" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f" +checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" dependencies = [ - "getrandom 0.2.7", + "getrandom 0.2.8", + "wasm-bindgen", ] [[package]] @@ -5253,7 +5327,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f20153a1c82ac5f1243b62e80f067ae608facc415c6ef82f88426a61c79886" dependencies = [ "aes", - "arrayvec 0.7.2", + "arrayvec", "base64", "cbc", "ed25519-dalek", @@ -5264,7 +5338,7 @@ dependencies = [ "rand 0.7.3", "serde", "serde_json", - "sha2 0.10.3", + "sha2 0.10.6", "subtle", "thiserror", "x25519-dalek", @@ -5436,9 +5510,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.59" +version = "0.3.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed055ab27f941423197eb86b2035720b1a3ce40504df082cac2ecc6ed73335a1" +checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" dependencies = [ "js-sys", "wasm-bindgen", @@ -5456,9 +5530,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.22.4" +version = "0.22.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf" +checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be" dependencies = [ "webpki", ] @@ -5520,43 +5594,100 @@ version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" dependencies = [ - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_msvc", + "windows_aarch64_msvc 0.36.1", + "windows_i686_gnu 0.36.1", + "windows_i686_msvc 0.36.1", + "windows_x86_64_gnu 0.36.1", + "windows_x86_64_msvc 0.36.1", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" + [[package]] name = "windows_aarch64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" + [[package]] name = "windows_i686_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +[[package]] +name = "windows_i686_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" + [[package]] name = "windows_i686_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +[[package]] +name = "windows_i686_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" + [[package]] name = "windows_x86_64_gnu" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" + [[package]] name = "windows_x86_64_msvc" version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + [[package]] name = "winreg" version = "0.10.1" @@ -5568,9 +5699,9 @@ dependencies = [ [[package]] name = "wiremock" -version = "0.5.14" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc3c7b7557dbfdad6431b5a51196c9110cef9d83f6a9b26699f35cdc0ae113ec" +checksum = "249dc68542861d17eae4b4e5e8fb381c2f9e8f255a84f6771d5fdf8b6c03ce3c" dependencies = [ "assert-json-diff", "async-trait", @@ -5629,7 +5760,7 @@ name = "xtask" version = "0.1.0" dependencies = [ "camino", - "clap 4.0.18", + "clap 4.0.26", "fs_extra", "serde", "serde_json", From de71d7e434daf7f9c2078b9073083c4ad3fed639 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 21 Nov 2022 15:53:59 +0100 Subject: [PATCH 63/78] refactor(base): Simplify default memory store initialization --- crates/matrix-sdk-base/src/client.rs | 12 +++------- crates/matrix-sdk-base/src/store/mod.rs | 29 ++++++++++++++----------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index 26a9b9586..d4ce5881a 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -25,8 +25,7 @@ use futures_signals::signal::ReadOnlyMutable; use matrix_sdk_common::{instant::Instant, locks::RwLock}; #[cfg(feature = "e2e-encryption")] use matrix_sdk_crypto::{ - store::{CryptoStore, MemoryStore as MemoryCryptoStore}, - EncryptionSettings, OlmError, OlmMachine, ToDeviceRequest, + store::CryptoStore, EncryptionSettings, OlmError, OlmMachine, ToDeviceRequest, }; #[cfg(feature = "e2e-encryption")] use once_cell::sync::OnceCell; @@ -108,15 +107,10 @@ impl BaseClient { /// * `config` - An optional session if the user already has one from a /// previous login call. pub fn with_store_config(config: StoreConfig) -> Self { - let store = config.state_store.map(Store::new).unwrap_or_else(Store::open_memory_store); - #[cfg(feature = "e2e-encryption")] - let crypto_store = - config.crypto_store.unwrap_or_else(|| Arc::new(MemoryCryptoStore::default())); - BaseClient { - store, + store: Store::new(config.state_store), #[cfg(feature = "e2e-encryption")] - crypto_store, + crypto_store: config.crypto_store, #[cfg(feature = "e2e-encryption")] olm_machine: Default::default(), } diff --git a/crates/matrix-sdk-base/src/store/mod.rs b/crates/matrix-sdk-base/src/store/mod.rs index 59cda12d3..1ff84c340 100644 --- a/crates/matrix-sdk-base/src/store/mod.rs +++ b/crates/matrix-sdk-base/src/store/mod.rs @@ -514,13 +514,6 @@ pub(crate) struct Store { } impl Store { - /// Create a new Store with the default `MemoryStore` - pub fn open_memory_store() -> Self { - let inner = Arc::new(MemoryStore::new()); - - Self::new(inner) - } - /// Create a new store, wrapping the given `StateStore` pub fn new(inner: Arc) -> Self { Self { @@ -822,11 +815,11 @@ impl StateChanges { /// /// let store_config = StoreConfig::new(); /// ``` -#[derive(Clone, Default)] +#[derive(Clone)] pub struct StoreConfig { #[cfg(feature = "e2e-encryption")] - pub(crate) crypto_store: Option>, - pub(crate) state_store: Option>, + pub(crate) crypto_store: Arc, + pub(crate) state_store: Arc, } #[cfg(not(tarpaulin_include))] @@ -840,7 +833,11 @@ impl StoreConfig { /// Create a new default `StoreConfig`. #[must_use] pub fn new() -> Self { - Default::default() + Self { + #[cfg(feature = "e2e-encryption")] + crypto_store: Arc::new(matrix_sdk_crypto::store::MemoryStore::new()), + state_store: Arc::new(MemoryStore::new()), + } } /// Set a custom implementation of a `CryptoStore`. @@ -848,13 +845,19 @@ impl StoreConfig { /// The crypto store must be opened before being set. #[cfg(feature = "e2e-encryption")] pub fn crypto_store(mut self, store: impl IntoCryptoStore) -> Self { - self.crypto_store = Some(store.into_crypto_store()); + self.crypto_store = store.into_crypto_store(); self } /// Set a custom implementation of a `StateStore`. pub fn state_store(mut self, store: impl IntoStateStore) -> Self { - self.state_store = Some(store.into_state_store()); + self.state_store = store.into_state_store(); self } } + +impl Default for StoreConfig { + fn default() -> Self { + Self::new() + } +} From 38c38bc9f07cca1ff94eb6ce389ba5e892cbb04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Fri, 18 Nov 2022 12:41:26 +0100 Subject: [PATCH 64/78] feat!(bindings): Expose the improved result of the verify_backup method --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 3 +- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 59 +++++++++++++++++-- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 16 ++++- 3 files changed, 71 insertions(+), 7 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index cc6c7e57c..06d82f8be 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -27,9 +27,10 @@ pub use error::{ }; use js_int::UInt; pub use logger::{set_logger, Logger}; -pub use machine::{KeyRequestPair, OlmMachine}; +pub use machine::{KeyRequestPair, OlmMachine, SignatureCheckResult}; use matrix_sdk_common::deserialized_responses::VerificationState; use matrix_sdk_crypto::{ + backups::SignatureState, types::{EventEncryptionAlgorithm as RustEventEncryptionAlgorithm, SigningKey}, EncryptionSettings as RustEncryptionSettings, LocalTrust, }; diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 223eabb44..11c55dd27 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -10,9 +10,15 @@ use base64::{decode_config, encode, STANDARD_NO_PAD}; use js_int::UInt; use matrix_sdk_common::deserialized_responses::AlgorithmInfo; use matrix_sdk_crypto::{ - backups::MegolmV1BackupKey as RustBackupKey, decrypt_room_key_export, encrypt_room_key_export, - matrix_sdk_qrcode::QrVerificationData, olm::ExportedRoomKey, store::RecoveryKey, LocalTrust, - OlmMachine as InnerMachine, UserIdentities, Verification as RustVerification, + backups::{ + MegolmV1BackupKey as RustBackupKey, SignatureCheckResult as RustSignatureCheckResult, + SignatureState, + }, + decrypt_room_key_export, encrypt_room_key_export, + matrix_sdk_qrcode::QrVerificationData, + olm::ExportedRoomKey, + store::RecoveryKey, + LocalTrust, OlmMachine as InnerMachine, UserIdentities, Verification as RustVerification, }; use ruma::{ api::{ @@ -66,6 +72,46 @@ pub struct KeyRequestPair { pub key_request: Request, } +/// The result of a signature check of a signed JSON object. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SignatureCheckResult { + /// The result of the signature check using the public key of our own + /// device. + pub device_signature: SignatureState, + /// The result of the signature check using the public key of our own + /// user identity. + pub user_identity_signature: SignatureState, + /// The result of signature checks using public keys of other devices we + /// own. + pub other_signatures: HashMap, + /// Is the signed JSON object trusted. + /// + /// This flag tells us if the result has a valid signature from any of the + /// following: + /// + /// * Our own device + /// * Our own user identity, provided the identity is trusted as well + /// * Any of our own devices, provided the device is trusted as well + pub trusted: bool, +} + +impl From for SignatureCheckResult { + fn from(r: RustSignatureCheckResult) -> Self { + let trusted = r.trusted(); + + Self { + device_signature: r.device_signature, + user_identity_signature: r.user_identity_signature, + other_signatures: r + .other_signatures + .into_iter() + .map(|(k, v)| (k.to_string(), v)) + .collect(), + trusted, + } + } +} + #[uniffi::export] impl OlmMachine { /// Get the user ID of the owner of this `OlmMachine`. @@ -1461,12 +1507,15 @@ impl OlmMachine { /// } /// } /// ``` - pub fn verify_backup(&self, backup_info: &str) -> Result { + pub fn verify_backup( + &self, + backup_info: &str, + ) -> Result { let backup_info = serde_json::from_str(backup_info)?; Ok(self .runtime .block_on(self.inner.backup_machine().verify_backup(backup_info, false))? - .trusted()) + .into()) } } diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index 59397eda0..2f0bc43a8 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -431,7 +431,7 @@ interface OlmMachine { BackupKeys? get_backup_keys(); boolean backup_enabled(); [Throws=CryptoStoreError] - boolean verify_backup([ByRef] string auth_data); + SignatureCheckResult verify_backup([ByRef] string auth_data); }; dictionary PassphraseInfo { @@ -439,6 +439,20 @@ dictionary PassphraseInfo { i32 private_key_iterations; }; +dictionary SignatureCheckResult { + SignatureState device_signature; + SignatureState user_identity_signature; + record other_signatures; + boolean trusted; +}; + +enum SignatureState { + "Missing", + "Invalid", + "ValidButNotTrusted", + "ValidAndTrusted", +}; + dictionary MegolmV1BackupKey { string public_key; record> signatures; From 3113f6698fa26be0f71ee4779795161602f745de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damir=20Jeli=C4=87?= Date: Tue, 22 Nov 2022 11:46:37 +0100 Subject: [PATCH 65/78] chore: Don't use the term check for signature verification --- bindings/matrix-sdk-crypto-ffi/src/lib.rs | 2 +- bindings/matrix-sdk-crypto-ffi/src/machine.rs | 24 +++++++++---------- bindings/matrix-sdk-crypto-ffi/src/olm.udl | 6 ++--- crates/matrix-sdk-crypto/src/backups/mod.rs | 20 ++++++++-------- 4 files changed, 26 insertions(+), 26 deletions(-) diff --git a/bindings/matrix-sdk-crypto-ffi/src/lib.rs b/bindings/matrix-sdk-crypto-ffi/src/lib.rs index 06d82f8be..98a6e4c8f 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/lib.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/lib.rs @@ -27,7 +27,7 @@ pub use error::{ }; use js_int::UInt; pub use logger::{set_logger, Logger}; -pub use machine::{KeyRequestPair, OlmMachine, SignatureCheckResult}; +pub use machine::{KeyRequestPair, OlmMachine, SignatureVerification}; use matrix_sdk_common::deserialized_responses::VerificationState; use matrix_sdk_crypto::{ backups::SignatureState, diff --git a/bindings/matrix-sdk-crypto-ffi/src/machine.rs b/bindings/matrix-sdk-crypto-ffi/src/machine.rs index 11c55dd27..28b689132 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/machine.rs +++ b/bindings/matrix-sdk-crypto-ffi/src/machine.rs @@ -11,8 +11,8 @@ use js_int::UInt; use matrix_sdk_common::deserialized_responses::AlgorithmInfo; use matrix_sdk_crypto::{ backups::{ - MegolmV1BackupKey as RustBackupKey, SignatureCheckResult as RustSignatureCheckResult, - SignatureState, + MegolmV1BackupKey as RustBackupKey, SignatureState, + SignatureVerification as RustSignatureCheckResult, }, decrypt_room_key_export, encrypt_room_key_export, matrix_sdk_qrcode::QrVerificationData, @@ -72,18 +72,18 @@ pub struct KeyRequestPair { pub key_request: Request, } -/// The result of a signature check of a signed JSON object. +/// The result of a signature verification of a signed JSON object. #[derive(Clone, Debug, PartialEq, Eq)] -pub struct SignatureCheckResult { - /// The result of the signature check using the public key of our own +pub struct SignatureVerification { + /// The result of the signature verification using the public key of our own /// device. pub device_signature: SignatureState, - /// The result of the signature check using the public key of our own + /// The result of the signature verification using the public key of our own /// user identity. pub user_identity_signature: SignatureState, - /// The result of signature checks using public keys of other devices we - /// own. - pub other_signatures: HashMap, + /// The result of the signature verification using public keys of other + /// devices we own. + pub other_devices_signatures: HashMap, /// Is the signed JSON object trusted. /// /// This flag tells us if the result has a valid signature from any of the @@ -95,14 +95,14 @@ pub struct SignatureCheckResult { pub trusted: bool, } -impl From for SignatureCheckResult { +impl From for SignatureVerification { fn from(r: RustSignatureCheckResult) -> Self { let trusted = r.trusted(); Self { device_signature: r.device_signature, user_identity_signature: r.user_identity_signature, - other_signatures: r + other_devices_signatures: r .other_signatures .into_iter() .map(|(k, v)| (k.to_string(), v)) @@ -1510,7 +1510,7 @@ impl OlmMachine { pub fn verify_backup( &self, backup_info: &str, - ) -> Result { + ) -> Result { let backup_info = serde_json::from_str(backup_info)?; Ok(self diff --git a/bindings/matrix-sdk-crypto-ffi/src/olm.udl b/bindings/matrix-sdk-crypto-ffi/src/olm.udl index 2f0bc43a8..4d595bf87 100644 --- a/bindings/matrix-sdk-crypto-ffi/src/olm.udl +++ b/bindings/matrix-sdk-crypto-ffi/src/olm.udl @@ -431,7 +431,7 @@ interface OlmMachine { BackupKeys? get_backup_keys(); boolean backup_enabled(); [Throws=CryptoStoreError] - SignatureCheckResult verify_backup([ByRef] string auth_data); + SignatureVerification verify_backup([ByRef] string auth_data); }; dictionary PassphraseInfo { @@ -439,10 +439,10 @@ dictionary PassphraseInfo { i32 private_key_iterations; }; -dictionary SignatureCheckResult { +dictionary SignatureVerification { SignatureState device_signature; SignatureState user_identity_signature; - record other_signatures; + record other_devices_signatures; boolean trusted; }; diff --git a/crates/matrix-sdk-crypto/src/backups/mod.rs b/crates/matrix-sdk-crypto/src/backups/mod.rs index 2dfedf053..d723a8548 100644 --- a/crates/matrix-sdk-crypto/src/backups/mod.rs +++ b/crates/matrix-sdk-crypto/src/backups/mod.rs @@ -85,21 +85,21 @@ impl From for OutgoingRequest { } } -/// The result of a signature check of a signed JSON object. +/// The result of a signature verification of a signed JSON object. #[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct SignatureCheckResult { - /// The result of the signature check using the public key of our own +pub struct SignatureVerification { + /// The result of the signature verification using the public key of our own /// device. pub device_signature: SignatureState, - /// The result of the signature check using the public key of our own + /// The result of the signature verification using the public key of our own /// user identity. pub user_identity_signature: SignatureState, - /// The result of signature checks using public keys of other devices we - /// own. + /// The result of the signature verification using public keys of other + /// devices we own. pub other_signatures: BTreeMap, } -impl SignatureCheckResult { +impl SignatureVerification { /// Is the result considered to be trusted? /// /// This tells us if the result has a valid signature from any of the @@ -294,7 +294,7 @@ impl BackupMachine { &self, auth_data: MegolmV1AuthData, compute_all_signatures: bool, - ) -> Result { + ) -> Result { trace!(?auth_data, "Verifying backup auth data"); let serialized_auth_data = match auth_data.to_canonical_json() { @@ -327,7 +327,7 @@ impl BackupMachine { Default::default() }; - Ok(SignatureCheckResult { device_signature, user_identity_signature, other_signatures }) + Ok(SignatureVerification { device_signature, user_identity_signature, other_signatures }) } /// Verify some backup info that we downloaded from the server. @@ -348,7 +348,7 @@ impl BackupMachine { &self, backup_info: RoomKeyBackupInfo, compute_all_signatures: bool, - ) -> Result { + ) -> Result { trace!(?backup_info, "Verifying backup auth data"); if let RoomKeyBackupInfo::MegolmBackupV1Curve25519AesSha2(data) = backup_info { From 1107f27c3d70b8de8e3f27528b4ad060a5c443a9 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Tue, 22 Nov 2022 16:21:58 +0200 Subject: [PATCH 66/78] fix(ffi): Use the right path for generated source files and only copy the generated folder contents (#1226) --- xtask/src/swift.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/xtask/src/swift.rs b/xtask/src/swift.rs index fb74c5e40..fe0257e85 100644 --- a/xtask/src/swift.rs +++ b/xtask/src/swift.rs @@ -214,16 +214,12 @@ fn build_xcframework( } create_dir_all(framework_target.as_path())?; create_dir_all(swift_target.as_path())?; - fs_extra::dir::copy( - xcframework_path.as_path(), - framework_target.as_path(), - &fs_extra::dir::CopyOptions::default(), - )?; - fs_extra::dir::copy( - swift_dir.as_path(), - framework_target.as_path(), - &fs_extra::dir::CopyOptions::default(), - )?; + + let mut copy_options = fs_extra::dir::CopyOptions::default(); + copy_options.content_only = true; + + fs_extra::dir::copy(xcframework_path.as_path(), framework_target.as_path(), ©_options)?; + fs_extra::dir::copy(swift_dir.as_path(), swift_target.as_path(), ©_options)?; } println!("-- All done and hunky dory. Enjoy!"); From f7d412960713c4f7cf82611a06bd12b2f2b925c6 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 24 Nov 2022 12:22:17 +0100 Subject: [PATCH 67/78] fix(sdk): Fix logic for updating an existing read marker --- crates/matrix-sdk/src/room/timeline/event_handler.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 82e8bb152..7edd35407 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -256,13 +256,13 @@ fn update_fully_read_item( ) { let Some(fully_read_event) = fully_read_event else { return }; let old_idx = find_fully_read(items_lock); - let new_idx = find_event(items_lock, fully_read_event).map(|(idx, _)| idx + 1); + let new_idx = find_event(items_lock, fully_read_event).map(|(idx, _)| idx); match (old_idx, new_idx) { (None, None) => {} (None, Some(idx)) => { *fully_read_event_in_timeline = true; let item = TimelineItem::Virtual(VirtualTimelineItem::ReadMarker); - items_lock.insert_cloned(idx, item.into()); + items_lock.insert_cloned(idx + 1, item.into()); } (Some(_), None) => { // Keep the current position of the read marker, hopefully we From 8ce216974f170f1998fc93e41244a6b14257be24 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 24 Nov 2022 12:36:14 +0100 Subject: [PATCH 68/78] test(sdk): Add a regression test for read marker updates --- crates/matrix-sdk/src/room/timeline/tests.rs | 25 +++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index 33d63e508..f64c091f5 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -45,7 +45,9 @@ use ruma::{ }; use serde_json::{json, Value as JsonValue}; -use super::{EncryptedMessage, TimelineInner, TimelineItem, TimelineItemContent}; +use super::{ + EncryptedMessage, TimelineInner, TimelineItem, TimelineItemContent, VirtualTimelineItem, +}; static ALICE: Lazy<&UserId> = Lazy::new(|| user_id!("@alice:server.name")); static BOB: Lazy<&UserId> = Lazy::new(|| user_id!("@bob:other.server")); @@ -235,6 +237,27 @@ async fn unable_to_decrypt() { assert_eq!(text, "It's a secret to everybody"); } +#[async_test] +async fn update_read_marker() { + let timeline = TestTimeline::new(&ALICE); + let mut stream = timeline.stream(); + + timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("A")).await; + let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); + let event_id = item.as_event().unwrap().event_id().unwrap().to_owned(); + + timeline.inner.set_fully_read_event(event_id).await; + let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); + assert_matches!(item.as_virtual(), Some(VirtualTimelineItem::ReadMarker)); + + timeline.handle_live_message_event(&BOB, RoomMessageEventContent::text_plain("B")).await; + let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); + let event_id = item.as_event().unwrap().event_id().unwrap().to_owned(); + + timeline.inner.set_fully_read_event(event_id).await; + assert_matches!(stream.next().await, Some(VecDiff::Move { old_index: 1, new_index: 2 })); +} + struct TestTimeline { own_user_id: OwnedUserId, inner: TimelineInner, From 43f0ba771198cefded80ce2f1d92c73029245d64 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 24 Nov 2022 12:37:20 +0100 Subject: [PATCH 69/78] chore: Appease clippy --- crates/matrix-sdk-crypto/src/verification/machine.rs | 1 + crates/matrix-sdk-crypto/src/verification/requests.rs | 4 ++-- xtask/src/swift.rs | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/matrix-sdk-crypto/src/verification/machine.rs b/crates/matrix-sdk-crypto/src/verification/machine.rs index 221794c5f..05a9a2fce 100644 --- a/crates/matrix-sdk-crypto/src/verification/machine.rs +++ b/crates/matrix-sdk-crypto/src/verification/machine.rs @@ -641,6 +641,7 @@ mod tests { } #[cfg(not(target_os = "macos"))] + #[allow(unknown_lints, clippy::unchecked_duration_subtraction)] #[async_test] async fn timing_out() { let (alice_machine, bob) = setup_verification_machine().await; diff --git a/crates/matrix-sdk-crypto/src/verification/requests.rs b/crates/matrix-sdk-crypto/src/verification/requests.rs index 63febb9cb..2e7deaeb7 100644 --- a/crates/matrix-sdk-crypto/src/verification/requests.rs +++ b/crates/matrix-sdk-crypto/src/verification/requests.rs @@ -949,9 +949,9 @@ struct Ready { } impl RequestState { - fn to_started_sas<'a>( + fn to_started_sas( &self, - content: &StartContent<'a>, + content: &StartContent<'_>, identities: IdentitiesBeingVerified, we_started: bool, request_handle: RequestHandle, diff --git a/xtask/src/swift.rs b/xtask/src/swift.rs index fe0257e85..db54d6639 100644 --- a/xtask/src/swift.rs +++ b/xtask/src/swift.rs @@ -215,8 +215,7 @@ fn build_xcframework( create_dir_all(framework_target.as_path())?; create_dir_all(swift_target.as_path())?; - let mut copy_options = fs_extra::dir::CopyOptions::default(); - copy_options.content_only = true; + let copy_options = fs_extra::dir::CopyOptions { content_only: true, ..Default::default() }; fs_extra::dir::copy(xcframework_path.as_path(), framework_target.as_path(), ©_options)?; fs_extra::dir::copy(swift_dir.as_path(), swift_target.as_path(), ©_options)?; From d027005e870d6bab1426cf6394d1bdd4ffe4abbb Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 21 Nov 2022 17:21:04 +0100 Subject: [PATCH 70/78] chore(sdk): Rewrap info! invocation --- crates/matrix-sdk/src/room/timeline/event_handler.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 7edd35407..c0eb4e22a 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -439,10 +439,7 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { let msg = match &item.content { TimelineItemContent::Message(msg) => msg, TimelineItemContent::RedactedMessage => { - info!( - %event_id, - "Edit event applies to a redacted message, discarding" - ); + info!(%event_id, "Edit event applies to a redacted message, discarding"); return None; } TimelineItemContent::UnableToDecrypt(_) => { From d0f0b650bd84c997248c1e072c4e1b04acd6f8b5 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 21 Nov 2022 18:07:28 +0100 Subject: [PATCH 71/78] chore: Move impl for type after its definition --- .../src/room/timeline/event_handler.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index c0eb4e22a..7d5c1e35b 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -318,6 +318,16 @@ struct TimelineEventMetadata { encryption_info: Option, } +#[derive(Clone)] +enum TimelineEventKind { + Message { content: AnyMessageLikeEventContent }, + RedactedMessage, + Redaction { redacts: OwnedEventId, content: RoomRedactionEventContent }, + // FIXME: Split further for state keys of different type + State { _content: AnyStateEventContent }, + RedactedState, // AnyRedactedStateEventContent +} + impl From for TimelineEventKind { fn from(event: AnySyncTimelineEvent) -> Self { match event { @@ -340,16 +350,6 @@ impl From for TimelineEventKind { } } -#[derive(Clone)] -enum TimelineEventKind { - Message { content: AnyMessageLikeEventContent }, - RedactedMessage, - Redaction { redacts: OwnedEventId, content: RoomRedactionEventContent }, - // FIXME: Split further for state keys of different type - State { _content: AnyStateEventContent }, - RedactedState, // AnyRedactedStateEventContent -} - enum TimelineItemPosition { Start, End, From 6a1edf133df12605b99ada049fe2cf04c52e99f2 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 21 Nov 2022 18:24:01 +0100 Subject: [PATCH 72/78] refactor(sdk): Add NewEventTimelineItem::from_content --- .../matrix-sdk/src/room/timeline/event_handler.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 7d5c1e35b..2904a665a 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -698,16 +698,14 @@ impl NewEventTimelineItem { } fn unable_to_decrypt(content: RoomEncryptedEventContent) -> Self { - Self { - content: TimelineItemContent::UnableToDecrypt(content.into()), - reactions: BundledReactions::default(), - } + Self::from_content(TimelineItemContent::UnableToDecrypt(content.into())) } fn redacted_message() -> Self { - Self { - content: TimelineItemContent::RedactedMessage, - reactions: BundledReactions::default(), - } + Self::from_content(TimelineItemContent::RedactedMessage) + } + + fn from_content(content: TimelineItemContent) -> Self { + Self { content, reactions: BundledReactions::default() } } } From c808a72914e2febed8189fa96a9505ad8d9fb1a3 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Mon, 21 Nov 2022 18:31:58 +0100 Subject: [PATCH 73/78] feat(sdk): Add FailedToParse timeline items --- crates/matrix-sdk/src/events.rs | 147 ++++++++++++++++++ crates/matrix-sdk/src/lib.rs | 2 + .../src/room/timeline/event_handler.rs | 133 ++++++++++++---- .../src/room/timeline/event_item.rs | 25 ++- crates/matrix-sdk/src/room/timeline/tests.rs | 94 ++++++++++- 5 files changed, 367 insertions(+), 34 deletions(-) create mode 100644 crates/matrix-sdk/src/events.rs diff --git a/crates/matrix-sdk/src/events.rs b/crates/matrix-sdk/src/events.rs new file mode 100644 index 000000000..cc6b8a4ab --- /dev/null +++ b/crates/matrix-sdk/src/events.rs @@ -0,0 +1,147 @@ +use ruma::{ + events::{ + EventContent, MessageLikeEventContent, MessageLikeEventType, OriginalSyncMessageLikeEvent, + OriginalSyncStateEvent, RedactedEventContent, RedactedMessageLikeEventContent, + RedactedStateEventContent, RedactedSyncMessageLikeEvent, RedactedSyncStateEvent, Relations, + StateEventContent, StateEventType, StateUnsigned, + }, + serde::from_raw_json_value, + EventId, MilliSecondsSinceUnixEpoch, TransactionId, UserId, +}; +use serde::{de, Deserialize, Serialize}; +use serde_json::value::RawValue as RawJsonValue; + +#[allow(clippy::large_enum_variant)] +pub(crate) enum SyncTimelineEventWithoutContent { + OriginalMessageLike(OriginalSyncMessageLikeEvent), + RedactedMessageLike(RedactedSyncMessageLikeEvent), + OriginalState(OriginalSyncStateEvent), + RedactedState(RedactedSyncStateEvent), +} + +impl SyncTimelineEventWithoutContent { + pub(crate) fn event_id(&self) -> &EventId { + match self { + Self::OriginalMessageLike(ev) => &ev.event_id, + Self::RedactedMessageLike(ev) => &ev.event_id, + Self::OriginalState(ev) => &ev.event_id, + Self::RedactedState(ev) => &ev.event_id, + } + } + + pub(crate) fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch { + match self { + SyncTimelineEventWithoutContent::OriginalMessageLike(ev) => ev.origin_server_ts, + SyncTimelineEventWithoutContent::RedactedMessageLike(ev) => ev.origin_server_ts, + SyncTimelineEventWithoutContent::OriginalState(ev) => ev.origin_server_ts, + SyncTimelineEventWithoutContent::RedactedState(ev) => ev.origin_server_ts, + } + } + + pub(crate) fn relations(&self) -> Option<&Relations> { + match self { + SyncTimelineEventWithoutContent::OriginalMessageLike(ev) => { + ev.unsigned.relations.as_ref() + } + SyncTimelineEventWithoutContent::OriginalState(ev) => ev.unsigned.relations.as_ref(), + SyncTimelineEventWithoutContent::RedactedMessageLike(_) + | SyncTimelineEventWithoutContent::RedactedState(_) => None, + } + } + + pub(crate) fn sender(&self) -> &UserId { + match self { + Self::OriginalMessageLike(ev) => &ev.sender, + Self::RedactedMessageLike(ev) => &ev.sender, + Self::OriginalState(ev) => &ev.sender, + Self::RedactedState(ev) => &ev.sender, + } + } + + pub(crate) fn transaction_id(&self) -> Option<&TransactionId> { + match self { + SyncTimelineEventWithoutContent::OriginalMessageLike(ev) => { + ev.unsigned.transaction_id.as_deref() + } + SyncTimelineEventWithoutContent::OriginalState(ev) => { + ev.unsigned.transaction_id.as_deref() + } + SyncTimelineEventWithoutContent::RedactedMessageLike(_) + | SyncTimelineEventWithoutContent::RedactedState(_) => None, + } + } +} + +#[derive(Deserialize)] +struct EventDeHelper { + state_key: Option, + #[serde(default)] + unsigned: UnsignedDeHelper, +} + +#[derive(Deserialize, Default)] +struct UnsignedDeHelper { + redacted_because: Option, +} + +impl<'de> Deserialize<'de> for SyncTimelineEventWithoutContent { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let EventDeHelper { state_key, unsigned } = from_raw_json_value(&json)?; + + Ok(match (state_key.is_some(), unsigned.redacted_because.is_some()) { + (false, false) => Self::OriginalMessageLike(from_raw_json_value(&json)?), + (false, true) => Self::RedactedMessageLike(from_raw_json_value(&json)?), + (true, false) => Self::OriginalState(from_raw_json_value(&json)?), + (true, true) => Self::RedactedState(from_raw_json_value(&json)?), + }) + } +} + +#[derive(Serialize)] +pub(crate) struct NoMessageLikeEventContent { + #[serde(skip)] + pub event_type: MessageLikeEventType, +} + +impl EventContent for NoMessageLikeEventContent { + type EventType = MessageLikeEventType; + + fn event_type(&self) -> Self::EventType { + self.event_type.clone() + } + + fn from_parts(event_type: &str, _content: &RawJsonValue) -> serde_json::Result { + Ok(Self { event_type: event_type.into() }) + } +} +impl MessageLikeEventContent for NoMessageLikeEventContent {} +impl RedactedEventContent for NoMessageLikeEventContent {} +impl RedactedMessageLikeEventContent for NoMessageLikeEventContent {} + +#[derive(Clone, Debug, Serialize)] +pub(crate) struct NoStateEventContent { + #[serde(skip)] + pub event_type: StateEventType, +} + +impl EventContent for NoStateEventContent { + type EventType = StateEventType; + + fn event_type(&self) -> Self::EventType { + self.event_type.clone() + } + + fn from_parts(event_type: &str, _content: &RawJsonValue) -> serde_json::Result { + Ok(Self { event_type: event_type.into() }) + } +} +impl StateEventContent for NoStateEventContent { + type StateKey = String; + type Unsigned = StateUnsigned; +} +impl RedactedEventContent for NoStateEventContent {} +impl RedactedStateEventContent for NoStateEventContent {} diff --git a/crates/matrix-sdk/src/lib.rs b/crates/matrix-sdk/src/lib.rs index 4260ec2a8..8120080b9 100644 --- a/crates/matrix-sdk/src/lib.rs +++ b/crates/matrix-sdk/src/lib.rs @@ -43,6 +43,8 @@ mod sliding_sync; #[cfg(feature = "e2e-encryption")] pub mod encryption; +#[cfg(feature = "experimental-timeline")] +mod events; pub use account::Account; #[cfg(feature = "sso-login")] diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 2904a665a..0c169e7a8 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -35,7 +35,7 @@ use ruma::{ }, }, AnyMessageLikeEventContent, AnyStateEventContent, AnySyncMessageLikeEvent, - AnySyncTimelineEvent, Relations, + AnySyncTimelineEvent, MessageLikeEventType, Relations, StateEventType, }, serde::Raw, uint, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedTransactionId, OwnedUserId, @@ -48,6 +48,7 @@ use super::{ find_event, find_fully_read, EventTimelineItem, Message, TimelineInner, TimelineInnerMetadata, TimelineItem, TimelineItemContent, TimelineKey, VirtualTimelineItem, }; +use crate::events::SyncTimelineEventWithoutContent; impl TimelineInner { pub(super) async fn handle_live_event( @@ -221,32 +222,38 @@ fn handle_remote_event( timeline_items: &mut MutableVecLockMut<'_, Arc>, timeline_meta: &mut MutexGuard<'_, TimelineInnerMetadata>, ) { - let event = match raw.deserialize() { - Ok(ev) => ev, - Err(_e) => { - // TODO: Add some sort of error timeline item - return; - } - }; + let (event_id, sender, origin_server_ts, txn_id, relations, event_kind) = + match raw.deserialize() { + Ok(event) => ( + event.event_id().to_owned(), + event.sender().to_owned(), + event.origin_server_ts(), + event.transaction_id().map(ToOwned::to_owned), + event.relations().cloned(), + event.into(), + ), + Err(e) => match raw.deserialize_as::() { + Ok(event) => ( + event.event_id().to_owned(), + event.sender().to_owned(), + event.origin_server_ts(), + event.transaction_id().map(ToOwned::to_owned), + event.relations().cloned(), + TimelineEventKind::failed_to_parse(event, e), + ), + Err(e) => { + warn!("Failed to deserialize timeline event: {e}"); + return; + } + }, + }; - let sender = event.sender().to_owned(); let is_own_event = sender == own_user_id; - let event_meta = TimelineEventMetadata { - sender, - is_own_event, - relations: event.relations().cloned(), - encryption_info, - }; - let flow = Flow::Remote { - event_id: event.event_id().to_owned(), - origin_server_ts: event.origin_server_ts(), - raw_event: raw, - txn_id: event.transaction_id().map(ToOwned::to_owned), - position, - }; + let event_meta = TimelineEventMetadata { sender, is_own_event, relations, encryption_info }; + let flow = Flow::Remote { event_id, origin_server_ts, raw_event: raw, txn_id, position }; TimelineEventHandler::new(event_meta, flow, timeline_items, timeline_meta) - .handle_event(event.into()) + .handle_event(event_kind) } fn update_fully_read_item( @@ -320,12 +327,52 @@ struct TimelineEventMetadata { #[derive(Clone)] enum TimelineEventKind { - Message { content: AnyMessageLikeEventContent }, + Message { + content: AnyMessageLikeEventContent, + }, RedactedMessage, - Redaction { redacts: OwnedEventId, content: RoomRedactionEventContent }, + Redaction { + redacts: OwnedEventId, + content: RoomRedactionEventContent, + }, // FIXME: Split further for state keys of different type - State { _content: AnyStateEventContent }, + State { + _content: AnyStateEventContent, + }, RedactedState, // AnyRedactedStateEventContent + FailedToParseMessageLike { + event_type: MessageLikeEventType, + error: Arc, + }, + FailedToParseState { + event_type: StateEventType, + state_key: String, + error: Arc, + }, +} + +impl TimelineEventKind { + fn failed_to_parse(event: SyncTimelineEventWithoutContent, error: serde_json::Error) -> Self { + let error = Arc::new(error); + match event { + SyncTimelineEventWithoutContent::OriginalMessageLike(ev) => { + Self::FailedToParseMessageLike { event_type: ev.content.event_type, error } + } + SyncTimelineEventWithoutContent::RedactedMessageLike(ev) => { + Self::FailedToParseMessageLike { event_type: ev.content.event_type, error } + } + SyncTimelineEventWithoutContent::OriginalState(ev) => Self::FailedToParseState { + event_type: ev.content.event_type, + state_key: ev.state_key, + error, + }, + SyncTimelineEventWithoutContent::RedactedState(ev) => Self::FailedToParseState { + event_type: ev.content.event_type, + state_key: ev.state_key, + error, + }, + } + } } impl From for TimelineEventKind { @@ -404,8 +451,15 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { TimelineEventKind::Redaction { redacts, content } => { self.handle_redaction(redacts, content) } - // TODO: State events - _ => {} + TimelineEventKind::State { .. } | TimelineEventKind::RedactedState => { + // TODO + } + TimelineEventKind::FailedToParseMessageLike { event_type, error } => { + self.add(NewEventTimelineItem::failed_to_parse_message_like(event_type, error)); + } + TimelineEventKind::FailedToParseState { event_type, state_key, error } => { + self.add(NewEventTimelineItem::failed_to_parse_state(event_type, state_key, error)); + } } if !self.event_added { @@ -449,6 +503,14 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { ); return None; } + TimelineItemContent::FailedToParseMessageLike { .. } + | TimelineItemContent::FailedToParseState { .. } => { + info!( + %event_id, + "Edit event applies to event that couldn't be parsed, discarding" + ); + return None; + } }; let content = TimelineItemContent::Message(Message { @@ -705,6 +767,21 @@ impl NewEventTimelineItem { Self::from_content(TimelineItemContent::RedactedMessage) } + fn failed_to_parse_message_like( + event_type: MessageLikeEventType, + error: Arc, + ) -> NewEventTimelineItem { + Self::from_content(TimelineItemContent::FailedToParseMessageLike { event_type, error }) + } + + fn failed_to_parse_state( + event_type: StateEventType, + state_key: String, + error: Arc, + ) -> NewEventTimelineItem { + Self::from_content(TimelineItemContent::FailedToParseState { event_type, state_key, error }) + } + fn from_content(content: TimelineItemContent) -> Self { Self { content, reactions: BundledReactions::default() } } diff --git a/crates/matrix-sdk/src/room/timeline/event_item.rs b/crates/matrix-sdk/src/room/timeline/event_item.rs index 8bacadc36..a9fcc7c3b 100644 --- a/crates/matrix-sdk/src/room/timeline/event_item.rs +++ b/crates/matrix-sdk/src/room/timeline/event_item.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::fmt; +use std::{fmt, sync::Arc}; use indexmap::IndexMap; use matrix_sdk_base::deserialized_responses::EncryptionInfo; @@ -23,7 +23,7 @@ use ruma::{ encrypted::{EncryptedEventScheme, MegolmV1AesSha2Content, RoomEncryptedEventContent}, message::MessageType, }, - AnySyncTimelineEvent, + AnySyncTimelineEvent, MessageLikeEventType, StateEventType, }, serde::Raw, uint, EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedTransactionId, @@ -266,6 +266,27 @@ pub enum TimelineItemContent { /// An `m.room.encrypted` event that could not be decrypted. UnableToDecrypt(EncryptedMessage), + + /// A message-like event that failed to deserialize. + FailedToParseMessageLike { + /// The event `type`. + event_type: MessageLikeEventType, + + /// The deserialization error. + error: Arc, + }, + + /// A state event that failed to deserialize. + FailedToParseState { + /// The event `type`. + event_type: StateEventType, + + /// The state key. + state_key: String, + + /// The deserialization error. + error: Arc, + }, } impl TimelineItemContent { diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index f64c091f5..cadfa9d00 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -27,7 +27,7 @@ use matrix_sdk_base::crypto::OlmMachine; use matrix_sdk_test::async_test; use once_cell::sync::Lazy; use ruma::{ - assign, + assign, event_id, events::{ reaction::{self, ReactionEventContent}, room::{ @@ -37,16 +37,18 @@ use ruma::{ message::{self, MessageType, Replacement, RoomMessageEventContent}, redaction::OriginalSyncRoomRedactionEvent, }, - MessageLikeEventContent, OriginalSyncMessageLikeEvent, + MessageLikeEventContent, MessageLikeEventType, OriginalSyncMessageLikeEvent, + StateEventType, }, room_id, serde::Raw, - server_name, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId, + server_name, uint, user_id, EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId, }; use serde_json::{json, Value as JsonValue}; use super::{ - EncryptedMessage, TimelineInner, TimelineItem, TimelineItemContent, VirtualTimelineItem, + EncryptedMessage, TimelineInner, TimelineItem, TimelineItemContent, TimelineKey, + VirtualTimelineItem, }; static ALICE: Lazy<&UserId> = Lazy::new(|| user_id!("@alice:server.name")); @@ -258,6 +260,90 @@ async fn update_read_marker() { assert_matches!(stream.next().await, Some(VecDiff::Move { old_index: 1, new_index: 2 })); } +#[async_test] +async fn invalid_event_content() { + let timeline = TestTimeline::new(&ALICE); + let mut stream = timeline.stream(); + + // m.room.message events must have a msgtype and body in content, so this + // event with an empty content object should fail to deserialize. + timeline + .handle_live_custom_event(json!({ + "content": {}, + "event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I", + "origin_server_ts": 10, + "sender": "@alice:example.org", + "type": "m.room.message", + })) + .await; + + let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); + let event_item = item.as_event().unwrap(); + assert_eq!(event_item.sender(), "@alice:example.org"); + assert_eq!( + *event_item.key(), + TimelineKey::EventId(event_id!("$eeG0HA0FAZ37wP8kXlNkxx3I").to_owned()) + ); + assert_eq!(event_item.origin_server_ts(), Some(MilliSecondsSinceUnixEpoch(uint!(10)))); + let event_type = assert_matches!( + event_item.content(), + TimelineItemContent::FailedToParseMessageLike { event_type, .. } => event_type + ); + assert_eq!(*event_type, MessageLikeEventType::RoomMessage); + + // Similar to above, the m.room.member state event must also not have an + // empty content object. + timeline + .handle_live_custom_event(json!({ + "content": {}, + "event_id": "$d5G0HA0FAZ37wP8kXlNkxx3I", + "origin_server_ts": 2179, + "sender": "@alice:example.org", + "type": "m.room.member", + "state_key": "@alice:example.org", + })) + .await; + + let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); + let event_item = item.as_event().unwrap(); + assert_eq!(event_item.sender(), "@alice:example.org"); + assert_eq!( + *event_item.key(), + TimelineKey::EventId(event_id!("$d5G0HA0FAZ37wP8kXlNkxx3I").to_owned()) + ); + assert_eq!(event_item.origin_server_ts(), Some(MilliSecondsSinceUnixEpoch(uint!(2179)))); + let (event_type, state_key) = assert_matches!( + event_item.content(), + TimelineItemContent::FailedToParseState { + event_type, + state_key, + .. + } => (event_type, state_key) + ); + assert_eq!(*event_type, StateEventType::RoomMember); + assert_eq!(*state_key, "@alice:example.org"); +} + +#[async_test] +async fn invalid_event() { + let timeline = TestTimeline::new(&ALICE); + + // This event is missing the sender field which the homeserver must add to + // all timeline events. Because the event is malformed, it will be ignored. + timeline + .handle_live_custom_event(json!({ + "content": { + "body": "hello world", + "msgtype": "m.text" + }, + "event_id": "$eeG0HA0FAZ37wP8kXlNkxx3I", + "origin_server_ts": 10, + "type": "m.room.message", + })) + .await; + assert_eq!(timeline.inner.items.lock_ref().len(), 0); +} + struct TestTimeline { own_user_id: OwnedUserId, inner: TimelineInner, From 5dc6416a7654b1fd96a08bbdb1cd09d590062364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 24 Nov 2022 15:19:46 +0100 Subject: [PATCH 74/78] fix(sdk): Fix logic if read marker event was not in the timeline --- crates/matrix-sdk/src/room/timeline/event_handler.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index 0c169e7a8..eff3a94f6 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -277,6 +277,7 @@ fn update_fully_read_item( *fully_read_event_in_timeline = false; } (Some(from), Some(to)) => { + *fully_read_event_in_timeline = true; items_lock.move_from_to(from, to); } } From 1f761f3c13d29eb3a16ad0365acd1ba8198baadd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 24 Nov 2022 15:34:30 +0100 Subject: [PATCH 75/78] test(sdk): Add read marker case when fully read event wasn't found --- crates/matrix-sdk/src/room/timeline/tests.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index cadfa9d00..210eeabfd 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -258,6 +258,16 @@ async fn update_read_marker() { timeline.inner.set_fully_read_event(event_id).await; assert_matches!(stream.next().await, Some(VecDiff::Move { old_index: 1, new_index: 2 })); + + // Nothing should happen if the fully read event isn't found. + timeline.inner.set_fully_read_event(event_id!("$fake_event_id").to_owned()).await; + + timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("C")).await; + let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); + let event_id = item.as_event().unwrap().event_id().unwrap().to_owned(); + + timeline.inner.set_fully_read_event(event_id).await; + assert_matches!(stream.next().await, Some(VecDiff::Move { old_index: 2, new_index: 3 })); } #[async_test] From 580861cd1c55abb34636bb3172796c6731664310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 24 Nov 2022 19:29:13 +0100 Subject: [PATCH 76/78] fix(sdk): Rename read marker functions and variables for clarity --- .../src/room/timeline/event_handler.rs | 16 ++++++++-------- crates/matrix-sdk/src/room/timeline/mod.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index eff3a94f6..f10f34f22 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -45,7 +45,7 @@ use tracing::{debug, error, info, warn}; use super::{ event_item::{BundledReactions, TimelineDetails}, - find_event, find_fully_read, EventTimelineItem, Message, TimelineInner, TimelineInnerMetadata, + find_event, find_read_marker, EventTimelineItem, Message, TimelineInner, TimelineInnerMetadata, TimelineItem, TimelineItemContent, TimelineKey, VirtualTimelineItem, }; use crate::events::SyncTimelineEventWithoutContent; @@ -131,7 +131,7 @@ impl TimelineInner { let mut items_lock = self.items.lock_mut(); let metadata = &mut *metadata_lock; - update_fully_read_item( + update_read_marker( &mut items_lock, metadata.fully_read_event.as_deref(), &mut metadata.fully_read_event_in_timeline, @@ -256,15 +256,15 @@ fn handle_remote_event( .handle_event(event_kind) } -fn update_fully_read_item( +fn update_read_marker( items_lock: &mut MutableVecLockMut<'_, Arc>, fully_read_event: Option<&EventId>, fully_read_event_in_timeline: &mut bool, ) { let Some(fully_read_event) = fully_read_event else { return }; - let old_idx = find_fully_read(items_lock); - let new_idx = find_event(items_lock, fully_read_event).map(|(idx, _)| idx); - match (old_idx, new_idx) { + let read_marker_idx = find_read_marker(items_lock); + let fully_read_event_idx = find_event(items_lock, fully_read_event).map(|(idx, _)| idx); + match (read_marker_idx, fully_read_event_idx) { (None, None) => {} (None, Some(idx)) => { *fully_read_event_in_timeline = true; @@ -695,9 +695,9 @@ impl<'a, 'i> TimelineEventHandler<'a, 'i> { } } - // See if we got the event corresponding to the fully read marker now. + // See if we got the event corresponding to the read marker now. if !*self.fully_read_event_in_timeline { - update_fully_read_item( + update_read_marker( self.timeline_items, self.fully_read_event.as_deref(), self.fully_read_event_in_timeline, diff --git a/crates/matrix-sdk/src/room/timeline/mod.rs b/crates/matrix-sdk/src/room/timeline/mod.rs index b0b2dc8fa..de9337130 100644 --- a/crates/matrix-sdk/src/room/timeline/mod.rs +++ b/crates/matrix-sdk/src/room/timeline/mod.rs @@ -386,7 +386,7 @@ fn find_event( .rfind(|(_, it)| key == it.key) } -fn find_fully_read(lock: &[Arc]) -> Option { +fn find_read_marker(lock: &[Arc]) -> Option { lock.iter() .enumerate() .rfind(|(_, item)| { From 183d4595aa6e71006fa376695786060ab1dbff5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Thu, 24 Nov 2022 19:29:39 +0100 Subject: [PATCH 77/78] fix(sdk): Make sure read marker can't go backwards --- crates/matrix-sdk/src/room/timeline/event_handler.rs | 6 +++++- crates/matrix-sdk/src/room/timeline/tests.rs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/matrix-sdk/src/room/timeline/event_handler.rs b/crates/matrix-sdk/src/room/timeline/event_handler.rs index f10f34f22..a42257100 100644 --- a/crates/matrix-sdk/src/room/timeline/event_handler.rs +++ b/crates/matrix-sdk/src/room/timeline/event_handler.rs @@ -278,7 +278,11 @@ fn update_read_marker( } (Some(from), Some(to)) => { *fully_read_event_in_timeline = true; - items_lock.move_from_to(from, to); + + // The read marker can't move backwards. + if from < to { + items_lock.move_from_to(from, to); + } } } } diff --git a/crates/matrix-sdk/src/room/timeline/tests.rs b/crates/matrix-sdk/src/room/timeline/tests.rs index 210eeabfd..6405bac4c 100644 --- a/crates/matrix-sdk/src/room/timeline/tests.rs +++ b/crates/matrix-sdk/src/room/timeline/tests.rs @@ -256,12 +256,16 @@ async fn update_read_marker() { let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); let event_id = item.as_event().unwrap().event_id().unwrap().to_owned(); - timeline.inner.set_fully_read_event(event_id).await; + timeline.inner.set_fully_read_event(event_id.clone()).await; assert_matches!(stream.next().await, Some(VecDiff::Move { old_index: 1, new_index: 2 })); // Nothing should happen if the fully read event isn't found. timeline.inner.set_fully_read_event(event_id!("$fake_event_id").to_owned()).await; + // Nothing should happen if the fully read event is set back to the same event + // as before. + timeline.inner.set_fully_read_event(event_id).await; + timeline.handle_live_message_event(&ALICE, RoomMessageEventContent::text_plain("C")).await; let item = assert_matches!(stream.next().await, Some(VecDiff::Push { value }) => value); let event_id = item.as_event().unwrap().event_id().unwrap().to_owned(); From c0910a369303ef35e6336bc2ff0588079af9741a Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 24 Nov 2022 21:31:19 +0100 Subject: [PATCH 78/78] chore: Fix a typo, upgrade typo check action --- .github/workflows/ci.yml | 2 +- crates/matrix-sdk/src/sliding_sync.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c98b8c0d..6238ccaf4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -370,7 +370,7 @@ jobs: uses: actions/checkout@v3 - name: Check the spelling of the files in our repo - uses: crate-ci/typos@v1.12.8 + uses: crate-ci/typos@v1.13.0 clippy: name: Run clippy diff --git a/crates/matrix-sdk/src/sliding_sync.rs b/crates/matrix-sdk/src/sliding_sync.rs index 85531960f..73622786e 100644 --- a/crates/matrix-sdk/src/sliding_sync.rs +++ b/crates/matrix-sdk/src/sliding_sync.rs @@ -247,7 +247,7 @@ pub struct UpdateSummary { #[derive(Clone, Debug, Builder)] #[builder(pattern = "owned", derive(Clone, Debug))] pub struct SlidingSync { - /// Customize the homeserver for sliding sync onlye + /// Customize the homeserver for sliding sync only #[builder(setter(strip_option))] homeserver: Option,