Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 55 additions & 19 deletions crates/matrix-sdk-base/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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<Self> {
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(),
Expand Down
4 changes: 4 additions & 0 deletions crates/matrix-sdk-base/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
16 changes: 16 additions & 0 deletions crates/matrix-sdk-base/src/event_cache/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynEventCacheStore> {
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`].
Expand Down
16 changes: 16 additions & 0 deletions crates/matrix-sdk-base/src/media/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynMediaStore> {
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`].
Expand Down
5 changes: 4 additions & 1 deletion crates/matrix-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
Loading