diff --git a/crates/matrix-sdk-base/src/client.rs b/crates/matrix-sdk-base/src/client.rs index aace8d0f2f2..cf334986816 100644 --- a/crates/matrix-sdk-base/src/client.rs +++ b/crates/matrix-sdk-base/src/client.rs @@ -64,8 +64,8 @@ use crate::{ Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState, }, store::{ - BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult, RoomLoadSettings, - StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt, StoreConfig, + BaseStateStore, DynStateStore, Result as StoreResult, RoomLoadSettings, StateChanges, + StateStoreDataKey, StateStoreDataValue, StateStoreExt, StoreConfig, ambiguity_map::AmbiguityCache, }, sync::{RoomUpdates, SyncResponse}, @@ -213,30 +213,66 @@ impl BaseClient { } } - /// Clones the current base client to use the same crypto store but a - /// different, in-memory store config, and resets transient state. + /// Derives the current states but for a notification client. + /// + /// The stores will be as follow: + /// + /// - state store will be in-memory only, + /// - event cache store will be cloned (it's behind a cross-process lock), + /// - media store will be cloned (it's behind a cross-process lock), + /// - crypto store will be cloned (custom mechanism for the moment). + /// + /// The transient state will be reset. #[cfg(feature = "e2e-encryption")] - pub async fn clone_with_in_memory_state_store( + pub async fn derive_states_for_notification_client( &self, cross_process_store_locks_holder_name: &str, handle_verification_events: bool, ) -> Result { - let config = StoreConfig::new(cross_process_store_locks_holder_name.to_owned()) - .state_store(MemoryStore::new()); - let config = config.crypto_store(self.crypto_store.clone()); + let StoreConfig { state_store, event_cache_store, media_store, .. } = + StoreConfig::new(cross_process_store_locks_holder_name.to_owned()) + .event_cache_store({ + if cross_process_store_locks_holder_name == self.event_cache_store.lock_holder() + { + return Err(Error::DuplicatedCrossProcessLockHolder( + cross_process_store_locks_holder_name.to_owned(), + )); + } + + // SAFETY: the store will be used behind another lock holder, so it's safe to + // use a clone of it. + unsafe { self.event_cache_store.clone_store() } + }) + .media_store({ + if cross_process_store_locks_holder_name == self.media_store.lock_holder() { + return Err(Error::DuplicatedCrossProcessLockHolder( + cross_process_store_locks_holder_name.to_owned(), + )); + } + + // SAFETY: the store will be used behind another lock holder, so it's safe to + // use a clone of it. + unsafe { self.media_store.clone_store() } + }); + + // Clone the Crypto store. + // We copy the crypto store as well as the `OlmMachine` for two reasons: + // 1. The `self.crypto_store` is the same as the one used inside the + // `OlmMachine`. + // 2. We need to ensure that the parent and child use the same data and caches + // inside the `OlmMachine` so the various ratchets and places where new + // randomness gets introduced don't diverge, i.e. one-time keys that get + // generated by the Olm Account or Olm sessions when they encrypt or decrypt + // messages. + let crypto_store = self.crypto_store.clone(); + let olm_machine = self.olm_machine.clone(); let copy = Self { - state_store: BaseStateStore::new(config.state_store), - event_cache_store: config.event_cache_store, - media_store: config.media_store, - // We copy the crypto store as well as the `OlmMachine` for two reasons: - // 1. The `self.crypto_store` is the same as the one used inside the `OlmMachine`. - // 2. We need to ensure that the parent and child use the same data and caches inside - // the `OlmMachine` so the various ratchets and places where new randomness gets - // introduced don't diverge, i.e. one-time keys that get generated by the Olm Account - // or Olm sessions when they encrypt or decrypt messages. - crypto_store: self.crypto_store.clone(), - olm_machine: self.olm_machine.clone(), + state_store: BaseStateStore::new(state_store), + event_cache_store, + media_store, + crypto_store, + olm_machine, ignore_user_list_changes: Default::default(), room_info_notable_update_sender: self.room_info_notable_update_sender.clone(), room_key_recipient_strategy: self.room_key_recipient_strategy.clone(), diff --git a/crates/matrix-sdk-base/src/error.rs b/crates/matrix-sdk-base/src/error.rs index 86e0586e498..8151d022c61 100644 --- a/crates/matrix-sdk-base/src/error.rs +++ b/crates/matrix-sdk-base/src/error.rs @@ -58,6 +58,10 @@ pub enum Error { #[error(transparent)] CryptoStore(#[from] CryptoStoreError), + /// The given cross-process lock holder name is already used by a store. + #[error("The given cross-process lock holder name is already used by a store: `{0}`")] + DuplicatedCrossProcessLockHolder(String), + /// An error occurred during a E2EE operation. #[cfg(feature = "e2e-encryption")] #[error(transparent)] diff --git a/crates/matrix-sdk-base/src/event_cache/store/mod.rs b/crates/matrix-sdk-base/src/event_cache/store/mod.rs index d6d9e932fcd..03c57a7d76d 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/mod.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/mod.rs @@ -91,6 +91,22 @@ impl EventCacheStoreLock { Ok(lock_state) } + + /// Clone the inner store, by-passing the lock. + /// + /// # Safety + /// + /// This method is useful when you want to build a new client with another + /// lock holder name for example. But the lock is fully by-passed in this + /// method. Be extremely careful! + pub(crate) unsafe fn clone_store(&self) -> Arc { + self.store.clone() + } + + /// Get the lock holder name. + pub(crate) fn lock_holder(&self) -> &str { + self.cross_process_lock.lock_holder() + } } /// The equivalent of [`CrossProcessLockState`] but for the [`EventCacheStore`]. diff --git a/crates/matrix-sdk-base/src/media/store/mod.rs b/crates/matrix-sdk-base/src/media/store/mod.rs index b7f6b4ba02b..13007b3072c 100644 --- a/crates/matrix-sdk-base/src/media/store/mod.rs +++ b/crates/matrix-sdk-base/src/media/store/mod.rs @@ -150,6 +150,22 @@ impl MediaStoreLock { Ok(MediaStoreLockGuard { cross_process_lock_guard, store: self.store.deref() }) } + + /// Clone the inner store, by-passing the lock. + /// + /// # Safety + /// + /// This method is useful when you want to build a new client with another + /// lock holder name for example. But the lock is fully by-passed in this + /// method. Be extremely careful! + pub(crate) unsafe fn clone_store(&self) -> Arc { + self.store.clone() + } + + /// Get the lock holder name. + pub(crate) fn lock_holder(&self) -> &str { + self.cross_process_lock.lock_holder() + } } /// An RAII implementation of a “scoped lock” of an [`MediaStoreLock`]. diff --git a/crates/matrix-sdk/src/client/mod.rs b/crates/matrix-sdk/src/client/mod.rs index f48102a55e0..d40c615b849 100644 --- a/crates/matrix-sdk/src/client/mod.rs +++ b/crates/matrix-sdk/src/client/mod.rs @@ -3054,7 +3054,10 @@ impl Client { self.inner.http_client.clone(), self.inner .base_client - .clone_with_in_memory_state_store(&cross_process_store_locks_holder_name, false) + .derive_states_for_notification_client( + &cross_process_store_locks_holder_name, + false, + ) .await?, self.inner.caches.supported_versions.read().await.clone(), self.inner.caches.well_known.read().await.clone(),