Skip to main content

matrix_sdk_crypto/store/
mod.rs

1// Copyright 2020, 2026 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types and traits to implement the storage layer for the [`OlmMachine`]
16//!
17//! The storage layer for the [`OlmMachine`] can be customized using a trait.
18//! Implementing your own [`CryptoStore`]
19//!
20//! An in-memory only store is provided as well as an SQLite-based one,
21//! depending on your needs and targets a custom store may be implemented, e.g.
22//! for `wasm-unknown-unknown` an indexeddb store would be needed
23//!
24//! ```
25//! # use std::sync::Arc;
26//! # use matrix_sdk_crypto::{
27//! #     OlmMachine,
28//! #     OlmMachineBuilder,
29//! #     store::MemoryStore,
30//! # };
31//! # use ruma::{device_id, user_id};
32//! # let user_id = user_id!("@example:localhost");
33//! # let device_id = device_id!("TEST");
34//! let store = Arc::new(MemoryStore::new());
35//!
36//! let machine = OlmMachineBuilder::new(user_id, device_id)
37//!     .with_crypto_store(store)
38//!     .build();
39//! ```
40//!
41//! [`OlmMachine`]: /matrix_sdk_crypto/struct.OlmMachine.html
42//! [`CryptoStore`]: trait.Cryptostore.html
43
44use std::{
45    collections::{BTreeMap, BTreeSet, HashMap, HashSet},
46    fmt::Debug,
47    ops::Deref,
48    pin::pin,
49    sync::{Arc, atomic::Ordering},
50    time::Duration,
51};
52
53use as_variant::as_variant;
54use futures_core::Stream;
55use futures_util::StreamExt;
56use itertools::{Either, Itertools};
57use ruma::{
58    DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, RoomId, UserId,
59    encryption::KeyUsage, events::secret::request::SecretName,
60};
61use serde::{Serialize, de::DeserializeOwned};
62use thiserror::Error;
63use tokio::sync::{Mutex, Notify, OwnedRwLockWriteGuard, RwLock};
64use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
65use tracing::{info, instrument, trace, warn};
66use types::{RoomKeyBundleInfo, StoredRoomKeyBundleData};
67use vodozemac::{Curve25519PublicKey, megolm::SessionOrdering};
68
69use self::types::{
70    Changes, CrossSigningKeyExport, DeviceChanges, DeviceUpdates, IdentityChanges, IdentityUpdates,
71    PendingChanges, RoomKeyInfo, RoomKeyWithheldInfo, RoomPendingKeyBundleDetails,
72    UserKeyQueryResult,
73};
74use crate::{
75    CrossSigningStatus, OwnUserIdentityData, RoomKeyImportResult,
76    gossiping::GossippedSecret,
77    identities::{Device, DeviceData, UserDevices, UserIdentityData, user::UserIdentity},
78    olm::{
79        Account, ExportedRoomKey, ForwarderData, InboundGroupSession, PrivateCrossSigningIdentity,
80        SenderData, Session, StaticAccountData,
81    },
82    store::types::{RoomKeyWithheldEntry, SecretsInboxItem},
83    types::{
84        BackupSecrets, CrossSigningSecrets, MegolmBackupV1Curve25519AesSha2Secrets, RoomKeyExport,
85        SecretsBundle,
86    },
87    verification::VerificationMachine,
88};
89#[cfg(doc)]
90use crate::{backups::BackupMachine, identities::OwnUserIdentity};
91
92pub mod caches;
93mod crypto_store_wrapper;
94mod error;
95mod memorystore;
96mod traits;
97pub mod types;
98
99#[cfg(any(test, feature = "testing"))]
100#[macro_use]
101#[allow(missing_docs)]
102pub mod integration_tests;
103
104pub(crate) use crypto_store_wrapper::CryptoStoreWrapper;
105pub use error::{CryptoStoreError, Result};
106use matrix_sdk_common::{
107    cross_process_lock::{CrossProcessLock, CrossProcessLockConfig, CrossProcessLockGeneration},
108    deserialized_responses::WithheldCode,
109    timeout::timeout,
110};
111pub use memorystore::MemoryStore;
112pub use traits::{CryptoStore, DynCryptoStore, IntoCryptoStore};
113
114use self::caches::{SequenceNumber, StoreCache, StoreCacheGuard, UsersForKeyQuery};
115use crate::types::{
116    events::room_key_withheld::RoomKeyWithheldContent, room_history::RoomKeyBundle,
117};
118#[cfg(feature = "experimental-x509-identity-verification")]
119use crate::x509::{X509Signer, X509Verifier};
120pub use crate::{
121    dehydrated_devices::DehydrationError,
122    gossiping::{GossipRequest, SecretInfo},
123};
124
125/// A wrapper for our CryptoStore trait object.
126///
127/// This is needed because we want to have a generic interface so we can
128/// store/restore objects that we can serialize. Since trait objects and
129/// generics don't mix let the CryptoStore store strings and this wrapper
130/// adds the generic interface on top.
131#[derive(Debug, Clone)]
132pub struct Store {
133    inner: Arc<StoreInner>,
134}
135
136#[derive(Debug, Default)]
137pub(crate) struct KeyQueryManager {
138    /// Record of the users that are waiting for a /keys/query.
139    users_for_key_query: Mutex<UsersForKeyQuery>,
140
141    /// Notifier that is triggered each time an update is received for a user.
142    users_for_key_query_notify: Notify,
143}
144
145impl KeyQueryManager {
146    pub async fn synced<'a>(&'a self, cache: &'a StoreCache) -> Result<SyncedKeyQueryManager<'a>> {
147        self.ensure_sync_tracked_users(cache).await?;
148        Ok(SyncedKeyQueryManager { cache, manager: self })
149    }
150
151    /// Load the list of users for whom we are tracking their device lists and
152    /// fill out our caches.
153    ///
154    /// This method ensures that we're only going to load the users from the
155    /// actual [`CryptoStore`] once, it will also make sure that any
156    /// concurrent calls to this method get deduplicated.
157    async fn ensure_sync_tracked_users(&self, cache: &StoreCache) -> Result<()> {
158        // Check if the users are loaded, and in that case do nothing.
159        let loaded = cache.loaded_tracked_users.read().await;
160        if *loaded {
161            return Ok(());
162        }
163
164        // Otherwise, we may load the users.
165        drop(loaded);
166        let mut loaded = cache.loaded_tracked_users.write().await;
167
168        // Check again if the users have been loaded, in case another call to this
169        // method loaded the tracked users between the time we tried to
170        // acquire the lock and the time we actually acquired the lock.
171        if *loaded {
172            return Ok(());
173        }
174
175        let tracked_users = cache.store.load_tracked_users().await?;
176
177        let mut query_users_lock = self.users_for_key_query.lock().await;
178        let mut tracked_users_cache = cache.tracked_users.write();
179        for user in tracked_users {
180            tracked_users_cache.insert(user.user_id.to_owned());
181
182            if user.dirty {
183                query_users_lock.insert_user(&user.user_id);
184            }
185        }
186
187        *loaded = true;
188
189        Ok(())
190    }
191
192    /// Wait for a `/keys/query` response to be received if one is expected for
193    /// the given user.
194    ///
195    /// If the given timeout elapses, the method will stop waiting and return
196    /// [`UserKeyQueryResult::TimeoutExpired`].
197    ///
198    /// Requires a [`StoreCacheGuard`] to make sure the users for which a key
199    /// query is pending are up to date, but doesn't hold on to it
200    /// thereafter: the lock is short-lived in this case.
201    pub async fn wait_if_user_key_query_pending(
202        &self,
203        cache: StoreCacheGuard,
204        timeout_duration: Duration,
205        user: &UserId,
206    ) -> Result<UserKeyQueryResult> {
207        {
208            // Drop the cache early, so we don't keep it while waiting (since writing the
209            // results requires to write in the cache, thus take another lock).
210            self.ensure_sync_tracked_users(&cache).await?;
211            drop(cache);
212        }
213
214        let mut users_for_key_query = self.users_for_key_query.lock().await;
215        let Some(waiter) = users_for_key_query.maybe_register_waiting_task(user) else {
216            return Ok(UserKeyQueryResult::WasNotPending);
217        };
218
219        let wait_for_completion = async {
220            while !waiter.completed.load(Ordering::Relaxed) {
221                // Register for being notified before releasing the mutex, so
222                // it's impossible to miss a wakeup between the last check for
223                // whether we should wait, and starting to wait.
224                let mut notified = pin!(self.users_for_key_query_notify.notified());
225                notified.as_mut().enable();
226                drop(users_for_key_query);
227
228                // Wait for a notification
229                notified.await;
230
231                // Reclaim the lock before checking the flag to avoid races
232                // when two notifications happen right after each other and the
233                // second one sets the flag we want to wait for.
234                users_for_key_query = self.users_for_key_query.lock().await;
235            }
236        };
237
238        match timeout(Box::pin(wait_for_completion), timeout_duration).await {
239            Err(_) => {
240                warn!(
241                    user_id = ?user,
242                    "The user has a pending `/keys/query` request which did \
243                    not finish yet, some devices might be missing."
244                );
245
246                Ok(UserKeyQueryResult::TimeoutExpired)
247            }
248            _ => Ok(UserKeyQueryResult::WasPending),
249        }
250    }
251}
252
253pub(crate) struct SyncedKeyQueryManager<'a> {
254    cache: &'a StoreCache,
255    manager: &'a KeyQueryManager,
256}
257
258impl SyncedKeyQueryManager<'_> {
259    /// Add entries to the list of users being tracked for device changes
260    ///
261    /// Any users not already on the list are flagged as awaiting a key query.
262    /// Users that were already in the list are unaffected.
263    pub async fn update_tracked_users(&self, users: impl Iterator<Item = &UserId>) -> Result<()> {
264        let mut store_updates = Vec::new();
265        let mut key_query_lock = self.manager.users_for_key_query.lock().await;
266
267        {
268            let mut tracked_users = self.cache.tracked_users.write();
269            for user_id in users {
270                if tracked_users.insert(user_id.to_owned()) {
271                    key_query_lock.insert_user(user_id);
272                    store_updates.push((user_id, true))
273                }
274            }
275        }
276
277        self.cache.store.save_tracked_users(&store_updates).await
278    }
279
280    /// Process notifications that users have changed devices.
281    ///
282    /// This is used to handle the list of device-list updates that is received
283    /// from the `/sync` response. Any users *whose device lists we are
284    /// tracking* are flagged as needing a key query. Users whose devices we
285    /// are not tracking are ignored.
286    pub async fn mark_tracked_users_as_changed(
287        &self,
288        users: impl Iterator<Item = &UserId>,
289    ) -> Result<()> {
290        let mut store_updates: Vec<(&UserId, bool)> = Vec::new();
291        let mut key_query_lock = self.manager.users_for_key_query.lock().await;
292
293        {
294            let tracked_users = &self.cache.tracked_users.read();
295            for user_id in users {
296                if tracked_users.contains(user_id) {
297                    key_query_lock.insert_user(user_id);
298                    store_updates.push((user_id, true));
299                }
300            }
301        }
302
303        self.cache.store.save_tracked_users(&store_updates).await
304    }
305
306    /// Flag that the given users devices are now up-to-date.
307    ///
308    /// This is called after processing the response to a /keys/query request.
309    /// Any users whose device lists we are tracking are removed from the
310    /// list of those pending a /keys/query.
311    pub async fn mark_tracked_users_as_up_to_date(
312        &self,
313        users: impl Iterator<Item = &UserId>,
314        sequence_number: SequenceNumber,
315    ) -> Result<()> {
316        let mut store_updates: Vec<(&UserId, bool)> = Vec::new();
317        let mut key_query_lock = self.manager.users_for_key_query.lock().await;
318
319        {
320            let tracked_users = self.cache.tracked_users.read();
321            for user_id in users {
322                if tracked_users.contains(user_id) {
323                    let clean = key_query_lock.maybe_remove_user(user_id, sequence_number);
324                    store_updates.push((user_id, !clean));
325                }
326            }
327        }
328
329        self.cache.store.save_tracked_users(&store_updates).await?;
330        // wake up any tasks that may have been waiting for updates
331        self.manager.users_for_key_query_notify.notify_waiters();
332
333        Ok(())
334    }
335
336    /// Get the set of users that has the outdate/dirty flag set for their list
337    /// of devices.
338    ///
339    /// This set should be included in a `/keys/query` request which will update
340    /// the device list.
341    ///
342    /// # Returns
343    ///
344    /// A pair `(users, sequence_number)`, where `users` is the list of users to
345    /// be queried, and `sequence_number` is the current sequence number,
346    /// which should be returned in `mark_tracked_users_as_up_to_date`.
347    pub async fn users_for_key_query(&self) -> (HashSet<OwnedUserId>, SequenceNumber) {
348        self.manager.users_for_key_query.lock().await.users_for_key_query()
349    }
350
351    /// See the docs for [`crate::OlmMachine::tracked_users()`].
352    pub fn tracked_users(&self) -> HashSet<OwnedUserId> {
353        self.cache.tracked_users.read().iter().cloned().collect()
354    }
355
356    /// Mark the given user as being tracked for device lists, and mark that it
357    /// has an outdated device list.
358    ///
359    /// This means that the user will be considered for a `/keys/query` request
360    /// next time [`Store::users_for_key_query()`] is called.
361    pub async fn mark_user_as_changed(&self, user: &UserId) -> Result<()> {
362        self.manager.users_for_key_query.lock().await.insert_user(user);
363        self.cache.tracked_users.write().insert(user.to_owned());
364
365        self.cache.store.save_tracked_users(&[(user, true)]).await
366    }
367}
368
369/// Convert the devices and vectors contained in the [`DeviceChanges`] into
370/// a [`DeviceUpdates`] struct.
371///
372/// The [`DeviceChanges`] will contain vectors of [`DeviceData`]s which
373/// we want to convert to a [`Device`].
374fn collect_device_updates(
375    verification_machine: VerificationMachine,
376    own_identity: Option<OwnUserIdentityData>,
377    identities: IdentityChanges,
378    devices: DeviceChanges,
379) -> DeviceUpdates {
380    let mut new: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new();
381    let mut changed: BTreeMap<_, BTreeMap<_, _>> = BTreeMap::new();
382
383    let (new_identities, changed_identities, unchanged_identities) = identities.into_maps();
384
385    let map_device = |device: DeviceData| {
386        let device_owner_identity = new_identities
387            .get(device.user_id())
388            .or_else(|| changed_identities.get(device.user_id()))
389            .or_else(|| unchanged_identities.get(device.user_id()))
390            .cloned();
391
392        Device {
393            inner: device,
394            verification_machine: verification_machine.to_owned(),
395            own_identity: own_identity.to_owned(),
396            device_owner_identity,
397        }
398    };
399
400    for device in devices.new {
401        let device = map_device(device);
402
403        new.entry(device.user_id().to_owned())
404            .or_default()
405            .insert(device.device_id().to_owned(), device);
406    }
407
408    for device in devices.changed {
409        let device = map_device(device);
410
411        changed
412            .entry(device.user_id().to_owned())
413            .or_default()
414            .insert(device.device_id().to_owned(), device.to_owned());
415    }
416
417    DeviceUpdates { new, changed }
418}
419
420/// A temporary transaction (that implies a write) to the underlying store.
421#[allow(missing_debug_implementations)]
422pub struct StoreTransaction {
423    store: Store,
424    changes: PendingChanges,
425    // TODO hold onto the cross-process crypto store lock + cache.
426    cache: OwnedRwLockWriteGuard<StoreCache>,
427}
428
429impl StoreTransaction {
430    /// Starts a new `StoreTransaction`.
431    async fn new(store: Store) -> Self {
432        let cache = store.inner.cache.clone();
433
434        Self { store, changes: PendingChanges::default(), cache: cache.clone().write_owned().await }
435    }
436
437    pub(crate) fn cache(&self) -> &StoreCache {
438        &self.cache
439    }
440
441    /// Returns a reference to the current `Store`.
442    pub fn store(&self) -> &Store {
443        &self.store
444    }
445
446    /// Gets a `Account` for update.
447    ///
448    /// Note: since it's guaranteed that one can't have both a
449    /// `StoreTransaction` and a `StoreCacheGuard` at runtime (since the
450    /// underlying `StoreCache` is guarded by a `RwLock` mutex), this ensures
451    /// that we can't have two copies of an `Account` alive at the same time.
452    pub async fn account(&mut self) -> Result<&mut Account> {
453        if self.changes.account.is_none() {
454            // Make sure the cache loaded the account.
455            let _ = self.cache.account().await?;
456            self.changes.account = self.cache.account.lock().await.take();
457        }
458        Ok(self.changes.account.as_mut().unwrap())
459    }
460
461    /// Commits all dirty fields to the store, and maintains the cache so it
462    /// reflects the current state of the database.
463    pub async fn commit(self) -> Result<()> {
464        if self.changes.is_empty() {
465            return Ok(());
466        }
467
468        // Save changes in the database.
469        let account = self.changes.account.as_ref().map(|acc| acc.deep_clone());
470
471        self.store.save_pending_changes(self.changes).await?;
472
473        // Make the cache coherent with the database.
474        if let Some(account) = account {
475            *self.cache.account.lock().await = Some(account);
476        }
477
478        Ok(())
479    }
480}
481
482#[derive(Debug)]
483struct StoreInner {
484    identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
485    store: Arc<CryptoStoreWrapper>,
486
487    /// In-memory cache for the current crypto store.
488    ///
489    /// ⚠ Must remain private.
490    cache: Arc<RwLock<StoreCache>>,
491
492    verification_machine: VerificationMachine,
493
494    /// If we can verify peoples' identities via X.509 certificates, a handle to
495    /// the implementation.
496    #[cfg(feature = "experimental-x509-identity-verification")]
497    x509_verifier: Option<X509Verifier>,
498
499    /// If we can sign our own identity via X.509 certificates, a handle to the
500    /// implementation.
501    #[cfg(feature = "experimental-x509-identity-verification")]
502    x509_signer: Option<X509Signer>,
503
504    /// Static account data that never changes (and thus can be loaded once and
505    /// for all when creating the store).
506    static_account: StaticAccountData,
507}
508
509/// Error describing what went wrong when importing private cross signing keys
510/// or the key backup key.
511#[derive(Debug, Error)]
512pub enum SecretImportError {
513    /// The key that we tried to import was invalid.
514    #[error("Error while importing {name}: {error}")]
515    Key {
516        /// The name of the secret that was being imported.
517        name: SecretName,
518        /// The key error that occurred.
519        error: vodozemac::KeyError,
520    },
521    /// The public key of the imported private key doesn't match the public
522    /// key that was uploaded to the server.
523    #[error(
524        "Error while importing {name}: The public key of the imported private \
525            key doesn't match the public key that was uploaded to the server"
526    )]
527    MismatchedPublicKeys {
528        /// The name of the secret that was being imported.
529        name: SecretName,
530    },
531    /// The new version of the identity couldn't be stored.
532    #[error(transparent)]
533    Store(#[from] CryptoStoreError),
534}
535
536/// Error describing what went wrong when exporting a [`SecretsBundle`].
537///
538/// The [`SecretsBundle`] can only be exported if we have all cross-signing
539/// private keys in the store.
540#[derive(Debug, Error)]
541pub enum SecretsBundleExportError {
542    /// The store itself had an error.
543    #[error(transparent)]
544    Store(#[from] CryptoStoreError),
545    /// We're missing one or multiple cross-signing keys.
546    #[error("The store is missing one or multiple cross-signing keys")]
547    MissingCrossSigningKey(KeyUsage),
548    /// We're missing all cross-signing keys.
549    #[error("The store doesn't contain any cross-signing keys")]
550    MissingCrossSigningKeys,
551    /// We have a backup key stored, but we don't know the version of the
552    /// backup.
553    #[error("The store contains a backup key, but no backup version")]
554    MissingBackupVersion,
555}
556
557impl Store {
558    /// Create a new Store. A thin wrapper for [`Store::new_with_x509`].
559    pub(crate) fn new(
560        account: StaticAccountData,
561        identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
562        store: Arc<CryptoStoreWrapper>,
563        verification_machine: VerificationMachine,
564    ) -> Self {
565        Self::new_with_x509(
566            account,
567            identity,
568            store,
569            verification_machine,
570            #[cfg(feature = "experimental-x509-identity-verification")]
571            None,
572            #[cfg(feature = "experimental-x509-identity-verification")]
573            None,
574        )
575    }
576
577    /// Create a new Store, allowing specification of X.509 parameters.
578    pub(crate) fn new_with_x509(
579        account: StaticAccountData,
580        identity: Arc<Mutex<PrivateCrossSigningIdentity>>,
581        store: Arc<CryptoStoreWrapper>,
582        verification_machine: VerificationMachine,
583        #[cfg(feature = "experimental-x509-identity-verification")] x509_verifier: Option<
584            X509Verifier,
585        >,
586        #[cfg(feature = "experimental-x509-identity-verification")] x509_signer: Option<X509Signer>,
587    ) -> Self {
588        Self {
589            inner: Arc::new(StoreInner {
590                static_account: account,
591                identity,
592                store: store.clone(),
593                verification_machine,
594                #[cfg(feature = "experimental-x509-identity-verification")]
595                x509_verifier,
596                #[cfg(feature = "experimental-x509-identity-verification")]
597                x509_signer,
598                cache: Arc::new(RwLock::new(StoreCache {
599                    store,
600                    tracked_users: Default::default(),
601                    loaded_tracked_users: Default::default(),
602                    account: Default::default(),
603                })),
604            }),
605        }
606    }
607
608    /// UserId associated with this store
609    pub(crate) fn user_id(&self) -> &UserId {
610        &self.inner.static_account.user_id
611    }
612
613    /// DeviceId associated with this store
614    pub(crate) fn device_id(&self) -> &DeviceId {
615        self.inner.verification_machine.own_device_id()
616    }
617
618    /// The static data for the account associated with this store.
619    pub(crate) fn static_account(&self) -> &StaticAccountData {
620        &self.inner.static_account
621    }
622
623    /// If we can sign our own identity via X.509 certificates, a handle to the
624    /// implementation.
625    #[cfg(feature = "experimental-x509-identity-verification")]
626    pub(crate) fn x509_signer(&self) -> Option<&X509Signer> {
627        self.inner.x509_signer.as_ref()
628    }
629
630    /// If we can verify peoples' identities via X.509 certificates, a handle to
631    /// the implementation.
632    #[cfg(feature = "experimental-x509-identity-verification")]
633    pub(crate) fn x509_verifier(&self) -> Option<&X509Verifier> {
634        self.inner.x509_verifier.as_ref()
635    }
636
637    pub(crate) async fn cache(&self) -> Result<StoreCacheGuard> {
638        Ok(StoreCacheGuard { cache: self.inner.cache.clone().read_owned().await })
639    }
640
641    pub(crate) async fn transaction(&self) -> StoreTransaction {
642        StoreTransaction::new(self.clone()).await
643    }
644
645    pub(crate) async fn with_transaction<T>(
646        &self,
647        func: impl AsyncFnOnce(&mut StoreTransaction) -> Result<T, crate::OlmError>,
648    ) -> Result<T, crate::OlmError> {
649        let mut tr = self.transaction().await;
650        let res = func(&mut tr).await?;
651        tr.commit().await?;
652        Ok(res)
653    }
654
655    #[cfg(test)]
656    /// test helper to reset the cross signing identity
657    pub(crate) async fn reset_cross_signing_identity(&self) {
658        self.inner.identity.lock().await.reset();
659    }
660
661    /// PrivateCrossSigningIdentity associated with this store
662    pub(crate) fn private_identity(&self) -> Arc<Mutex<PrivateCrossSigningIdentity>> {
663        self.inner.identity.clone()
664    }
665
666    /// Save the given Sessions to the store
667    pub(crate) async fn save_sessions(&self, sessions: &[Session]) -> Result<()> {
668        let changes = Changes { sessions: sessions.to_vec(), ..Default::default() };
669
670        self.save_changes(changes).await
671    }
672
673    pub(crate) async fn get_sessions(
674        &self,
675        sender_key: &str,
676    ) -> Result<Option<Arc<Mutex<Vec<Session>>>>> {
677        self.inner.store.get_sessions(sender_key).await
678    }
679
680    pub(crate) async fn save_changes(&self, changes: Changes) -> Result<()> {
681        self.inner.store.save_changes(changes).await
682    }
683
684    /// Given an `InboundGroupSession` which we have just received, see if we
685    /// have a matching session already in the store, and determine how to
686    /// handle it.
687    ///
688    /// If the store already has everything we can gather from the new session,
689    /// returns `None`. Otherwise, returns a merged session which should be
690    /// persisted to the store.
691    pub(crate) async fn merge_received_group_session(
692        &self,
693        session: InboundGroupSession,
694    ) -> Result<Option<InboundGroupSession>> {
695        let old_session = self
696            .inner
697            .store
698            .get_inbound_group_session(session.room_id(), session.session_id())
699            .await?;
700
701        // If there is no old session, just use the new session.
702        let Some(old_session) = old_session else {
703            info!("Received a new megolm room key");
704            return Ok(Some(session));
705        };
706
707        let index_comparison = session.compare_ratchet(&old_session).await;
708        let trust_level_comparison =
709            session.sender_data.compare_trust_level(&old_session.sender_data);
710
711        let result = match (index_comparison, trust_level_comparison) {
712            (SessionOrdering::Unconnected, _) => {
713                // If this happens, it means that we have two sessions purporting to have the
714                // same session id, but where the ratchets do not match up.
715                // In other words, someone is playing silly buggers.
716                warn!(
717                    "Received a group session with an ratchet that does not connect to the one in the store, discarding"
718                );
719                None
720            }
721
722            (SessionOrdering::Better, std::cmp::Ordering::Greater)
723            | (SessionOrdering::Better, std::cmp::Ordering::Equal)
724            | (SessionOrdering::Equal, std::cmp::Ordering::Greater) => {
725                // The new session is unambiguously better than what we have in the store.
726                info!(
727                    ?index_comparison,
728                    ?trust_level_comparison,
729                    "Received a megolm room key that we have a worse version of, merging"
730                );
731                Some(session)
732            }
733
734            (SessionOrdering::Worse, std::cmp::Ordering::Less)
735            | (SessionOrdering::Worse, std::cmp::Ordering::Equal)
736            | (SessionOrdering::Equal, std::cmp::Ordering::Less) => {
737                // The new session is unambiguously worse than the one we have in the store.
738                warn!(
739                    ?index_comparison,
740                    ?trust_level_comparison,
741                    "Received a megolm room key that we already have a better version \
742                     of, discarding"
743                );
744                None
745            }
746
747            (SessionOrdering::Equal, std::cmp::Ordering::Equal) => {
748                // The new session is the same as what we have.
749                info!("Received a megolm room key that we already have, discarding");
750                None
751            }
752
753            (SessionOrdering::Better, std::cmp::Ordering::Less) => {
754                // We need to take the ratchet from the new session, and the
755                // sender data from the old session.
756                info!("Upgrading a previously-received megolm session with new ratchet");
757                let result = old_session.with_ratchet(&session);
758                // We'll need to back it up again.
759                result.reset_backup_state();
760                Some(result)
761            }
762
763            (SessionOrdering::Worse, std::cmp::Ordering::Greater) => {
764                // We need to take the ratchet from the old session, and the
765                // sender data from the new session.
766                info!("Upgrading a previously-received megolm session with new sender data");
767                Some(session.with_ratchet(&old_session))
768            }
769        };
770
771        Ok(result)
772    }
773
774    #[cfg(test)]
775    /// Testing helper to allow to save only a set of devices
776    pub(crate) async fn save_device_data(&self, devices: &[DeviceData]) -> Result<()> {
777        use types::DeviceChanges;
778
779        let changes = Changes {
780            devices: DeviceChanges { changed: devices.to_vec(), ..Default::default() },
781            ..Default::default()
782        };
783
784        self.save_changes(changes).await
785    }
786
787    /// Convenience helper to persist an array of [`InboundGroupSession`]s.
788    pub(crate) async fn save_inbound_group_sessions(
789        &self,
790        sessions: &[InboundGroupSession],
791    ) -> Result<()> {
792        let changes = Changes { inbound_group_sessions: sessions.to_vec(), ..Default::default() };
793
794        self.save_changes(changes).await
795    }
796
797    /// Get the display name of our own device.
798    pub(crate) async fn device_display_name(&self) -> Result<Option<String>, CryptoStoreError> {
799        Ok(self
800            .inner
801            .store
802            .get_device(self.user_id(), self.device_id())
803            .await?
804            .and_then(|d| d.display_name().map(|d| d.to_owned())))
805    }
806
807    /// Get the device data for the given [`UserId`] and [`DeviceId`].
808    ///
809    /// *Note*: This method will include our own device which is always present
810    /// in the store.
811    pub(crate) async fn get_device_data(
812        &self,
813        user_id: &UserId,
814        device_id: &DeviceId,
815    ) -> Result<Option<DeviceData>> {
816        self.inner.store.get_device(user_id, device_id).await
817    }
818
819    /// Get the device data for the given [`UserId`] and [`DeviceId`].
820    ///
821    /// *Note*: This method will **not** include our own device.
822    ///
823    /// Use this method if you need a list of recipients for a given user, since
824    /// we don't want to encrypt for our own device, otherwise take a look at
825    /// the [`Store::get_device_data_for_user`] method.
826    pub(crate) async fn get_device_data_for_user_filtered(
827        &self,
828        user_id: &UserId,
829    ) -> Result<HashMap<OwnedDeviceId, DeviceData>> {
830        self.inner.store.get_user_devices(user_id).await.map(|mut d| {
831            if user_id == self.user_id() {
832                d.remove(self.device_id());
833            }
834            d
835        })
836    }
837
838    /// Get the [`DeviceData`] for all the devices a user has.
839    ///
840    /// *Note*: This method will include our own device which is always present
841    /// in the store.
842    ///
843    /// Use this method if you need to operate on or update all devices of a
844    /// user, otherwise take a look at the
845    /// [`Store::get_device_data_for_user_filtered`] method.
846    pub(crate) async fn get_device_data_for_user(
847        &self,
848        user_id: &UserId,
849    ) -> Result<HashMap<OwnedDeviceId, DeviceData>> {
850        self.inner.store.get_user_devices(user_id).await
851    }
852
853    /// Get a [`Device`] for the given user with the given
854    /// [`Curve25519PublicKey`] key.
855    ///
856    /// *Note*: This method will include our own device which is always present
857    /// in the store.
858    pub(crate) async fn get_device_from_curve_key(
859        &self,
860        user_id: &UserId,
861        curve_key: Curve25519PublicKey,
862    ) -> Result<Option<Device>> {
863        self.get_user_devices(user_id)
864            .await
865            .map(|d| d.devices().find(|d| d.curve25519_key() == Some(curve_key)))
866    }
867
868    /// Get all devices associated with the given [`UserId`].
869    ///
870    /// This method is more expensive than the
871    /// [`Store::get_device_data_for_user`] method, since a [`Device`]
872    /// requires the [`OwnUserIdentityData`] and the [`UserIdentityData`] of the
873    /// device owner to be fetched from the store as well.
874    ///
875    /// *Note*: This method will include our own device which is always present
876    /// in the store.
877    pub(crate) async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices> {
878        let devices = self.get_device_data_for_user(user_id).await?;
879
880        let own_identity = self
881            .inner
882            .store
883            .get_user_identity(self.user_id())
884            .await?
885            .and_then(|i| i.own().cloned());
886        let device_owner_identity = self.inner.store.get_user_identity(user_id).await?;
887
888        Ok(UserDevices {
889            inner: devices,
890            verification_machine: self.inner.verification_machine.clone(),
891            own_identity,
892            device_owner_identity,
893        })
894    }
895
896    /// Get a [`Device`] for the given user with the given [`DeviceId`].
897    ///
898    /// This method is more expensive than the [`Store::get_device_data`] method
899    /// since a [`Device`] requires the [`OwnUserIdentityData`] and the
900    /// [`UserIdentityData`] of the device owner to be fetched from the
901    /// store as well.
902    ///
903    /// *Note*: This method will include our own device which is always present
904    /// in the store.
905    pub(crate) async fn get_device(
906        &self,
907        user_id: &UserId,
908        device_id: &DeviceId,
909    ) -> Result<Option<Device>> {
910        if let Some(device_data) = self.inner.store.get_device(user_id, device_id).await? {
911            Ok(Some(self.wrap_device_data(device_data).await?))
912        } else {
913            Ok(None)
914        }
915    }
916
917    /// Create a new device using the supplied [`DeviceData`]. Normally we would
918    /// call [`Self::get_device`] to find an existing device inside this
919    /// store. Only call this if you have some existing DeviceData and want
920    /// to wrap it with the extra information provided by a [`Device`].
921    pub(crate) async fn wrap_device_data(&self, device_data: DeviceData) -> Result<Device> {
922        let own_identity = self
923            .inner
924            .store
925            .get_user_identity(self.user_id())
926            .await?
927            .and_then(|i| i.own().cloned());
928
929        let device_owner_identity =
930            self.inner.store.get_user_identity(device_data.user_id()).await?;
931
932        Ok(Device {
933            inner: device_data,
934            verification_machine: self.inner.verification_machine.clone(),
935            own_identity,
936            device_owner_identity,
937        })
938    }
939
940    ///  Get the Identity of `user_id`
941    pub(crate) async fn get_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
942        let own_identity = self
943            .inner
944            .store
945            .get_user_identity(self.user_id())
946            .await?
947            .and_then(as_variant!(UserIdentityData::Own));
948
949        Ok(self.inner.store.get_user_identity(user_id).await?.map(|i| {
950            UserIdentity::new(
951                self.clone(),
952                i,
953                self.inner.verification_machine.to_owned(),
954                own_identity,
955            )
956        }))
957    }
958
959    /// Try to export the secret with the given secret name.
960    ///
961    /// The exported secret will be encoded as unpadded base64. Returns `Null`
962    /// if the secret can't be found.
963    ///
964    /// # Arguments
965    ///
966    /// * `secret_name` - The name of the secret that should be exported.
967    pub async fn export_secret(
968        &self,
969        secret_name: &SecretName,
970    ) -> Result<Option<String>, CryptoStoreError> {
971        Ok(match secret_name {
972            SecretName::CrossSigningMasterKey
973            | SecretName::CrossSigningUserSigningKey
974            | SecretName::CrossSigningSelfSigningKey => {
975                self.inner.identity.lock().await.export_secret(secret_name).await
976            }
977            SecretName::RecoveryKey => {
978                if let Some(key) = self.load_backup_keys().await?.decryption_key {
979                    let exported = key.to_base64();
980                    Some(exported)
981                } else {
982                    None
983                }
984            }
985            name => {
986                warn!(secret = ?name, "Unknown secret was requested");
987                None
988            }
989        })
990    }
991
992    /// Export all the private cross signing keys we have.
993    ///
994    /// The export will contain the seed for the ed25519 keys as a unpadded
995    /// base64 encoded string.
996    ///
997    /// This method returns `None` if we don't have any private cross signing
998    /// keys.
999    pub async fn export_cross_signing_keys(
1000        &self,
1001    ) -> Result<Option<CrossSigningKeyExport>, CryptoStoreError> {
1002        let master_key = self.export_secret(&SecretName::CrossSigningMasterKey).await?;
1003        let self_signing_key = self.export_secret(&SecretName::CrossSigningSelfSigningKey).await?;
1004        let user_signing_key = self.export_secret(&SecretName::CrossSigningUserSigningKey).await?;
1005
1006        Ok(if master_key.is_none() && self_signing_key.is_none() && user_signing_key.is_none() {
1007            None
1008        } else {
1009            Some(CrossSigningKeyExport { master_key, self_signing_key, user_signing_key })
1010        })
1011    }
1012
1013    /// Import our private cross signing keys.
1014    ///
1015    /// The export needs to contain the seed for the Ed25519 keys as an unpadded
1016    /// base64 encoded string.
1017    pub async fn import_cross_signing_keys(
1018        &self,
1019        export: CrossSigningKeyExport,
1020    ) -> Result<CrossSigningStatus, SecretImportError> {
1021        if let Some(public_identity) =
1022            self.get_identity(self.user_id()).await?.and_then(|i| i.own())
1023        {
1024            let identity = self.inner.identity.lock().await;
1025
1026            identity
1027                .import_secrets(
1028                    public_identity.to_owned(),
1029                    export.master_key.as_deref(),
1030                    export.self_signing_key.as_deref(),
1031                    export.user_signing_key.as_deref(),
1032                )
1033                .await?;
1034
1035            let status = identity.status().await;
1036
1037            let diff = identity.get_public_identity_diff(&public_identity.inner).await;
1038
1039            let mut changes =
1040                Changes { private_identity: Some(identity.clone()), ..Default::default() };
1041
1042            if diff.none_differ() {
1043                public_identity.mark_as_verified();
1044                changes.identities.changed.push(UserIdentityData::Own(public_identity.inner));
1045            }
1046
1047            info!(?status, "Successfully imported the private cross-signing keys");
1048
1049            self.save_changes(changes).await?;
1050        } else {
1051            warn!(
1052                "No public identity found while importing cross-signing keys, \
1053                 a /keys/query needs to be done"
1054            );
1055        }
1056
1057        Ok(self.inner.identity.lock().await.status().await)
1058    }
1059
1060    /// Export all the secrets we have in the store into a [`SecretsBundle`].
1061    ///
1062    /// This method will export all the private cross-signing keys and, if
1063    /// available, the private part of a backup key and its accompanying
1064    /// version.
1065    ///
1066    /// The method will fail if we don't have all three private cross-signing
1067    /// keys available.
1068    ///
1069    /// **Warning**: Only export this and share it with a trusted recipient,
1070    /// i.e. if an existing device is sharing this with a new device.
1071    pub async fn export_secrets_bundle(&self) -> Result<SecretsBundle, SecretsBundleExportError> {
1072        let Some(cross_signing) = self.export_cross_signing_keys().await? else {
1073            return Err(SecretsBundleExportError::MissingCrossSigningKeys);
1074        };
1075
1076        let Some(master_key) = cross_signing.master_key.clone() else {
1077            return Err(SecretsBundleExportError::MissingCrossSigningKey(KeyUsage::Master));
1078        };
1079
1080        let Some(user_signing_key) = cross_signing.user_signing_key.clone() else {
1081            return Err(SecretsBundleExportError::MissingCrossSigningKey(KeyUsage::UserSigning));
1082        };
1083
1084        let Some(self_signing_key) = cross_signing.self_signing_key.clone() else {
1085            return Err(SecretsBundleExportError::MissingCrossSigningKey(KeyUsage::SelfSigning));
1086        };
1087
1088        let backup_keys = self.load_backup_keys().await?;
1089
1090        let backup = if let Some(key) = backup_keys.decryption_key {
1091            if let Some(backup_version) = backup_keys.backup_version {
1092                Some(BackupSecrets::MegolmBackupV1Curve25519AesSha2(
1093                    MegolmBackupV1Curve25519AesSha2Secrets { key, backup_version },
1094                ))
1095            } else {
1096                return Err(SecretsBundleExportError::MissingBackupVersion);
1097            }
1098        } else {
1099            None
1100        };
1101
1102        Ok(SecretsBundle {
1103            cross_signing: CrossSigningSecrets { master_key, user_signing_key, self_signing_key },
1104            backup,
1105        })
1106    }
1107
1108    /// Import and persists secrets from a [`SecretsBundle`].
1109    ///
1110    /// This method will import all the private cross-signing keys and, if
1111    /// available, the private part of a backup key and its accompanying
1112    /// version into the store.
1113    ///
1114    /// **Warning**: Only import this from a trusted source, i.e. if an existing
1115    /// device is sharing this with a new device. The imported cross-signing
1116    /// keys will create a [`OwnUserIdentity`] and mark it as verified.
1117    ///
1118    /// The backup key will be persisted in the store and can be enabled using
1119    /// the [`BackupMachine`].
1120    pub async fn import_secrets_bundle(
1121        &self,
1122        bundle: &SecretsBundle,
1123    ) -> Result<(), SecretImportError> {
1124        let mut changes = Changes::default();
1125
1126        if let Some(backup_bundle) = &bundle.backup {
1127            match backup_bundle {
1128                BackupSecrets::MegolmBackupV1Curve25519AesSha2(bundle) => {
1129                    changes.backup_decryption_key = Some(bundle.key.clone());
1130                    changes.backup_version = Some(bundle.backup_version.clone());
1131                }
1132            }
1133        }
1134
1135        let identity = self.inner.identity.lock().await;
1136
1137        identity
1138            .import_secrets_unchecked(
1139                Some(&bundle.cross_signing.master_key),
1140                Some(&bundle.cross_signing.self_signing_key),
1141                Some(&bundle.cross_signing.user_signing_key),
1142            )
1143            .await?;
1144
1145        let public_identity = identity.to_public_identity().await.expect(
1146            "We should be able to create a new public identity since we just imported \
1147             all the private cross-signing keys",
1148        );
1149
1150        changes.private_identity = Some(identity.clone());
1151        changes.identities.new.push(UserIdentityData::Own(public_identity));
1152
1153        Ok(self.save_changes(changes).await?)
1154    }
1155
1156    /// Import the given `secret` named `secret_name` into the keystore.
1157    pub async fn import_secret(&self, secret: &GossippedSecret) -> Result<(), SecretImportError> {
1158        match &secret.secret_name {
1159            SecretName::CrossSigningMasterKey
1160            | SecretName::CrossSigningUserSigningKey
1161            | SecretName::CrossSigningSelfSigningKey => {
1162                if let Some(public_identity) =
1163                    self.get_identity(self.user_id()).await?.and_then(|i| i.own())
1164                {
1165                    let identity = self.inner.identity.lock().await;
1166
1167                    identity
1168                        .import_secret(
1169                            public_identity,
1170                            &secret.secret_name,
1171                            &secret.event.content.secret,
1172                        )
1173                        .await?;
1174                    info!(
1175                        secret_name = ?secret.secret_name,
1176                        "Successfully imported a private cross signing key"
1177                    );
1178
1179                    let changes =
1180                        Changes { private_identity: Some(identity.clone()), ..Default::default() };
1181
1182                    self.save_changes(changes).await?;
1183                }
1184            }
1185            SecretName::RecoveryKey => {
1186                // We don't import the decryption key here since we'll want to
1187                // check if the public key matches to the latest version on the
1188                // server. We instead put the secret into a secret inbox where
1189                // it will stay until it either gets overwritten
1190                // or the user accepts the secret.
1191            }
1192            name => {
1193                warn!(secret = ?name, "Tried to import an unknown secret");
1194            }
1195        }
1196
1197        Ok(())
1198    }
1199
1200    /// Check whether there is a global flag to only encrypt messages for
1201    /// trusted devices or for everyone.
1202    pub async fn get_only_allow_trusted_devices(&self) -> Result<bool> {
1203        let value = self.get_value("only_allow_trusted_devices").await?.unwrap_or_default();
1204        Ok(value)
1205    }
1206
1207    /// Set global flag whether to encrypt messages for untrusted devices, or
1208    /// whether they should be excluded from the conversation.
1209    pub async fn set_only_allow_trusted_devices(
1210        &self,
1211        block_untrusted_devices: bool,
1212    ) -> Result<()> {
1213        self.set_value("only_allow_trusted_devices", &block_untrusted_devices).await
1214    }
1215
1216    /// Get custom stored value associated with a key
1217    pub async fn get_value<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
1218        let Some(value) = self.get_custom_value(key).await? else {
1219            return Ok(None);
1220        };
1221        let deserialized = self.deserialize_value(&value)?;
1222        Ok(Some(deserialized))
1223    }
1224
1225    /// Store custom value associated with a key
1226    pub async fn set_value(&self, key: &str, value: &impl Serialize) -> Result<()> {
1227        let serialized = self.serialize_value(value)?;
1228        self.set_custom_value(key, serialized).await?;
1229        Ok(())
1230    }
1231
1232    fn serialize_value(&self, value: &impl Serialize) -> Result<Vec<u8>> {
1233        let serialized =
1234            rmp_serde::to_vec_named(value).map_err(|x| CryptoStoreError::Backend(x.into()))?;
1235        Ok(serialized)
1236    }
1237
1238    fn deserialize_value<T: DeserializeOwned>(&self, value: &[u8]) -> Result<T> {
1239        let deserialized =
1240            rmp_serde::from_slice(value).map_err(|e| CryptoStoreError::Backend(e.into()))?;
1241        Ok(deserialized)
1242    }
1243
1244    /// Receive notifications of room keys being received as a [`Stream`].
1245    ///
1246    /// Each time a room key is updated in any way, an update will be sent to
1247    /// the stream. Updates that happen at the same time are batched into a
1248    /// [`Vec`].
1249    ///
1250    /// If the reader of the stream lags too far behind an error will be sent to
1251    /// the reader.
1252    ///
1253    /// The stream will terminate once all references to the underlying
1254    /// `CryptoStoreWrapper` are dropped.
1255    pub fn room_keys_received_stream(
1256        &self,
1257    ) -> impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<> {
1258        self.inner.store.room_keys_received_stream()
1259    }
1260
1261    /// Receive notifications of received `m.room_key.withheld` messages.
1262    ///
1263    /// Each time an `m.room_key.withheld` is received and stored, an update
1264    /// will be sent to the stream. Updates that happen at the same time are
1265    /// batched into a [`Vec`].
1266    ///
1267    /// If the reader of the stream lags too far behind, a warning will be
1268    /// logged and items will be dropped.
1269    pub fn room_keys_withheld_received_stream(
1270        &self,
1271    ) -> impl Stream<Item = Vec<RoomKeyWithheldInfo>> + use<> {
1272        self.inner.store.room_keys_withheld_received_stream()
1273    }
1274
1275    /// Returns a stream of user identity updates, allowing users to listen for
1276    /// notifications about new or changed user identities.
1277    ///
1278    /// The stream produced by this method emits updates whenever a new user
1279    /// identity is discovered or when an existing identities information is
1280    /// changed. Users can subscribe to this stream and receive updates in
1281    /// real-time.
1282    ///
1283    /// Caution: the returned stream will never terminate, and it holds a
1284    /// reference to the [`CryptoStore`]. Listeners should be careful to avoid
1285    /// resource leaks.
1286    ///
1287    /// # Examples
1288    ///
1289    /// ```no_run
1290    /// # use matrix_sdk_crypto::OlmMachine;
1291    /// # use ruma::{device_id, user_id};
1292    /// # use futures_util::{pin_mut, StreamExt};
1293    /// # let machine: OlmMachine = unimplemented!();
1294    /// # futures_executor::block_on(async {
1295    /// let identities_stream = machine.store().user_identities_stream();
1296    /// pin_mut!(identities_stream);
1297    ///
1298    /// for identity_updates in identities_stream.next().await {
1299    ///     for (_, identity) in identity_updates.new {
1300    ///         println!("A new identity has been added {}", identity.user_id());
1301    ///     }
1302    /// }
1303    /// # });
1304    /// ```
1305    pub fn user_identities_stream(&self) -> impl Stream<Item = IdentityUpdates> + use<> {
1306        let verification_machine = self.inner.verification_machine.to_owned();
1307
1308        let this = self.clone();
1309        self.inner.store.identities_stream().map(move |(own_identity, identities, _)| {
1310            let (new_identities, changed_identities, unchanged_identities) = identities.into_maps();
1311
1312            let map_identity = |(user_id, identity)| {
1313                (
1314                    user_id,
1315                    UserIdentity::new(
1316                        this.clone(),
1317                        identity,
1318                        verification_machine.to_owned(),
1319                        own_identity.to_owned(),
1320                    ),
1321                )
1322            };
1323
1324            let new = new_identities.into_iter().map(map_identity).collect();
1325            let changed = changed_identities.into_iter().map(map_identity).collect();
1326            let unchanged = unchanged_identities.into_iter().map(map_identity).collect();
1327
1328            IdentityUpdates { new, changed, unchanged }
1329        })
1330    }
1331
1332    /// Returns a stream of device updates, allowing users to listen for
1333    /// notifications about new or changed devices.
1334    ///
1335    /// The stream produced by this method emits updates whenever a new device
1336    /// is discovered or when an existing device's information is changed. Users
1337    /// can subscribe to this stream and receive updates in real-time.
1338    ///
1339    /// Caution: the returned stream will never terminate, and it holds a
1340    /// reference to the [`CryptoStore`]. Listeners should be careful to avoid
1341    /// resource leaks.
1342    ///
1343    /// # Examples
1344    ///
1345    /// ```no_run
1346    /// # use matrix_sdk_crypto::OlmMachine;
1347    /// # use ruma::{device_id, user_id};
1348    /// # use futures_util::{pin_mut, StreamExt};
1349    /// # let machine: OlmMachine = unimplemented!();
1350    /// # futures_executor::block_on(async {
1351    /// let devices_stream = machine.store().devices_stream();
1352    /// pin_mut!(devices_stream);
1353    ///
1354    /// for device_updates in devices_stream.next().await {
1355    ///     if let Some(user_devices) = device_updates.new.get(machine.user_id()) {
1356    ///         for device in user_devices.values() {
1357    ///             println!("A new device has been added {}", device.device_id());
1358    ///         }
1359    ///     }
1360    /// }
1361    /// # });
1362    /// ```
1363    pub fn devices_stream(&self) -> impl Stream<Item = DeviceUpdates> + use<> {
1364        let verification_machine = self.inner.verification_machine.to_owned();
1365
1366        self.inner.store.identities_stream().map(move |(own_identity, identities, devices)| {
1367            collect_device_updates(
1368                verification_machine.to_owned(),
1369                own_identity,
1370                identities,
1371                devices,
1372            )
1373        })
1374    }
1375
1376    /// Returns a [`Stream`] of user identity and device updates
1377    ///
1378    /// The stream returned by this method returns the same data as
1379    /// [`Store::user_identities_stream`] and [`Store::devices_stream`] but does
1380    /// not include references to the `VerificationMachine`. It is therefore a
1381    /// lower-level view on that data.
1382    ///
1383    /// The stream will terminate once all references to the underlying
1384    /// `CryptoStoreWrapper` are dropped.
1385    pub fn identities_stream_raw(
1386        &self,
1387    ) -> impl Stream<Item = (IdentityChanges, DeviceChanges)> + use<> {
1388        self.inner.store.identities_stream().map(|(_, identities, devices)| (identities, devices))
1389    }
1390
1391    /// Creates a [`CrossProcessLock`] for this store, that will contain the
1392    /// given key and value when hold.
1393    pub fn create_store_lock(
1394        &self,
1395        lock_key: String,
1396        config: CrossProcessLockConfig,
1397    ) -> CrossProcessLock<LockableCryptoStore> {
1398        self.inner.store.create_store_lock(lock_key, config)
1399    }
1400
1401    /// Receive notifications of gossipped secrets being received and stored in
1402    /// the secret inbox as a [`Stream`].
1403    ///
1404    /// The gossipped secrets are received using the `m.secret.send` event type
1405    /// and are guaranteed to have been received over a 1-to-1 Olm
1406    /// [`Session`] from a verified [`Device`].
1407    ///
1408    /// The [`GossippedSecret`] can also be later found in the secret inbox and
1409    /// retrieved using the [`CryptoStore::get_secrets_from_inbox()`] method.
1410    ///
1411    /// After a suitable secret of a certain type has been found it can be
1412    /// removed from the store
1413    /// using the [`CryptoStore::delete_secrets_from_inbox()`] method.
1414    ///
1415    /// The only secret this will currently broadcast is the
1416    /// `m.megolm_backup.v1`.
1417    ///
1418    /// If the reader of the stream lags too far behind, a warning will be
1419    /// logged and items will be dropped.
1420    ///
1421    /// # Examples
1422    ///
1423    /// ```no_run
1424    /// # use matrix_sdk_crypto::OlmMachine;
1425    /// # use ruma::{device_id, owned_user_id};
1426    /// # use futures_util::{pin_mut, StreamExt};
1427    /// # let alice = owned_user_id!("@alice:example.org");
1428    /// # futures_executor::block_on(async {
1429    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1430    ///
1431    /// let secret_stream = machine.store().secrets_stream();
1432    /// pin_mut!(secret_stream);
1433    ///
1434    /// for secret in secret_stream.next().await {
1435    ///     // Accept the secret if it's valid, then delete all the secrets of this type.
1436    ///     machine.store().delete_secrets_from_inbox(&secret.secret_name);
1437    /// }
1438    /// # });
1439    /// ```
1440    pub fn secrets_stream(&self) -> impl Stream<Item = SecretsInboxItem> + use<> {
1441        self.inner.store.secrets_stream()
1442    }
1443
1444    /// Receive notifications of historic room key bundles as a [`Stream`].
1445    ///
1446    /// Historic room key bundles are defined in [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
1447    ///
1448    /// Each time a historic room key bundle was received, an update will be
1449    /// sent to the stream. This stream can be used to accept historic room key
1450    /// bundles that arrive out of order, i.e. the bundle arrives after the
1451    /// user has already accepted a room invitation.
1452    ///
1453    /// # Examples
1454    ///
1455    /// ```no_run
1456    /// # use matrix_sdk_crypto::{
1457    /// #    OlmMachine,
1458    /// #    store::types::StoredRoomKeyBundleData,
1459    /// #    types::room_history::RoomKeyBundle
1460    /// # };
1461    /// # use ruma::{device_id, owned_user_id};
1462    /// # use futures_util::{pin_mut, StreamExt};
1463    /// # let alice = owned_user_id!("@alice:example.org");
1464    /// # async {
1465    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1466    /// let bundle_stream = machine.store().historic_room_key_stream();
1467    /// pin_mut!(bundle_stream);
1468    ///
1469    /// while let Some(bundle_info) = bundle_stream.next().await {
1470    ///     // Try to find the bundle content in the store and if it's valid accept it.
1471    ///     if let Some(bundle_data) = machine.store().get_received_room_key_bundle_data(&bundle_info.room_id, &bundle_info.sender).await? {
1472    ///         // Download the bundle now and import it.
1473    ///         let bundle: RoomKeyBundle = todo!("Download the bundle");
1474    ///         machine.store().receive_room_key_bundle(
1475    ///             &bundle_data,
1476    ///             bundle,
1477    ///             |_, _| {},
1478    ///         ).await?;
1479    ///     }
1480    /// }
1481    /// # anyhow::Ok(()) };
1482    /// ```
1483    pub fn historic_room_key_stream(&self) -> impl Stream<Item = RoomKeyBundleInfo> + use<> {
1484        self.inner.store.historic_room_key_stream()
1485    }
1486
1487    /// Import the given room keys into the store.
1488    ///
1489    /// # Arguments
1490    ///
1491    /// * `exported_keys` - The keys to be imported.
1492    /// * `from_backup_version` - If the keys came from key backup, the key
1493    ///   backup version. This will cause the keys to be marked as already
1494    ///   backed up, and therefore not requiring another backup.
1495    /// * `progress_listener` - Callback which will be called after each key is
1496    ///   processed. Called with arguments `(processed, total)` where
1497    ///   `processed` is the number of keys processed so far, and `total` is the
1498    ///   total number of keys (i.e., `exported_keys.len()`).
1499    pub async fn import_room_keys(
1500        &self,
1501        exported_keys: Vec<ExportedRoomKey>,
1502        from_backup_version: Option<&str>,
1503        progress_listener: impl Fn(usize, usize),
1504    ) -> Result<RoomKeyImportResult> {
1505        let exported_keys = exported_keys.iter().filter_map(|key| {
1506            key.try_into()
1507                .map_err(|e| {
1508                    warn!(
1509                        sender_key = key.sender_key().to_base64(),
1510                        room_id = ?key.room_id(),
1511                        session_id = key.session_id(),
1512                        error = ?e,
1513                        "Couldn't import a room key from a file export."
1514                    );
1515                })
1516                .ok()
1517        });
1518        self.import_sessions_impl(exported_keys, from_backup_version, progress_listener).await
1519    }
1520
1521    /// Import the given room keys into our store.
1522    ///
1523    /// # Arguments
1524    ///
1525    /// * `exported_keys` - A list of previously exported keys that should be
1526    ///   imported into our store. If we already have a better version of a key
1527    ///   the key will *not* be imported.
1528    ///
1529    /// Returns a tuple of numbers that represent the number of sessions that
1530    /// were imported and the total number of sessions that were found in the
1531    /// key export.
1532    ///
1533    /// # Examples
1534    ///
1535    /// ```no_run
1536    /// # use std::io::Cursor;
1537    /// # use matrix_sdk_crypto::{OlmMachine, decrypt_room_key_export};
1538    /// # use ruma::{device_id, user_id};
1539    /// # let alice = user_id!("@alice:example.org");
1540    /// # async {
1541    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1542    /// # let export = Cursor::new("".to_owned());
1543    /// let exported_keys = decrypt_room_key_export(export, "1234").unwrap();
1544    /// machine.store().import_exported_room_keys(exported_keys, |_, _| {}).await.unwrap();
1545    /// # };
1546    /// ```
1547    pub async fn import_exported_room_keys(
1548        &self,
1549        exported_keys: Vec<ExportedRoomKey>,
1550        progress_listener: impl Fn(usize, usize),
1551    ) -> Result<RoomKeyImportResult> {
1552        self.import_room_keys(exported_keys, None, progress_listener).await
1553    }
1554
1555    async fn import_sessions_impl(
1556        &self,
1557        sessions: impl Iterator<Item = InboundGroupSession>,
1558        from_backup_version: Option<&str>,
1559        progress_listener: impl Fn(usize, usize),
1560    ) -> Result<RoomKeyImportResult> {
1561        let sessions: Vec<_> = sessions.collect();
1562        let mut imported_sessions = Vec::new();
1563
1564        let total_count = sessions.len();
1565        let mut keys = BTreeMap::new();
1566
1567        for (i, session) in sessions.into_iter().enumerate() {
1568            // Only import the session if we didn't have this session or
1569            // if it's a better version of the same session.
1570            if let Some(merged) = self.merge_received_group_session(session).await? {
1571                if from_backup_version.is_some() {
1572                    merged.mark_as_backed_up();
1573                }
1574
1575                keys.entry(merged.room_id().to_owned())
1576                    .or_insert_with(BTreeMap::new)
1577                    .entry(merged.sender_key().to_base64())
1578                    .or_insert_with(BTreeSet::new)
1579                    .insert(merged.session_id().to_owned());
1580
1581                imported_sessions.push(merged);
1582            }
1583
1584            progress_listener(i, total_count);
1585        }
1586
1587        let imported_count = imported_sessions.len();
1588
1589        self.inner
1590            .store
1591            .save_inbound_group_sessions(imported_sessions, from_backup_version)
1592            .await?;
1593
1594        info!(total_count, imported_count, room_keys = ?keys, "Successfully imported room keys");
1595
1596        Ok(RoomKeyImportResult::new(imported_count, total_count, keys))
1597    }
1598
1599    pub(crate) fn crypto_store(&self) -> Arc<CryptoStoreWrapper> {
1600        self.inner.store.clone()
1601    }
1602
1603    /// Export the keys that match the given predicate.
1604    ///
1605    /// # Arguments
1606    ///
1607    /// * `predicate` - A closure that will be called for every known
1608    ///   `InboundGroupSession`, which represents a room key. If the closure
1609    ///   returns `true` the `InboundGroupSession` will be included in the
1610    ///   export, if the closure returns `false` it will not be included.
1611    ///
1612    /// # Examples
1613    ///
1614    /// ```no_run
1615    /// # use matrix_sdk_crypto::{OlmMachine, encrypt_room_key_export};
1616    /// # use ruma::{device_id, user_id, room_id};
1617    /// # let alice = user_id!("@alice:example.org");
1618    /// # async {
1619    /// # let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1620    /// let room_id = room_id!("!test:localhost");
1621    /// let exported_keys = machine.store().export_room_keys(|s| s.room_id() == room_id).await.unwrap();
1622    /// let encrypted_export = encrypt_room_key_export(&exported_keys, "1234", 1);
1623    /// # };
1624    /// ```
1625    pub async fn export_room_keys(
1626        &self,
1627        predicate: impl FnMut(&InboundGroupSession) -> bool,
1628    ) -> Result<Vec<ExportedRoomKey>> {
1629        let mut exported = Vec::new();
1630
1631        let mut sessions = self.get_inbound_group_sessions().await?;
1632        sessions.retain(predicate);
1633
1634        for session in sessions {
1635            let export = session.export().await;
1636            exported.push(export);
1637        }
1638
1639        Ok(exported)
1640    }
1641
1642    /// Export room keys matching a predicate, providing them as an async
1643    /// `Stream`.
1644    ///
1645    /// # Arguments
1646    ///
1647    /// * `predicate` - A closure that will be called for every known
1648    ///   `InboundGroupSession`, which represents a room key. If the closure
1649    ///   returns `true` the `InboundGroupSession` will be included in the
1650    ///   export, if the closure returns `false` it will not be included.
1651    ///
1652    /// # Examples
1653    ///
1654    /// ```no_run
1655    /// use std::pin::pin;
1656    ///
1657    /// use matrix_sdk_crypto::{OlmMachine, olm::ExportedRoomKey};
1658    /// use ruma::{device_id, room_id, user_id};
1659    /// use tokio_stream::StreamExt;
1660    /// # async {
1661    /// let alice = user_id!("@alice:example.org");
1662    /// let machine = OlmMachine::new(&alice, device_id!("DEVICEID")).await;
1663    /// let room_id = room_id!("!test:localhost");
1664    /// let mut keys = pin!(
1665    ///     machine
1666    ///         .store()
1667    ///         .export_room_keys_stream(|s| s.room_id() == room_id)
1668    ///         .await
1669    ///         .unwrap()
1670    /// );
1671    /// while let Some(key) = keys.next().await {
1672    ///     println!("{}", key.room_id);
1673    /// }
1674    /// # };
1675    /// ```
1676    pub async fn export_room_keys_stream(
1677        &self,
1678        predicate: impl FnMut(&InboundGroupSession) -> bool,
1679    ) -> Result<impl Stream<Item = ExportedRoomKey>> {
1680        // TODO: if/when there is a get_inbound_group_sessions_stream, use that here.
1681        let sessions = self.get_inbound_group_sessions().await?;
1682        Ok(futures_util::stream::iter(sessions.into_iter().filter(predicate))
1683            .then(|session| async move { session.export().await }))
1684    }
1685
1686    /// Assemble a room key bundle for sharing encrypted history, as per
1687    /// [MSC4268].
1688    ///
1689    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1690    pub async fn build_room_key_bundle(
1691        &self,
1692        room_id: &RoomId,
1693    ) -> std::result::Result<RoomKeyBundle, CryptoStoreError> {
1694        let sessions = self.get_inbound_group_sessions_by_room_id(room_id).await?;
1695
1696        let mut bundle = RoomKeyBundle::default();
1697        for session in sessions {
1698            if session.shared_history() {
1699                bundle.room_keys.push(session.export().await.into());
1700            } else {
1701                bundle.withheld.push(RoomKeyWithheldContent::new(
1702                    session.algorithm().to_owned(),
1703                    WithheldCode::HistoryNotShared,
1704                    session.room_id().to_owned(),
1705                    session.session_id().to_owned(),
1706                    session.sender_key().to_owned(),
1707                    self.device_id().to_owned(),
1708                ));
1709            }
1710        }
1711
1712        // If we received a key bundle ourselves, in which one or more sessions was
1713        // marked as "history not shared", pass that on to the new user.
1714        let withhelds = self.get_withheld_sessions_by_room_id(room_id).await?;
1715        for withheld in withhelds {
1716            if withheld.content.withheld_code() == WithheldCode::HistoryNotShared {
1717                bundle.withheld.push(withheld.content);
1718            }
1719        }
1720
1721        Ok(bundle)
1722    }
1723
1724    /// Import the contents of a downloaded and decrypted [MSC4268] key bundle.
1725    ///
1726    /// # Arguments
1727    ///
1728    /// * `bundle_info` - The [`StoredRoomKeyBundleData`] of the bundle that is
1729    ///   being received.
1730    /// * `bundle` - The decrypted and deserialized bundle itself.
1731    ///
1732    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1733    #[instrument(skip(self, bundle, progress_listener), fields(bundle_size = bundle.room_keys.len(), sender_data))]
1734    pub async fn receive_room_key_bundle(
1735        &self,
1736        bundle_info: &StoredRoomKeyBundleData,
1737        bundle: RoomKeyBundle,
1738        progress_listener: impl Fn(usize, usize),
1739    ) -> Result<(), CryptoStoreError> {
1740        let sender_data = if bundle_info.sender_data.should_recalculate() {
1741            let device = self
1742                .get_device_from_curve_key(&bundle_info.sender_user, bundle_info.sender_key)
1743                .await?;
1744
1745            device
1746                .as_ref()
1747                .map(SenderData::from_device)
1748                .unwrap_or_else(|| bundle_info.sender_data.clone())
1749        } else {
1750            bundle_info.sender_data.clone()
1751        };
1752
1753        tracing::Span::current().record("sender_data", tracing::field::debug(&sender_data));
1754
1755        // The sender's device must be either `SenderData::SenderUnverified` (i.e.,
1756        // TOFU-trusted) or `SenderData::SenderVerified` (i.e., fully verified
1757        // via user verification and cross-signing).
1758        let Ok(forwarder_data) = (&sender_data).try_into() else {
1759            warn!(
1760                "Not accepting a historic room key bundle due to insufficient trust in the sender"
1761            );
1762            return Ok(());
1763        };
1764
1765        self.import_room_key_bundle_sessions(
1766            bundle_info,
1767            &bundle,
1768            &forwarder_data,
1769            progress_listener,
1770        )
1771        .await?;
1772        self.import_room_key_bundle_withheld_info(bundle_info, &bundle).await?;
1773
1774        Ok(())
1775    }
1776
1777    async fn import_room_key_bundle_sessions(
1778        &self,
1779        bundle_info: &StoredRoomKeyBundleData,
1780        bundle: &RoomKeyBundle,
1781        forwarder_data: &ForwarderData,
1782        progress_listener: impl Fn(usize, usize),
1783    ) -> Result<(), CryptoStoreError> {
1784        let (good, bad): (Vec<_>, Vec<_>) = bundle.room_keys.iter().partition_map(|key| {
1785            if key.room_id != bundle_info.bundle_data.room_id {
1786                trace!("Ignoring key for incorrect room {} in bundle", key.room_id);
1787                Either::Right(key)
1788            } else {
1789                Either::Left(key)
1790            }
1791        });
1792
1793        match (bad.is_empty(), good.is_empty()) {
1794            // Case 1: Completely empty bundle.
1795            (true, true) => {
1796                warn!("Received a completely empty room key bundle");
1797            }
1798
1799            // Case 2: A bundle for the wrong room.
1800            (false, true) => {
1801                let bad_keys: Vec<_> =
1802                    bad.iter().map(|&key| (&key.room_id, &key.session_id)).collect();
1803
1804                warn!(
1805                    ?bad_keys,
1806                    "Received a room key bundle for the wrong room, ignoring all room keys from the bundle"
1807                );
1808            }
1809
1810            // Case 3: A bundle containing useful room keys.
1811            (_, false) => {
1812                // We have at least some good keys, if we also have some bad ones let's
1813                // mention that here.
1814                if !bad.is_empty() {
1815                    warn!(
1816                        bad_key_count = bad.len(),
1817                        "The room key bundle contained some room keys \
1818                         that were meant for a different room"
1819                    );
1820                }
1821
1822                let keys = good.iter().filter_map(|key| {
1823                    key.try_into_inbound_group_session(forwarder_data)
1824                        .map_err(|e| {
1825                            warn!(
1826                                sender_key = ?key.sender_key().to_base64(),
1827                                room_id = ?key.room_id(),
1828                                session_id = key.session_id(),
1829                                error = ?e,
1830                                "Couldn't import a room key from a key bundle."
1831                            );
1832                        })
1833                        .ok()
1834                });
1835
1836                self.import_sessions_impl(keys, None, progress_listener).await?;
1837            }
1838        }
1839
1840        Ok(())
1841    }
1842
1843    async fn import_room_key_bundle_withheld_info(
1844        &self,
1845        bundle_info: &StoredRoomKeyBundleData,
1846        bundle: &RoomKeyBundle,
1847    ) -> Result<(), CryptoStoreError> {
1848        let mut session_id_to_withheld_code_map = BTreeMap::new();
1849
1850        let mut changes = Changes::default();
1851        for withheld in &bundle.withheld {
1852            let (room_id, session_id) = match withheld {
1853                RoomKeyWithheldContent::MegolmV1AesSha2(c) => match (c.room_id(), c.session_id()) {
1854                    (Some(room_id), Some(session_id)) => (room_id, session_id),
1855                    _ => continue,
1856                },
1857                #[cfg(feature = "experimental-algorithms")]
1858                RoomKeyWithheldContent::MegolmV2AesSha2(c) => match (c.room_id(), c.session_id()) {
1859                    (Some(room_id), Some(session_id)) => (room_id, session_id),
1860                    _ => continue,
1861                },
1862                RoomKeyWithheldContent::Unknown(_) => continue,
1863            };
1864
1865            if room_id != bundle_info.bundle_data.room_id {
1866                trace!("Ignoring withheld info for incorrect room {} in bundle", room_id);
1867                continue;
1868            }
1869
1870            changes.withheld_session_info.entry(room_id.to_owned()).or_default().insert(
1871                session_id.to_owned(),
1872                RoomKeyWithheldEntry {
1873                    sender: bundle_info.sender_user.clone(),
1874                    content: withheld.to_owned(),
1875                },
1876            );
1877            session_id_to_withheld_code_map.insert(session_id, withheld.withheld_code());
1878        }
1879
1880        self.save_changes(changes).await?;
1881
1882        info!(
1883            room_id = ?bundle_info.bundle_data.room_id,
1884            ?session_id_to_withheld_code_map,
1885            "Successfully imported withheld info from room key bundle",
1886        );
1887
1888        Ok(())
1889    }
1890
1891    /// Store the fact that we have accepted an invite for a given room on this
1892    /// client, so should accept an [MSC4268] key bundle if one arrives
1893    /// soon.
1894    ///
1895    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
1896    pub async fn store_room_pending_key_bundle(
1897        &self,
1898        room_id: &RoomId,
1899        inviter: &UserId,
1900    ) -> Result<(), CryptoStoreError> {
1901        let invite_accepted_at = MilliSecondsSinceUnixEpoch::now();
1902        self.save_changes(Changes {
1903            rooms_pending_key_bundle: HashMap::from([(
1904                room_id.to_owned(),
1905                Some(RoomPendingKeyBundleDetails {
1906                    room_id: room_id.to_owned(),
1907                    invite_accepted_at,
1908                    inviter: inviter.to_owned(),
1909                }),
1910            )]),
1911            ..Default::default()
1912        })
1913        .await?;
1914
1915        Ok(())
1916    }
1917
1918    /// Clear the record of accepting an invite for a specific room.
1919    ///
1920    /// The counterpart of [`Store::store_room_pending_key_bundle`].
1921    pub async fn clear_room_pending_key_bundle(&self, room_id: &RoomId) -> Result<()> {
1922        self.save_changes(Changes {
1923            rooms_pending_key_bundle: HashMap::from([(room_id.to_owned(), None)]),
1924            ..Default::default()
1925        })
1926        .await
1927    }
1928}
1929
1930impl Deref for Store {
1931    type Target = DynCryptoStore;
1932
1933    fn deref(&self) -> &Self::Target {
1934        self.inner.store.deref().deref()
1935    }
1936}
1937
1938/// A crypto store that implements primitives for cross-process locking.
1939#[derive(Clone, Debug)]
1940pub struct LockableCryptoStore(Arc<dyn CryptoStore<Error = CryptoStoreError>>);
1941
1942impl matrix_sdk_common::cross_process_lock::TryLock for LockableCryptoStore {
1943    type LockError = CryptoStoreError;
1944
1945    async fn try_lock(
1946        &self,
1947        lease_duration_ms: u32,
1948        key: &str,
1949        holder: &str,
1950    ) -> std::result::Result<Option<CrossProcessLockGeneration>, Self::LockError> {
1951        self.0.try_take_leased_lock(lease_duration_ms, key, holder).await
1952    }
1953}
1954
1955#[cfg(test)]
1956mod tests {
1957    use std::{collections::BTreeMap, pin::pin};
1958
1959    use assert_matches2::{assert_let, assert_matches};
1960    use futures_util::StreamExt;
1961    use insta::{_macro_support::Content, assert_json_snapshot, internals::ContentPath};
1962    use matrix_sdk_test::async_test;
1963    use rand::RngExt;
1964    use ruma::{
1965        RoomId, device_id,
1966        events::room::{EncryptedFile, EncryptedFileHashes, V2EncryptedFileInfo},
1967        owned_device_id, owned_mxc_uri, room_id, user_id,
1968    };
1969    use serde_json::json;
1970    use vodozemac::{Ed25519Keypair, megolm::SessionKey};
1971
1972    use crate::{
1973        Account, OlmMachine,
1974        machine::test_helpers::get_machine_pair,
1975        olm::{InboundGroupSession, SenderData},
1976        store::types::{DehydratedDeviceKey, RoomKeyWithheldEntry, StoredRoomKeyBundleData},
1977        types::{
1978            EventEncryptionAlgorithm,
1979            events::{
1980                room_key_bundle::RoomKeyBundleContent,
1981                room_key_withheld::{MegolmV1AesSha2WithheldContent, RoomKeyWithheldContent},
1982            },
1983        },
1984    };
1985
1986    #[async_test]
1987    async fn test_merge_received_group_session() {
1988        let alice_account = Account::with_device_id(user_id!("@a:s.co"), device_id!("ABC"));
1989        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("DEF")).await;
1990
1991        let room_id = room_id!("!test:localhost");
1992
1993        let megolm_signing_key = Ed25519Keypair::new();
1994        let inbound = make_inbound_group_session(&alice_account, &megolm_signing_key, room_id);
1995
1996        // Bob already knows about the session, at index 5, with the device keys.
1997        let mut inbound_at_index_5 =
1998            InboundGroupSession::from_export(&inbound.export_at_index(5).await).unwrap();
1999        inbound_at_index_5.sender_data = inbound.sender_data.clone();
2000        bob.store().save_inbound_group_sessions(&[inbound_at_index_5.clone()]).await.unwrap();
2001
2002        // No changes if we get a disconnected session.
2003        let disconnected = make_inbound_group_session(&alice_account, &megolm_signing_key, room_id);
2004        assert_eq!(bob.store().merge_received_group_session(disconnected).await.unwrap(), None);
2005
2006        // No changes needed when we receive a worse copy of the session
2007        let mut worse =
2008            InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
2009        worse.sender_data = inbound.sender_data.clone();
2010        assert_eq!(bob.store().merge_received_group_session(worse).await.unwrap(), None);
2011
2012        // Nor when we receive an exact copy of what we already have
2013        let mut copy = InboundGroupSession::from_pickle(inbound_at_index_5.pickle().await).unwrap();
2014        copy.sender_data = inbound.sender_data.clone();
2015        assert_eq!(bob.store().merge_received_group_session(copy).await.unwrap(), None);
2016
2017        // But when we receive a better copy of the session, we should get it back
2018        let mut better =
2019            InboundGroupSession::from_export(&inbound.export_at_index(0).await).unwrap();
2020        better.sender_data = inbound.sender_data.clone();
2021        assert_let!(Some(update) = bob.store().merge_received_group_session(better).await.unwrap());
2022        assert_eq!(update.first_known_index(), 0);
2023
2024        // A worse copy of the ratchet, but better trust data
2025        {
2026            let mut worse_ratchet_better_trust =
2027                InboundGroupSession::from_export(&inbound.export_at_index(10).await).unwrap();
2028            let updated_sender_data = SenderData::sender_verified(
2029                alice_account.user_id(),
2030                alice_account.device_id(),
2031                Ed25519Keypair::new().public_key(),
2032            );
2033            worse_ratchet_better_trust.sender_data = updated_sender_data.clone();
2034            assert_let!(
2035                Some(update) = bob
2036                    .store()
2037                    .merge_received_group_session(worse_ratchet_better_trust)
2038                    .await
2039                    .unwrap()
2040            );
2041            assert_eq!(update.sender_data, updated_sender_data);
2042            assert_eq!(update.first_known_index(), 5);
2043            assert_eq!(
2044                update.export_at_index(0).await.session_key.to_bytes(),
2045                inbound.export_at_index(5).await.session_key.to_bytes()
2046            );
2047        }
2048
2049        // A better copy of the ratchet, but worse trust data
2050        {
2051            let mut better_ratchet_worse_trust =
2052                InboundGroupSession::from_export(&inbound.export_at_index(0).await).unwrap();
2053            let updated_sender_data = SenderData::unknown();
2054            better_ratchet_worse_trust.sender_data = updated_sender_data.clone();
2055            assert_let!(
2056                Some(update) = bob
2057                    .store()
2058                    .merge_received_group_session(better_ratchet_worse_trust)
2059                    .await
2060                    .unwrap()
2061            );
2062            assert_eq!(update.sender_data, inbound.sender_data);
2063            assert_eq!(update.first_known_index(), 0);
2064            assert_eq!(
2065                update.export_at_index(0).await.session_key.to_bytes(),
2066                inbound.export_at_index(0).await.session_key.to_bytes()
2067            );
2068        }
2069    }
2070
2071    /// Create an [`InboundGroupSession`] for the given room, using the given
2072    /// Ed25519 key as the signing key/session ID.
2073    fn make_inbound_group_session(
2074        sender_account: &Account,
2075        signing_key: &Ed25519Keypair,
2076        room_id: &RoomId,
2077    ) -> InboundGroupSession {
2078        InboundGroupSession::new(
2079            sender_account.identity_keys.curve25519,
2080            sender_account.identity_keys.ed25519,
2081            room_id,
2082            &make_session_key(signing_key),
2083            SenderData::device_info(crate::types::DeviceKeys::new(
2084                sender_account.user_id().to_owned(),
2085                sender_account.device_id().to_owned(),
2086                vec![],
2087                BTreeMap::new(),
2088                crate::types::Signatures::new(),
2089            )),
2090            None,
2091            EventEncryptionAlgorithm::MegolmV1AesSha2,
2092            Some(ruma::events::room::history_visibility::HistoryVisibility::Shared),
2093            true,
2094        )
2095        .unwrap()
2096    }
2097
2098    /// Make a Megolm [`SessionKey`] using the given Ed25519 key as a signing
2099    /// key/session ID.
2100    fn make_session_key(signing_key: &Ed25519Keypair) -> SessionKey {
2101        // `SessionKey::new` is not public, so the easiest way to construct a Megolm
2102        // session using a known Ed25519 key is to build a byte array in the export
2103        // format.
2104
2105        let mut session_key_bytes = vec![0u8; 229];
2106        // 0: version
2107        session_key_bytes[0] = 2;
2108        // 1..5: index
2109        // 5..133: ratchet key
2110        rand::rng().fill(&mut session_key_bytes[5..133]);
2111        // 133..165: public ed25519 key
2112        session_key_bytes[133..165].copy_from_slice(signing_key.public_key().as_bytes());
2113        // 165..229: signature
2114        let sig = signing_key.sign(&session_key_bytes[0..165]);
2115        session_key_bytes[165..229].copy_from_slice(&sig.to_bytes());
2116
2117        SessionKey::from_bytes(&session_key_bytes).unwrap()
2118    }
2119
2120    #[async_test]
2121    async fn test_import_room_keys_notifies_stream() {
2122        use futures_util::FutureExt;
2123
2124        let (alice, bob, _) =
2125            get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2126
2127        let room1_id = room_id!("!room1:localhost");
2128        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2129        let exported_sessions = alice.store().export_room_keys(|_| true).await.unwrap();
2130
2131        let mut room_keys_received_stream = Box::pin(bob.store().room_keys_received_stream());
2132        bob.store().import_room_keys(exported_sessions, None, |_, _| {}).await.unwrap();
2133
2134        let room_keys = room_keys_received_stream
2135            .next()
2136            .now_or_never()
2137            .flatten()
2138            .expect("We should have received an update of room key infos")
2139            .unwrap();
2140        assert_eq!(room_keys.len(), 1);
2141        assert_eq!(room_keys[0].room_id, "!room1:localhost");
2142    }
2143
2144    #[async_test]
2145    async fn test_export_room_keys_provides_selected_keys() {
2146        // Given an OlmMachine with room keys in it
2147        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2148        let room1_id = room_id!("!room1:localhost");
2149        let room2_id = room_id!("!room2:localhost");
2150        let room3_id = room_id!("!room3:localhost");
2151        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2152        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2153        alice.create_outbound_group_session_with_defaults_test_helper(room3_id).await.unwrap();
2154
2155        // When I export some of the keys
2156        let keys = alice
2157            .store()
2158            .export_room_keys(|s| s.room_id() == room2_id || s.room_id() == room3_id)
2159            .await
2160            .unwrap();
2161
2162        // Then the requested keys were provided
2163        assert_eq!(keys.len(), 2);
2164        assert_eq!(keys[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2165        assert_eq!(keys[1].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2166        assert_eq!(keys[0].room_id, "!room2:localhost");
2167        assert_eq!(keys[1].room_id, "!room3:localhost");
2168        assert_eq!(keys[0].session_key.to_base64().len(), 220);
2169        assert_eq!(keys[1].session_key.to_base64().len(), 220);
2170    }
2171
2172    #[async_test]
2173    async fn test_export_room_keys_stream_can_provide_all_keys() {
2174        // Given an OlmMachine with room keys in it
2175        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2176        let room1_id = room_id!("!room1:localhost");
2177        let room2_id = room_id!("!room2:localhost");
2178        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2179        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2180
2181        // When I export the keys as a stream
2182        let mut keys = pin!(alice.store().export_room_keys_stream(|_| true).await.unwrap());
2183
2184        // And collect them
2185        let mut collected = vec![];
2186        while let Some(key) = keys.next().await {
2187            collected.push(key);
2188        }
2189
2190        // Then all the keys were provided
2191        assert_eq!(collected.len(), 2);
2192        assert_eq!(collected[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2193        assert_eq!(collected[1].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2194        assert_eq!(collected[0].room_id, "!room1:localhost");
2195        assert_eq!(collected[1].room_id, "!room2:localhost");
2196        assert_eq!(collected[0].session_key.to_base64().len(), 220);
2197        assert_eq!(collected[1].session_key.to_base64().len(), 220);
2198    }
2199
2200    #[async_test]
2201    async fn test_export_room_keys_stream_can_provide_a_subset_of_keys() {
2202        // Given an OlmMachine with room keys in it
2203        let (alice, _, _) = get_machine_pair(user_id!("@a:s.co"), user_id!("@b:s.co"), false).await;
2204        let room1_id = room_id!("!room1:localhost");
2205        let room2_id = room_id!("!room2:localhost");
2206        alice.create_outbound_group_session_with_defaults_test_helper(room1_id).await.unwrap();
2207        alice.create_outbound_group_session_with_defaults_test_helper(room2_id).await.unwrap();
2208
2209        // When I export the keys as a stream
2210        let mut keys =
2211            pin!(alice.store().export_room_keys_stream(|s| s.room_id() == room1_id).await.unwrap());
2212
2213        // And collect them
2214        let mut collected = vec![];
2215        while let Some(key) = keys.next().await {
2216            collected.push(key);
2217        }
2218
2219        // Then all the keys matching our predicate were provided, and no others
2220        assert_eq!(collected.len(), 1);
2221        assert_eq!(collected[0].algorithm, EventEncryptionAlgorithm::MegolmV1AesSha2);
2222        assert_eq!(collected[0].room_id, "!room1:localhost");
2223        assert_eq!(collected[0].session_key.to_base64().len(), 220);
2224    }
2225
2226    #[async_test]
2227    async fn test_export_secrets_bundle() {
2228        let user_id = user_id!("@alice:example.com");
2229        let (first, second, _) = get_machine_pair(user_id, user_id, false).await;
2230
2231        let _ = first
2232            .bootstrap_cross_signing(false)
2233            .await
2234            .expect("We should be able to bootstrap cross-signing");
2235
2236        let bundle = first.store().export_secrets_bundle().await.expect(
2237            "We should be able to export the secrets bundle, now that we \
2238             have the cross-signing keys",
2239        );
2240
2241        assert!(bundle.backup.is_none(), "The bundle should not contain a backup key");
2242
2243        second
2244            .store()
2245            .import_secrets_bundle(&bundle)
2246            .await
2247            .expect("We should be able to import the secrets bundle");
2248
2249        let status = second.cross_signing_status().await;
2250        let identity = second.get_identity(user_id, None).await.unwrap().unwrap().own().unwrap();
2251
2252        assert!(identity.is_verified(), "The public identity should be marked as verified.");
2253
2254        assert!(status.is_complete(), "We should have imported all the cross-signing keys");
2255    }
2256
2257    #[async_test]
2258    async fn test_create_dehydrated_device_key() {
2259        let pickle_key = DehydratedDeviceKey::new();
2260
2261        let to_vec = pickle_key.inner.to_vec();
2262        let pickle_key_from_slice = DehydratedDeviceKey::from_slice(to_vec.as_slice())
2263            .expect("Should be able to create a dehydrated device key from slice");
2264
2265        assert_eq!(pickle_key_from_slice.to_base64(), pickle_key.to_base64());
2266    }
2267
2268    #[async_test]
2269    async fn test_create_dehydrated_errors() {
2270        let too_small = [0u8; 22];
2271        let pickle_key = DehydratedDeviceKey::from_slice(&too_small);
2272
2273        assert!(pickle_key.is_err());
2274
2275        let too_big = [0u8; 40];
2276        let pickle_key = DehydratedDeviceKey::from_slice(&too_big);
2277
2278        assert!(pickle_key.is_err());
2279    }
2280
2281    #[async_test]
2282    async fn test_build_room_key_bundle() {
2283        // Given: Alice has sent a number of room keys to Bob, including some in the
2284        // wrong room, and some that are not marked as shared...
2285        let alice = OlmMachine::new(user_id!("@a:s.co"), device_id!("ALICE")).await;
2286        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("BOB")).await;
2287
2288        let room1_id = room_id!("!room1:localhost");
2289        let room2_id = room_id!("!room2:localhost");
2290
2291        /* We use hardcoded megolm session data, to get a stable output snapshot. These were all created with:
2292
2293           println!("{}", vodozemac::megolm::GroupSession::new(Default::default()).session_key().to_base64());
2294        */
2295        let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw";
2296        let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ";
2297        let session_key3 = "AgAAAAAM9KFsliaUUhGSXgwOzM5UemjkNH4n8NHgvC/y8hhw13zTF+ooGD4uIYEXYX630oNvQm/EvgZo+dkoc0re+vsqsx4sQeNODdSjcBsWOa0oDF+irQn9oYoLUDPI1IBtY1rX+FV99Zm/xnG7uFOX7aTVlko2GSdejy1w9mfobmfxu5aUc04A9zaKJP1pOthZvRAlhpymGYHgsDtWPrrjyc/yypMflE4kIUEEEtu1kT6mrAmcl615XYRAHYK9G2+fZsGvokwzbkl4nulGwcZMpQEoM0nD2o3GWgX81HW3nGfKBg";
2298        let session_key4 = "AgAAAAA4Kkesxq2h4v9PLD6Sm3Smxspz1PXTqytQPCMQMkkrHNmzV2bHlJ+6/Al9cu8vh1Oj69AK0WUAeJOJuaiskEeg/PI3P03+UYLeC379RzgqwSHdBgdQ41G2vD6zpgmE/8vYToe+qpCZACtPOswZxyqxHH+T/Iq0nv13JmlFGIeA6fEPfr5Y28B49viG74Fs9rxV9EH5PfjbuPM/p+Sz5obShuaBPKQBX1jT913nEXPoIJ06exNZGr0285nw/LgVvNlmWmbqNnbzO2cNZjQWA+xZYz5FSfyCxwqEBbEdUCuRCQ";
2299
2300        let sessions = [
2301            create_inbound_group_session_with_visibility(
2302                &alice,
2303                room1_id,
2304                &SessionKey::from_base64(session_key1).unwrap(),
2305                true,
2306            ),
2307            create_inbound_group_session_with_visibility(
2308                &alice,
2309                room1_id,
2310                &SessionKey::from_base64(session_key2).unwrap(),
2311                true,
2312            ),
2313            create_inbound_group_session_with_visibility(
2314                &alice,
2315                room1_id,
2316                &SessionKey::from_base64(session_key3).unwrap(),
2317                false,
2318            ),
2319            create_inbound_group_session_with_visibility(
2320                &alice,
2321                room2_id,
2322                &SessionKey::from_base64(session_key4).unwrap(),
2323                true,
2324            ),
2325        ];
2326        bob.store().save_inbound_group_sessions(&sessions).await.unwrap();
2327
2328        // When I build the bundle
2329        let mut bundle = bob.store().build_room_key_bundle(room1_id).await.unwrap();
2330
2331        // Then the bundle matches the snapshot.
2332
2333        // We sort the sessions in the bundle, so that the snapshot is stable.
2334        bundle.room_keys.sort_by_key(|session| session.session_id.clone());
2335
2336        // We substitute the algorithm, since this changes based on feature flags.
2337        let algorithm = if cfg!(feature = "experimental-algorithms") {
2338            "m.megolm.v2.aes-sha2"
2339        } else {
2340            "m.megolm.v1.aes-sha2"
2341        };
2342        let map_algorithm = move |value: Content, _path: ContentPath<'_>| {
2343            assert_eq!(value.as_str().unwrap(), algorithm);
2344            "[algorithm]"
2345        };
2346
2347        // We also substitute alice's keys in the snapshot with placeholders
2348        let alice_curve_key = alice.identity_keys().curve25519.to_base64();
2349        let map_alice_curve_key = move |value: Content, _path: ContentPath<'_>| {
2350            assert_eq!(value.as_str().unwrap(), alice_curve_key);
2351            "[alice curve key]"
2352        };
2353        let alice_ed25519_key = alice.identity_keys().ed25519.to_base64();
2354        let map_alice_ed25519_key = move |value: Content, _path: ContentPath<'_>| {
2355            assert_eq!(value.as_str().unwrap(), alice_ed25519_key);
2356            "[alice ed25519 key]"
2357        };
2358
2359        insta::with_settings!({ sort_maps => true }, {
2360            assert_json_snapshot!(bundle, {
2361                ".withheld[].algorithm" => insta::dynamic_redaction(map_algorithm),
2362                ".room_keys[].algorithm" => insta::dynamic_redaction(map_algorithm),
2363                ".room_keys[].sender_key" => insta::dynamic_redaction(map_alice_curve_key.clone()),
2364                ".withheld[].sender_key" => insta::dynamic_redaction(map_alice_curve_key),
2365                ".room_keys[].sender_claimed_keys.ed25519" => insta::dynamic_redaction(map_alice_ed25519_key),
2366            });
2367        });
2368    }
2369
2370    #[async_test]
2371    async fn test_receive_room_key_bundle() {
2372        let alice = OlmMachine::new(user_id!("@a:s.co"), device_id!("ALICE")).await;
2373        let alice_key = alice.identity_keys().curve25519;
2374        let bob = OlmMachine::new(user_id!("@b:s.co"), device_id!("BOB")).await;
2375
2376        let room_id = room_id!("!room1:localhost");
2377
2378        let session_key1 = "AgAAAAC2XHVzsMBKs4QCRElJ92CJKyGtknCSC8HY7cQ7UYwndMKLQAejXLh5UA0l6s736mgctcUMNvELScUWrObdflrHo+vth/gWreXOaCnaSxmyjjKErQwyIYTkUfqbHy40RJfEesLwnN23on9XAkch/iy8R2+Jz7B8zfG01f2Ow2SxPQFnAndcO1ZSD2GmXgedy6n4B20MWI1jGP2wiexOWbFSya8DO/VxC9m5+/mF+WwYqdpKn9g4Y05Yw4uz7cdjTc3rXm7xK+8E7hI//5QD1nHPvuKYbjjM9u2JSL+Bzp61Cw";
2379        let session_key2 = "AgAAAAC1BXreFTUQQSBGekTEuYxhdytRKyv4JgDGcG+VOBYdPNGgs807SdibCGJky4lJ3I+7ZDGHoUzZPZP/4ogGu4kxni0PWdtWuN7+5zsuamgoFF/BkaGeUUGv6kgIkx8pyPpM5SASTUEP9bN2loDSpUPYwfiIqz74DgC4WQ4435sTBctYvKz8n+TDJwdLXpyT6zKljuqADAioud+s/iqx9LYn9HpbBfezZcvbg67GtE113pLrvde3IcPI5s6dNHK2onGO2B2eoaobcen18bbEDnlUGPeIivArLya7Da6us14jBQ";
2380
2381        let sessions = [
2382            create_inbound_group_session_with_visibility(
2383                &alice,
2384                room_id,
2385                &SessionKey::from_base64(session_key1).unwrap(),
2386                true,
2387            ),
2388            create_inbound_group_session_with_visibility(
2389                &alice,
2390                room_id,
2391                &SessionKey::from_base64(session_key2).unwrap(),
2392                false,
2393            ),
2394        ];
2395
2396        alice.store().save_inbound_group_sessions(&sessions).await.unwrap();
2397        let bundle = alice.store().build_room_key_bundle(room_id).await.unwrap();
2398
2399        bob.store()
2400            .receive_room_key_bundle(
2401                &StoredRoomKeyBundleData {
2402                    sender_user: alice.user_id().to_owned(),
2403                    sender_key: alice_key,
2404                    sender_data: SenderData::sender_verified(
2405                        alice.user_id(),
2406                        device_id!("ALICE"),
2407                        alice.identity_keys().ed25519,
2408                    ),
2409
2410                    bundle_data: RoomKeyBundleContent {
2411                        room_id: room_id.to_owned(),
2412                        // This isn't used at all in the method call, so we can fill it with
2413                        // garbage.
2414                        file: EncryptedFile::new(
2415                            owned_mxc_uri!("mxc://example.com/0"),
2416                            V2EncryptedFileInfo::encode([0; 32], [0; 16]).into(),
2417                            EncryptedFileHashes::with_sha256([0; 32]),
2418                        ),
2419                    },
2420                },
2421                bundle,
2422                |_, _| {},
2423            )
2424            .await
2425            .unwrap();
2426
2427        // The room key should be imported successfully
2428        let imported_sessions =
2429            bob.store().get_inbound_group_sessions_by_room_id(room_id).await.unwrap();
2430
2431        assert_eq!(imported_sessions.len(), 1);
2432        assert_eq!(imported_sessions[0].room_id(), room_id);
2433
2434        // The session forwarder data should be set correctly.
2435        assert_eq!(
2436            imported_sessions[0]
2437                .forwarder_data
2438                .as_ref()
2439                .expect("Session should contain forwarder data.")
2440                .user_id(),
2441            alice.user_id()
2442        );
2443
2444        assert_matches!(
2445            bob.store()
2446                .get_withheld_info(room_id, sessions[1].session_id())
2447                .await
2448                .unwrap()
2449                .expect("Withheld info should be present in the store."),
2450            RoomKeyWithheldEntry {
2451                #[cfg(not(feature = "experimental-algorithms"))]
2452                content: RoomKeyWithheldContent::MegolmV1AesSha2(
2453                    MegolmV1AesSha2WithheldContent::HistoryNotShared(_)
2454                ),
2455                #[cfg(feature = "experimental-algorithms")]
2456                content: RoomKeyWithheldContent::MegolmV2AesSha2(
2457                    MegolmV1AesSha2WithheldContent::HistoryNotShared(_)
2458                ),
2459                ..
2460            }
2461        );
2462    }
2463
2464    /// Tests that the new store format introduced in [#5737][#5737] does not
2465    /// conflict with items already in the store that were serialised with the
2466    /// older format.
2467    ///
2468    /// [#5737]: https://github.com/matrix-org/matrix-rust-sdk/pull/5737
2469    #[async_test]
2470    async fn test_deserialize_room_key_withheld_entry_from_to_device_event() {
2471        let entry: RoomKeyWithheldEntry = serde_json::from_value(json!(
2472            {
2473              "content": {
2474                "algorithm": "m.megolm.v1.aes-sha2",
2475                "code": "m.unauthorised",
2476                "from_device": "ALICE",
2477                "reason": "You are not authorised to read the message.",
2478                "room_id": "!roomid:s.co",
2479                "sender_key": "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0",
2480                "session_id": "session123"
2481              },
2482              "sender": "@alice:s.co",
2483              "type": "m.room_key.withheld"
2484            }
2485        ))
2486        .unwrap();
2487
2488        assert_matches!(
2489            entry,
2490            RoomKeyWithheldEntry {
2491                sender,
2492                content: RoomKeyWithheldContent::MegolmV1AesSha2(
2493                    MegolmV1AesSha2WithheldContent::Unauthorised(withheld_content,)
2494                ),
2495            }
2496        );
2497
2498        assert_eq!(sender, "@alice:s.co");
2499        assert_eq!(withheld_content.room_id, "!roomid:s.co");
2500        assert_eq!(withheld_content.session_id, "session123");
2501        assert_eq!(
2502            withheld_content.sender_key.to_base64(),
2503            "7hIcOrEroXYdzjtCBvBjUiqvT0Me7g+ymeXqoc65RS0"
2504        );
2505        assert_eq!(withheld_content.from_device, Some(owned_device_id!("ALICE")));
2506    }
2507
2508    /// Create an inbound Megolm session for the given room.
2509    ///
2510    /// `olm_machine` is used to set the `sender_key` and `signing_key`
2511    /// fields of the resultant session.
2512    ///
2513    /// The encryption algorithm used for the session depends on the
2514    /// `experimental-algorithms` feature flag:
2515    ///
2516    /// - When not set, the session uses `m.megolm.v1.aes-sha2`.
2517    /// - When set, the session uses `m.megolm.v2.aes-sha2`.
2518    fn create_inbound_group_session_with_visibility(
2519        olm_machine: &OlmMachine,
2520        room_id: &RoomId,
2521        session_key: &SessionKey,
2522        shared_history: bool,
2523    ) -> InboundGroupSession {
2524        let identity_keys = &olm_machine.store().static_account().identity_keys;
2525        InboundGroupSession::new(
2526            identity_keys.curve25519,
2527            identity_keys.ed25519,
2528            room_id,
2529            session_key,
2530            SenderData::unknown(),
2531            None,
2532            #[cfg(not(feature = "experimental-algorithms"))]
2533            EventEncryptionAlgorithm::MegolmV1AesSha2,
2534            #[cfg(feature = "experimental-algorithms")]
2535            EventEncryptionAlgorithm::MegolmV2AesSha2,
2536            None,
2537            shared_history,
2538        )
2539        .unwrap()
2540    }
2541}