Skip to main content

matrix_sdk/encryption/
mod.rs

1// Copyright 2021 The Matrix.org Foundation C.I.C.
2// Copyright 2021 Damir Jelić
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16#![doc = include_str!("../docs/encryption.md")]
17#![cfg_attr(target_family = "wasm", allow(unused_imports))]
18
19#[cfg(feature = "experimental-send-custom-to-device")]
20use std::{collections::BTreeSet, ops::Deref};
21use std::{
22    collections::{BTreeMap, HashSet},
23    io::{Cursor, Read, Write},
24    iter,
25    path::{Path, PathBuf},
26    str::FromStr,
27    sync::Arc,
28    time::Duration,
29};
30
31#[cfg(feature = "experimental-send-custom-to-device")]
32use as_variant::as_variant;
33use eyeball::{SharedObservable, Subscriber};
34use futures_core::Stream;
35use futures_util::{
36    future::try_join,
37    stream::{self, StreamExt},
38};
39#[cfg(feature = "experimental-send-custom-to-device")]
40use matrix_sdk_base::crypto::CollectStrategy;
41use matrix_sdk_base::{
42    StateStoreDataKey, StateStoreDataValue,
43    cross_process_lock::{AcquireCrossProcessLockFn, CrossProcessLock, CrossProcessLockError},
44    crypto::{
45        CrossSigningBootstrapRequests, OlmMachine,
46        store::{
47            LockableCryptoStore, SecretImportError,
48            types::{RoomKeyBundleInfo, RoomKeyInfo},
49        },
50        types::{
51            SecretsBundle, SignedKey,
52            requests::{
53                OutgoingRequest, OutgoingVerificationRequest, RoomMessageRequest, ToDeviceRequest,
54            },
55        },
56    },
57    sleep::sleep,
58    timeout::timeout,
59};
60use matrix_sdk_common::{executor::spawn, locks::Mutex as StdMutex};
61use ruma::{
62    DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, TransactionId, UserId,
63    api::{
64        client::{
65            keys::{
66                get_keys, upload_keys, upload_signatures::v3::Request as UploadSignaturesRequest,
67                upload_signing_keys::v3::Request as UploadSigningKeysRequest,
68            },
69            message::send_message_event,
70            to_device::send_event_to_device::v3::{
71                Request as RumaToDeviceRequest, Response as ToDeviceResponse,
72            },
73            uiaa::{AuthData, AuthType, OAuthParams, UiaaInfo},
74        },
75        error::{ErrorBody, StandardErrorBody},
76    },
77    assign,
78    events::room::{
79        MediaSource, ThumbnailInfo,
80        member::{MembershipChange, OriginalSyncRoomMemberEvent},
81    },
82};
83#[cfg(feature = "experimental-send-custom-to-device")]
84use ruma::{
85    events::{AnyToDeviceEventContent, ToDeviceEventType},
86    serde::Raw,
87    to_device::DeviceIdOrAllDevices,
88};
89use serde::{Deserialize, de::Error as _};
90use tasks::BundleReceiverTask;
91use tokio::sync::{Mutex, RwLockReadGuard};
92use tokio_stream::wrappers::errors::BroadcastStreamRecvError;
93use tracing::{Instrument, Span, debug, error, instrument, warn};
94use url::Url;
95use vodozemac::Curve25519PublicKey;
96
97use self::{
98    backups::{Backups, types::BackupClientState},
99    futures::UploadEncryptedFile,
100    identities::{Device, DeviceUpdates, IdentityUpdates, UserDevices, UserIdentity},
101    recovery::{Recovery, RecoveryState},
102    secret_storage::SecretStorage,
103    tasks::{BackupDownloadTask, BackupUploadingTask, ClientTasks},
104    verification::{SasVerification, Verification, VerificationRequest},
105};
106use crate::{
107    Client, Error, HttpError, Result, Room, TransmissionProgress,
108    attachment::Thumbnail,
109    client::{ClientInner, WeakClient},
110    cross_process_lock::CrossProcessLockGuard,
111    error::HttpResult,
112};
113
114pub mod backups;
115pub mod dehydrated_devices;
116pub mod futures;
117pub mod identities;
118pub mod recovery;
119pub mod secret_storage;
120pub(crate) mod tasks;
121pub mod verification;
122
123use matrix_sdk_base::crypto::OlmMachineBuilder;
124pub use matrix_sdk_base::crypto::{
125    CrossSigningStatus, CryptoStoreError, DecryptorError, EventError, KeyExportError, LocalTrust,
126    MediaEncryptionInfo, MegolmError, OlmError, RoomKeyImportResult, SessionCreationError,
127    SignatureError, VERSION,
128    olm::{
129        SessionCreationError as MegolmSessionCreationError,
130        SessionExportError as OlmSessionExportError,
131    },
132    vodozemac,
133};
134use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
135
136#[cfg(feature = "experimental-send-custom-to-device")]
137use crate::config::RequestConfig;
138pub use crate::error::RoomKeyImportError;
139
140/// Error type describing failures that can happen while exporting a
141/// [`SecretsBundle`] from a SQLite store.
142#[cfg(feature = "sqlite")]
143#[derive(Debug, thiserror::Error)]
144pub enum BundleExportError {
145    /// The SQLite store couldn't be opened.
146    #[error(transparent)]
147    OpenStoreError(#[from] matrix_sdk_sqlite::OpenStoreError),
148    /// Data from the SQLite store couldn't be exported.
149    #[error(transparent)]
150    StoreError(#[from] CryptoStoreError),
151    /// The store doesn't contain a secrets bundle or it couldn't be read from
152    /// the store.
153    #[error(transparent)]
154    SecretExport(#[from] matrix_sdk_base::crypto::store::SecretsBundleExportError),
155}
156
157/// Error type describing failures that can happen while importing a
158/// [`SecretsBundle`].
159#[derive(Debug, thiserror::Error)]
160pub enum BundleImportError {
161    /// The bundle couldn't be imported.
162    #[error(transparent)]
163    SecretImport(#[from] SecretImportError),
164    /// The cross-signed device keys couldn't been uploaded.
165    #[error(transparent)]
166    DeviceKeys(#[from] Error),
167}
168
169/// Attempt to export a [`SecretsBundle`] from a crypto store.
170///
171/// This method can be used to retrieve a [`SecretsBundle`] from an existing
172/// `matrix-sdk`-based client in order to import the [`SecretsBundle`] in
173/// another [`Client`] instance.
174///
175/// This can be useful for migration purposes or to allow existing client
176/// instances create new ones that will be fully verified.
177#[cfg(feature = "sqlite")]
178pub async fn export_secrets_bundle_from_store(
179    database_path: impl AsRef<Path>,
180    passphrase: Option<&str>,
181) -> std::result::Result<Option<(OwnedUserId, SecretsBundle)>, BundleExportError> {
182    use matrix_sdk_base::crypto::store::CryptoStore;
183
184    let store = matrix_sdk_sqlite::SqliteCryptoStore::open(database_path, passphrase).await?;
185    let account =
186        store.load_account().await.map_err(|e| BundleExportError::StoreError(e.into()))?;
187
188    if let Some(account) = account {
189        let machine = OlmMachineBuilder::new(&account.user_id, &account.device_id)
190            .with_crypto_store(store)
191            .build()
192            .await
193            .map_err(BundleExportError::StoreError)?;
194
195        let bundle = machine.store().export_secrets_bundle().await?;
196
197        Ok(Some((account.user_id.to_owned(), bundle)))
198    } else {
199        Ok(None)
200    }
201}
202
203/// All the data related to the encryption state.
204pub(crate) struct EncryptionData {
205    /// Background tasks related to encryption (key backup, initialization
206    /// tasks, etc.).
207    pub tasks: StdMutex<ClientTasks>,
208
209    /// End-to-end encryption settings.
210    pub encryption_settings: EncryptionSettings,
211
212    /// All state related to key backup.
213    pub backup_state: BackupClientState,
214
215    /// All state related to secret storage recovery.
216    pub recovery_state: SharedObservable<RecoveryState>,
217
218    /// State for the dehydrated-devices manager (event channel, scheduled
219    /// rotation task).
220    pub dehydrated_devices_state: dehydrated_devices::DehydratedDevicesState,
221}
222
223impl EncryptionData {
224    pub fn new(encryption_settings: EncryptionSettings) -> Self {
225        Self {
226            encryption_settings,
227
228            tasks: StdMutex::new(Default::default()),
229            backup_state: Default::default(),
230            recovery_state: Default::default(),
231            dehydrated_devices_state: Default::default(),
232        }
233    }
234
235    pub fn initialize_tasks(&self, client: &Arc<ClientInner>) {
236        let weak_client = WeakClient::from_inner(client);
237
238        let mut tasks = self.tasks.lock();
239        tasks.upload_room_keys = Some(BackupUploadingTask::new(weak_client.clone()));
240
241        if self.encryption_settings.backup_download_strategy
242            == BackupDownloadStrategy::AfterDecryptionFailure
243        {
244            tasks.download_room_keys = Some(BackupDownloadTask::new(weak_client));
245        }
246    }
247
248    /// Initialize the background task which listens for changes in the
249    /// [`backups::BackupState`] and updataes the [`recovery::RecoveryState`].
250    ///
251    /// This should happen after the usual tasks have been set up and after the
252    /// E2EE initialization tasks have been set up.
253    pub fn initialize_recovery_state_update_task(&self, client: &Client) {
254        let mut guard = self.tasks.lock();
255
256        let future = Recovery::update_state_after_backup_state_change(client);
257        let join_handle = spawn(future);
258
259        guard.update_recovery_state_after_backup = Some(join_handle);
260    }
261}
262
263/// Settings for end-to-end encryption features.
264#[derive(Clone, Copy, Debug, Default)]
265pub struct EncryptionSettings {
266    /// Automatically bootstrap cross-signing for a user once they're logged, in
267    /// case it's not already done yet.
268    ///
269    /// This requires to login with a username and password, or that MSC3967 is
270    /// enabled on the server, as of 2023-10-20.
271    pub auto_enable_cross_signing: bool,
272
273    /// Select a strategy to download room keys from the backup, by default room
274    /// keys won't be downloaded from the backup automatically.
275    ///
276    /// Take a look at the [`BackupDownloadStrategy`] enum for more options.
277    pub backup_download_strategy: BackupDownloadStrategy,
278
279    /// Automatically create a backup version if no backup exists.
280    pub auto_enable_backups: bool,
281}
282
283/// Settings for end-to-end encryption features.
284#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
285#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
286pub enum BackupDownloadStrategy {
287    /// Automatically download all room keys from the backup when the backup
288    /// recovery key has been received. The backup recovery key can be received
289    /// in two ways:
290    ///
291    /// 1. Received as a `m.secret.send` to-device event, after a successful
292    ///    interactive verification.
293    /// 2. Imported from secret storage (4S) using the
294    ///    [`SecretStore::import_secrets()`] method.
295    ///
296    /// [`SecretStore::import_secrets()`]: crate::encryption::secret_storage::SecretStore::import_secrets
297    OneShot,
298
299    /// Attempt to download a single room key if an event fails to be decrypted.
300    AfterDecryptionFailure,
301
302    /// Don't download any room keys automatically. The user can manually
303    /// download room keys using the [`Backups::download_room_key()`] methods.
304    ///
305    /// This is the default option.
306    #[default]
307    Manual,
308}
309
310/// The verification state of our own device
311///
312/// This enum tells us if our own user identity trusts these devices, in other
313/// words it tells us if the user identity has signed the device.
314#[derive(Clone, Copy, Debug, Eq, PartialEq)]
315pub enum VerificationState {
316    /// The verification state is unknown for now.
317    Unknown,
318    /// The device is considered to be verified, it has been signed by its user
319    /// identity.
320    Verified,
321    /// The device is unverified.
322    Unverified,
323}
324
325/// A stateful struct remembering the cross-signing keys we need to upload.
326///
327/// Since the `/_matrix/client/v3/keys/device_signing/upload` might require
328/// additional authentication, this struct will contain information on the type
329/// of authentication the user needs to complete before the upload might be
330/// continued.
331///
332/// More info can be found in the [spec].
333///
334/// [spec]: https://spec.matrix.org/v1.11/client-server-api/#post_matrixclientv3keysdevice_signingupload
335#[derive(Debug)]
336pub struct CrossSigningResetHandle {
337    client: Client,
338    upload_request: UploadSigningKeysRequest,
339    signatures_request: UploadSignaturesRequest,
340    auth_type: CrossSigningResetAuthType,
341    is_cancelled: Mutex<bool>,
342}
343
344impl CrossSigningResetHandle {
345    /// Set up a new `CrossSigningResetHandle`.
346    pub fn new(
347        client: Client,
348        upload_request: UploadSigningKeysRequest,
349        signatures_request: UploadSignaturesRequest,
350        auth_type: CrossSigningResetAuthType,
351    ) -> Self {
352        Self {
353            client,
354            upload_request,
355            signatures_request,
356            auth_type,
357            is_cancelled: Mutex::new(false),
358        }
359    }
360
361    /// Get the [`CrossSigningResetAuthType`] this cross-signing reset process
362    /// is using.
363    pub fn auth_type(&self) -> &CrossSigningResetAuthType {
364        &self.auth_type
365    }
366
367    /// Continue the cross-signing reset by either waiting for the
368    /// authentication to be done on the side of the OAuth 2.0 server or by
369    /// providing additional [`AuthData`] the homeserver requires.
370    pub async fn auth(&self, auth: Option<AuthData>) -> Result<()> {
371        // Poll to see whether the reset has been authorized twice per second.
372        const RETRY_EVERY: Duration = Duration::from_millis(500);
373
374        // Give up after two minutes of polling.
375        const TIMEOUT: Duration = Duration::from_mins(2);
376
377        timeout(
378            async {
379                let mut upload_request = self.upload_request.clone();
380                upload_request.auth = auth;
381
382                debug!(
383                    "Repeatedly PUTting to keys/device_signing/upload until it works \
384                    or we hit a permanent failure."
385                );
386                while let Err(e) = self.client.send(upload_request.clone()).await {
387                    if *self.is_cancelled.lock().await {
388                        return Ok(());
389                    }
390
391                    match e.as_uiaa_response() {
392                        Some(uiaa_info) => {
393                            // Return the error except if we are at the `m.oauth` stage where we
394                            // want to keep polling.
395                            if !matches!(self.auth_type, CrossSigningResetAuthType::OAuth(_))
396                                && uiaa_info.auth_error.is_some()
397                            {
398                                return Err(e.into());
399                            }
400                        }
401                        None => return Err(e.into()),
402                    }
403
404                    debug!(
405                        "PUT to keys/device_signing/upload failed with 401. Retrying after \
406                        a short delay."
407                    );
408                    sleep(RETRY_EVERY).await;
409                }
410
411                self.client.send(self.signatures_request.clone()).await?;
412
413                Ok(())
414            },
415            TIMEOUT,
416        )
417        .await
418        .unwrap_or_else(|_| {
419            warn!("Timed out waiting for keys/device_signing/upload to succeed.");
420            Err(Error::Timeout)
421        })
422    }
423
424    /// Cancel the ongoing identity reset process
425    pub async fn cancel(&self) {
426        *self.is_cancelled.lock().await = true;
427    }
428}
429
430/// information about the additional authentication that is required before the
431/// cross-signing keys can be uploaded.
432#[derive(Debug, Clone)]
433pub enum CrossSigningResetAuthType {
434    /// The homeserver requires user-interactive authentication.
435    Uiaa(UiaaInfo),
436    /// OAuth 2.0 is used for authentication and the user needs to open a URL to
437    /// approve the upload of cross-signing keys.
438    OAuth(OAuthCrossSigningResetInfo),
439}
440
441impl CrossSigningResetAuthType {
442    fn new(error: &HttpError) -> Result<Option<Self>> {
443        if let Some(auth_info) = error.as_uiaa_response() {
444            if let Ok(Some(auth_info)) = OAuthCrossSigningResetInfo::from_auth_info(auth_info) {
445                Ok(Some(CrossSigningResetAuthType::OAuth(auth_info)))
446            } else {
447                Ok(Some(CrossSigningResetAuthType::Uiaa(auth_info.clone())))
448            }
449        } else {
450            Ok(None)
451        }
452    }
453}
454
455/// OAuth 2.0 specific information about the required authentication for the
456/// upload of cross-signing keys.
457#[derive(Debug, Clone, Deserialize)]
458pub struct OAuthCrossSigningResetInfo {
459    /// The URL where the user can approve the reset of the cross-signing keys.
460    pub approval_url: Url,
461
462    /// Session key to use to complete the authentication.
463    pub session: Option<String>,
464}
465
466impl OAuthCrossSigningResetInfo {
467    fn from_auth_info(auth_info: &UiaaInfo) -> Result<Option<Self>> {
468        let Some(parameters) = auth_info.params::<OAuthParams>(&AuthType::OAuth)? else {
469            return Ok(None);
470        };
471
472        Ok(Some(OAuthCrossSigningResetInfo {
473            approval_url: parameters.url.as_str().try_into()?,
474            session: auth_info.session.clone(),
475        }))
476    }
477}
478
479/// A struct that helps to parse the custom error message Synapse posts if a
480/// duplicate one-time key is uploaded.
481#[derive(Clone, Debug)]
482pub struct DuplicateOneTimeKeyErrorMessage {
483    /// The previously uploaded one-time key.
484    pub old_key: Curve25519PublicKey,
485    /// The one-time key we're attempting to upload right now.
486    pub new_key: Curve25519PublicKey,
487}
488
489impl FromStr for DuplicateOneTimeKeyErrorMessage {
490    type Err = serde_json::Error;
491
492    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
493        // First we split the string into two parts, the part containing the old key and
494        // the part containing the new key. The parts are conveniently separated
495        // by a `;` character.
496        let mut split = s.split_terminator(';');
497
498        let old_key = split
499            .next()
500            .ok_or(serde_json::Error::custom("Old key is missing in the error message"))?;
501        let new_key = split
502            .next()
503            .ok_or(serde_json::Error::custom("New key is missing in the error message"))?;
504
505        // Now we remove the lengthy prefix from the part containing the old key, we
506        // should be left with just the JSON of the signed key.
507        let old_key_index = old_key
508            .find("Old key:")
509            .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
510
511        let old_key = old_key[old_key_index..]
512            .trim()
513            .strip_prefix("Old key:")
514            .ok_or(serde_json::Error::custom("Old key is missing the prefix"))?;
515
516        // The part containing the new key is much simpler, we just remove a static
517        // prefix.
518        let new_key = new_key
519            .trim()
520            .strip_prefix("new key:")
521            .ok_or(serde_json::Error::custom("New key is missing the prefix"))?;
522
523        // The JSON containing the new key is for some reason quoted using single
524        // quotes, so let's replace them with normal double quotes.
525        let new_key = new_key.replace("'", "\"");
526
527        // Let's deserialize now.
528        let old_key: SignedKey = serde_json::from_str(old_key)?;
529        let new_key: SignedKey = serde_json::from_str(&new_key)?;
530
531        // Pick out the Curve keys, we don't care about the rest that much.
532        let old_key = old_key.key();
533        let new_key = new_key.key();
534
535        Ok(Self { old_key, new_key })
536    }
537}
538
539impl Client {
540    pub(crate) async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
541        self.base_client().olm_machine().await
542    }
543
544    pub(crate) async fn mark_request_as_sent(
545        &self,
546        request_id: &TransactionId,
547        response: impl Into<matrix_sdk_base::crypto::types::requests::AnyIncomingResponse<'_>>,
548    ) -> Result<(), matrix_sdk_base::Error> {
549        Ok(self
550            .olm_machine()
551            .await
552            .as_ref()
553            .expect(
554                "We should have an olm machine once we try to mark E2EE related requests as sent",
555            )
556            .mark_request_as_sent(request_id, response)
557            .await?)
558    }
559
560    /// Query the server for users device keys.
561    ///
562    /// # Panics
563    ///
564    /// Panics if no key query needs to be done.
565    #[instrument(skip(self, device_keys))]
566    pub(crate) async fn keys_query(
567        &self,
568        request_id: &TransactionId,
569        device_keys: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
570    ) -> Result<get_keys::v3::Response> {
571        let request = assign!(get_keys::v3::Request::new(), { device_keys });
572
573        let response = self.send(request).await?;
574        self.mark_request_as_sent(request_id, &response).await?;
575        self.encryption().update_state_after_keys_query(&response).await;
576
577        Ok(response)
578    }
579
580    /// Construct a [`EncryptedFile`][ruma::events::room::EncryptedFile] by
581    /// encrypting and uploading a provided reader.
582    ///
583    /// # Arguments
584    ///
585    /// * `content_type` - The content type of the file.
586    /// * `reader` - The reader that should be encrypted and uploaded.
587    ///
588    /// # Examples
589    ///
590    /// ```no_run
591    /// # use matrix_sdk::Client;
592    /// # use url::Url;
593    /// # use matrix_sdk::ruma::{room_id, OwnedRoomId};
594    /// use serde::{Deserialize, Serialize};
595    /// use matrix_sdk::ruma::events::{macros::EventContent, room::EncryptedFile};
596    ///
597    /// #[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
598    /// #[ruma_event(type = "com.example.custom", kind = MessageLike)]
599    /// struct CustomEventContent {
600    ///     encrypted_file: EncryptedFile,
601    /// }
602    ///
603    /// # async {
604    /// # let homeserver = Url::parse("http://example.com")?;
605    /// # let client = Client::new(homeserver).await?;
606    /// # let room = client.get_room(&room_id!("!test:example.com")).unwrap();
607    /// let mut reader = std::io::Cursor::new(b"Hello, world!");
608    /// let encrypted_file = client.upload_encrypted_file(&mut reader).await?;
609    ///
610    /// room.send(CustomEventContent { encrypted_file }).await?;
611    /// # anyhow::Ok(()) };
612    /// ```
613    pub fn upload_encrypted_file<'a, R: Read + ?Sized + 'a>(
614        &'a self,
615        reader: &'a mut R,
616    ) -> UploadEncryptedFile<'a, R> {
617        UploadEncryptedFile::new(self, reader)
618    }
619
620    /// Encrypt and upload the file and thumbnails, and return the source
621    /// information.
622    pub(crate) async fn upload_encrypted_media_and_thumbnail(
623        &self,
624        data: &[u8],
625        thumbnail: Option<Thumbnail>,
626        send_progress: SharedObservable<TransmissionProgress>,
627    ) -> Result<(MediaSource, Option<(MediaSource, Box<ThumbnailInfo>)>)> {
628        let upload_thumbnail = self.upload_encrypted_thumbnail(thumbnail, send_progress.clone());
629
630        let upload_attachment = async {
631            let mut cursor = Cursor::new(data);
632            self.upload_encrypted_file(&mut cursor)
633                .with_send_progress_observable(send_progress)
634                .await
635        };
636
637        let (thumbnail, file) = try_join(upload_thumbnail, upload_attachment).await?;
638
639        Ok((MediaSource::Encrypted(Box::new(file)), thumbnail))
640    }
641
642    /// Uploads an encrypted thumbnail to the media repository, and returns
643    /// its source and extra information.
644    async fn upload_encrypted_thumbnail(
645        &self,
646        thumbnail: Option<Thumbnail>,
647        send_progress: SharedObservable<TransmissionProgress>,
648    ) -> Result<Option<(MediaSource, Box<ThumbnailInfo>)>> {
649        let Some(thumbnail) = thumbnail else {
650            return Ok(None);
651        };
652
653        let (data, _, thumbnail_info) = thumbnail.into_parts();
654        let mut cursor = Cursor::new(data);
655
656        let file = self
657            .upload_encrypted_file(&mut cursor)
658            .with_send_progress_observable(send_progress)
659            .await?;
660
661        Ok(Some((MediaSource::Encrypted(Box::new(file)), thumbnail_info)))
662    }
663
664    /// Claim one-time keys creating new Olm sessions.
665    ///
666    /// # Arguments
667    ///
668    /// * `users` - The list of user/device pairs that we should claim keys for.
669    pub(crate) async fn claim_one_time_keys(
670        &self,
671        users: impl Iterator<Item = &UserId>,
672    ) -> Result<()> {
673        let _lock = self.locks().key_claim_lock.lock().await;
674
675        if let Some((request_id, request)) = self
676            .olm_machine()
677            .await
678            .as_ref()
679            .ok_or(Error::NoOlmMachine)?
680            .get_missing_sessions(users)
681            .await?
682        {
683            let response = self.send(request).await?;
684            self.mark_request_as_sent(&request_id, &response).await?;
685        }
686
687        Ok(())
688    }
689
690    /// Upload the E2E encryption keys.
691    ///
692    /// This uploads the long lived device keys as well as the required amount
693    /// of one-time keys.
694    ///
695    /// # Panics
696    ///
697    /// Panics if the client isn't logged in, or if no encryption keys need to
698    /// be uploaded.
699    #[instrument(skip(self, request))]
700    pub(crate) async fn keys_upload(
701        &self,
702        request_id: &TransactionId,
703        request: &upload_keys::v3::Request,
704    ) -> Result<upload_keys::v3::Response> {
705        debug!(
706            device_keys = request.device_keys.is_some(),
707            one_time_key_count = request.one_time_keys.len(),
708            "Uploading public encryption keys",
709        );
710
711        let response = self.send(request.clone()).await?;
712        self.mark_request_as_sent(request_id, &response).await?;
713
714        Ok(response)
715    }
716
717    pub(crate) async fn room_send_helper(
718        &self,
719        request: &RoomMessageRequest,
720    ) -> Result<send_message_event::v3::Response> {
721        let content = request.content.clone();
722        let txn_id = request.txn_id.clone();
723        let room_id = &request.room_id;
724
725        self.get_room(room_id)
726            .expect("Can't send a message to a room that isn't known to the store")
727            .send(*content)
728            .with_transaction_id(txn_id)
729            .await
730            .map(|result| result.response)
731    }
732
733    pub(crate) async fn send_to_device(
734        &self,
735        request: &ToDeviceRequest,
736    ) -> HttpResult<ToDeviceResponse> {
737        let request = RumaToDeviceRequest::new_raw(
738            request.event_type.clone(),
739            request.txn_id.clone(),
740            request.messages.clone(),
741        );
742
743        self.send(request).await
744    }
745
746    pub(crate) async fn send_verification_request(
747        &self,
748        request: OutgoingVerificationRequest,
749    ) -> Result<()> {
750        use matrix_sdk_base::crypto::types::requests::OutgoingVerificationRequest::*;
751
752        match request {
753            ToDevice(t) => {
754                self.send_to_device(&t).await?;
755            }
756            InRoom(r) => {
757                self.room_send_helper(&r).await?;
758            }
759        }
760
761        Ok(())
762    }
763
764    async fn send_outgoing_request(&self, r: OutgoingRequest) -> Result<()> {
765        use matrix_sdk_base::crypto::types::requests::AnyOutgoingRequest;
766
767        match r.request() {
768            AnyOutgoingRequest::KeysQuery(request) => {
769                self.keys_query(r.request_id(), request.device_keys.clone()).await?;
770            }
771            AnyOutgoingRequest::KeysUpload(request) => {
772                let response = self.keys_upload(r.request_id(), request).await;
773
774                if let Err(e) = &response {
775                    match e.as_client_api_error() {
776                        Some(e) if e.status_code == 400 => {
777                            if let ErrorBody::Standard(StandardErrorBody { message, .. }) = &e.body
778                            {
779                                // This is one of the nastiest errors we can have. The server
780                                // telling us that we already have a one-time key uploaded means
781                                // that we forgot about some of our one-time keys. This will lead to
782                                // UTDs.
783                                {
784                                    let already_reported = self
785                                        .state_store()
786                                        .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
787                                        .await?
788                                        .is_some();
789
790                                    if message.starts_with("One time key") && !already_reported {
791                                        let error_message =
792                                            DuplicateOneTimeKeyErrorMessage::from_str(message);
793
794                                        if let Ok(message) = &error_message {
795                                            error!(
796                                                sentry = true,
797                                                old_key = %message.old_key,
798                                                new_key = %message.new_key,
799                                                "Duplicate one-time keys have been uploaded"
800                                            );
801                                        } else {
802                                            error!(
803                                                sentry = true,
804                                                "Duplicate one-time keys have been uploaded"
805                                            );
806                                        }
807
808                                        self.state_store()
809                                            .set_kv_data(
810                                                StateStoreDataKey::OneTimeKeyAlreadyUploaded,
811                                                StateStoreDataValue::OneTimeKeyAlreadyUploaded,
812                                            )
813                                            .await?;
814
815                                        if let Err(e) = self
816                                            .inner
817                                            .duplicate_key_upload_error_sender
818                                            .send(error_message.ok())
819                                        {
820                                            error!(
821                                                "Failed to dispatch duplicate key upload error notification: {}",
822                                                e
823                                            );
824                                        }
825                                    }
826                                }
827                            }
828                        }
829                        _ => {}
830                    }
831
832                    response?;
833                }
834            }
835            AnyOutgoingRequest::ToDeviceRequest(request) => {
836                let response = self.send_to_device(request).await?;
837                self.mark_request_as_sent(r.request_id(), &response).await?;
838            }
839            AnyOutgoingRequest::SignatureUpload(request) => {
840                let response = self.send(request.clone()).await?;
841                self.mark_request_as_sent(r.request_id(), &response).await?;
842            }
843            AnyOutgoingRequest::RoomMessage(request) => {
844                let response = self.room_send_helper(request).await?;
845                self.mark_request_as_sent(r.request_id(), &response).await?;
846            }
847            AnyOutgoingRequest::KeysClaim(request) => {
848                let response = self.send(request.clone()).await?;
849                self.mark_request_as_sent(r.request_id(), &response).await?;
850            }
851        }
852
853        Ok(())
854    }
855
856    #[instrument(skip_all)]
857    pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
858        const MAX_CONCURRENT_REQUESTS: usize = 20;
859
860        // This is needed because sometimes we need to automatically
861        // claim some one-time keys to unwedge an existing Olm session.
862        if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
863            warn!("Error while claiming one-time keys {:?}", e);
864        }
865
866        let outgoing_requests = stream::iter(
867            self.olm_machine()
868                .await
869                .as_ref()
870                .ok_or(Error::NoOlmMachine)?
871                .outgoing_requests()
872                .await?,
873        )
874        .map(|r| self.send_outgoing_request(r));
875
876        let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
877
878        requests
879            .for_each(|r| async move {
880                match r {
881                    Ok(_) => (),
882                    Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
883                }
884            })
885            .await;
886
887        Ok(())
888    }
889}
890
891#[cfg(feature = "experimental-send-custom-to-device")]
892impl Client {
893    /// Olm-encrypt a raw to-device message and send it to a set of recipient
894    /// devices.
895    ///
896    /// If there are a lot of recipient devices multiple `/sendToDevice`
897    /// requests might be sent out.
898    ///
899    /// The content is always encrypted.
900    ///
901    /// # Arguments
902    ///
903    /// * `event_type` - The type of the to-device event to send.
904    ///
905    /// * `recipients` - The devices to send the message to, as a `user id ->
906    ///   device ids` map. [`DeviceIdOrAllDevices::AllDevices`] targets every
907    ///   device of that user we know about.
908    ///
909    /// * `content` - The content of the to-device event, encrypted for and sent
910    ///   to every recipient.
911    ///
912    /// # Returns
913    ///
914    /// The devices that did *not* receive the message, as a `user id -> device
915    /// ids` map. A device can end up in there because it is unknown to us,
916    /// because the sharing strategy excluded it, or because encrypting for it
917    /// or sending to it failed. An empty map means every recipient was served.
918    ///
919    /// [`send_event_to_device`]: ruma::api::client::to_device::send_event_to_device
920    pub async fn send_encrypted_to_device(
921        &self,
922        event_type: &ToDeviceEventType,
923        recipients: BTreeMap<OwnedUserId, Vec<DeviceIdOrAllDevices>>,
924        content: Raw<AnyToDeviceEventContent>,
925    ) -> Result<BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>> {
926        let mut failures: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> = BTreeMap::new();
927        let mut recipient_devices = Vec::<_>::new();
928
929        // Find out the actual user devices from their name/wildcard
930        for (user_id, recipient_device_ids) in recipients {
931            let (devices, unknown_devices) =
932                self.resolve_recipient_devices(&user_id, recipient_device_ids).await?;
933            recipient_devices.extend(devices);
934            if !unknown_devices.is_empty() {
935                failures.insert(user_id, unknown_devices);
936            }
937        }
938
939        if !recipient_devices.is_empty() {
940            let encrypt_and_send_failures = self
941                .encryption()
942                .encrypt_and_send_raw_to_device(
943                    recipient_devices.iter().collect(),
944                    &event_type.to_string(),
945                    content,
946                    CollectStrategy::AllDevices,
947                )
948                .await?;
949
950            for (user_id, device_id) in encrypt_and_send_failures {
951                failures.entry(user_id).or_default().push(device_id)
952            }
953        }
954
955        Ok(failures)
956    }
957
958    /// Resolve the devices ([`Device`]) of a single user from a list of
959    /// DeviceIdOrAllDevices.
960    ///
961    /// # Returns
962    ///
963    /// A tuple of the [`Device`]s we know about and should send to, and the
964    /// device IDs that were explicitly requested but are unknown to us.
965    /// [`DeviceIdOrAllDevices::AllDevices`] never yields unknown devices.
966    async fn resolve_recipient_devices(
967        &self,
968        user_id: &UserId,
969        recipient_device_ids: Vec<DeviceIdOrAllDevices>,
970    ) -> Result<(Vec<Device>, Vec<OwnedDeviceId>)> {
971        let user_devices = self.encryption().get_user_devices(user_id).await?;
972
973        if recipient_device_ids.contains(&DeviceIdOrAllDevices::AllDevices) {
974            // If the user wants to send to all devices, there's nothing to filter and no
975            // need to inspect other entries in the user's device list.
976            let devices: Vec<_> = user_devices.devices().collect();
977
978            if devices.is_empty() {
979                warn!(
980                    "Recipient list contains `AllDevices` but no devices found for user {user_id}."
981                );
982            }
983            if recipient_device_ids.len() > 1 {
984                warn!(
985                    "The recipient_device_ids list for {user_id} contains both `AllDevices` and explicit `DeviceId` entries. Only consider `AllDevices`",
986                );
987            }
988
989            Ok((devices, Vec::new()))
990        } else {
991            // If the user wants to send to only some devices, filter out any devices that
992            // aren't part of the recipient_device_ids list.
993            let (found_device_ids, devices): (BTreeSet<_>, Vec<_>) = user_devices
994                .devices()
995                .map(|device| (device.device_id().to_owned(), device))
996                .filter(|(device_id, _)| {
997                    recipient_device_ids
998                        .contains(&DeviceIdOrAllDevices::DeviceId(device_id.clone()))
999                })
1000                .unzip();
1001
1002            let requested_device_ids: BTreeSet<_> = recipient_device_ids
1003                .into_iter()
1004                .filter_map(|d| as_variant!(d, DeviceIdOrAllDevices::DeviceId))
1005                .collect();
1006
1007            // Let's now find any devices that are part of the recipient_device_ids list but
1008            // were not found in our store.
1009            let missing_devices =
1010                requested_device_ids.difference(&found_device_ids).map(ToOwned::to_owned).collect();
1011
1012            Ok((devices, missing_devices))
1013        }
1014    }
1015}
1016
1017#[cfg(any(feature = "testing", test))]
1018impl Client {
1019    /// Get the olm machine, for testing purposes only.
1020    pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1021        self.olm_machine().await
1022    }
1023
1024    /// Aborts the client's bundle receiver task, for testing purposes only.
1025    pub fn abort_bundle_receiver_task(&self) {
1026        let tasks = self.inner.e2ee.tasks.lock();
1027        if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
1028            task.abort()
1029        }
1030    }
1031}
1032
1033/// A high-level API to manage the client's encryption.
1034///
1035/// To get this, use [`Client::encryption()`].
1036#[derive(Debug, Clone)]
1037pub struct Encryption {
1038    /// The underlying client.
1039    client: Client,
1040}
1041
1042impl Encryption {
1043    pub(crate) fn new(client: Client) -> Self {
1044        Self { client }
1045    }
1046
1047    /// Returns the current encryption settings for this client.
1048    pub(crate) fn settings(&self) -> EncryptionSettings {
1049        self.client.inner.e2ee.encryption_settings
1050    }
1051
1052    /// Get the public ed25519 key of our own device. This is usually what is
1053    /// called the fingerprint of the device.
1054    pub async fn ed25519_key(&self) -> Option<String> {
1055        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
1056    }
1057
1058    /// Get the public Curve25519 key of our own device.
1059    pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
1060        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
1061    }
1062
1063    /// Get the current device creation timestamp.
1064    pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
1065        match self.get_own_device().await {
1066            Ok(Some(device)) => device.first_time_seen_ts(),
1067            // Should not happen, there should always be an own device
1068            _ => MilliSecondsSinceUnixEpoch::now(),
1069        }
1070    }
1071
1072    /// This method will import all the private cross-signing keys and, if
1073    /// available, the private part of a backup key and its accompanying
1074    /// version into the store.
1075    ///
1076    /// Importing all the secrets will mark the device as verified and enable
1077    /// backups if a backup key was available in the bundle.
1078    ///
1079    /// **Warning**: Only import this from a trusted source, i.e. if an existing
1080    /// device is sharing this with a new device.
1081    ///
1082    /// **Warning*: Only call this method right after logging in and before the
1083    /// initial sync has been started.
1084    pub async fn import_secrets_bundle(
1085        &self,
1086        bundle: &SecretsBundle,
1087    ) -> Result<(), BundleImportError> {
1088        self.import_secrets_bundle_impl(bundle).await?;
1089
1090        // Upload the device keys, this will ensure that other devices see us as a fully
1091        // verified device as soon as this method returns.
1092        self.ensure_device_keys_upload().await?;
1093        self.wait_for_e2ee_initialization_tasks().await;
1094
1095        // If our initialization tasks completed before we imported the secrets bundle,
1096        // backups might not have been enabled.
1097        //
1098        // In this case attempt to enable them again.
1099        if !self.backups().are_enabled().await {
1100            self.backups().maybe_resume_backups().await?;
1101        }
1102
1103        Ok(())
1104    }
1105
1106    pub(crate) async fn import_secrets_bundle_impl(
1107        &self,
1108        bundle: &SecretsBundle,
1109    ) -> Result<(), SecretImportError> {
1110        let olm_machine = self.client.olm_machine().await;
1111        let olm_machine =
1112            olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
1113
1114        olm_machine.store().import_secrets_bundle(bundle).await
1115    }
1116
1117    /// Get the status of the private cross signing keys.
1118    ///
1119    /// This can be used to check which private cross signing keys we have
1120    /// stored locally.
1121    pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
1122        let olm = self.client.olm_machine().await;
1123        let machine = olm.as_ref()?;
1124        Some(machine.cross_signing_status().await)
1125    }
1126
1127    /// Does the user have other devices that the current device can verify
1128    /// against?
1129    ///
1130    /// The device must be signed by the user's cross-signing key, must have an
1131    /// identity, and must not be a dehydrated device.
1132    pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1133        let olm_machine = self.client.olm_machine().await;
1134        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1135        let user_id = olm_machine.user_id();
1136
1137        self.ensure_initial_key_query().await?;
1138
1139        let devices = self.get_user_devices(user_id).await?;
1140
1141        let ret = devices.devices().any(|device| {
1142            device.is_cross_signed_by_owner()
1143                && device.curve25519_key().is_some()
1144                && !device.is_dehydrated()
1145        });
1146
1147        Ok(ret)
1148    }
1149
1150    /// Get all the tracked users we know about
1151    ///
1152    /// Tracked users are users for which we keep the device list of E2EE
1153    /// capable devices up to date.
1154    pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1155        if let Some(machine) = self.client.olm_machine().await.as_ref() {
1156            machine.tracked_users().await
1157        } else {
1158            Ok(HashSet::new())
1159        }
1160    }
1161
1162    /// Get a [`Subscriber`] for the [`VerificationState`].
1163    ///
1164    /// # Examples
1165    ///
1166    /// ```no_run
1167    /// use matrix_sdk::{Client, encryption};
1168    /// use url::Url;
1169    ///
1170    /// # async {
1171    /// let homeserver = Url::parse("http://example.com")?;
1172    /// let client = Client::new(homeserver).await?;
1173    /// let mut subscriber = client.encryption().verification_state();
1174    ///
1175    /// let current_value = subscriber.get();
1176    ///
1177    /// println!("The current verification state is: {current_value:?}");
1178    ///
1179    /// if let Some(verification_state) = subscriber.next().await {
1180    ///     println!("Received verification state update {:?}", verification_state)
1181    /// }
1182    /// # anyhow::Ok(()) };
1183    /// ```
1184    pub fn verification_state(&self) -> Subscriber<VerificationState> {
1185        self.client.inner.verification_state.subscribe_reset()
1186    }
1187
1188    /// Get a verification object with the given flow id.
1189    pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1190        let olm = self.client.olm_machine().await;
1191        let olm = olm.as_ref()?;
1192        #[allow(clippy::bind_instead_of_map)]
1193        olm.get_verification(user_id, flow_id).and_then(|v| match v {
1194            matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1195                Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1196            }
1197            #[cfg(feature = "qrcode")]
1198            matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1199                Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1200            }
1201            _ => None,
1202        })
1203    }
1204
1205    /// Get a `VerificationRequest` object for the given user with the given
1206    /// flow id.
1207    pub async fn get_verification_request(
1208        &self,
1209        user_id: &UserId,
1210        flow_id: impl AsRef<str>,
1211    ) -> Option<VerificationRequest> {
1212        let olm = self.client.olm_machine().await;
1213        let olm = olm.as_ref()?;
1214
1215        olm.get_verification_request(user_id, flow_id)
1216            .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1217    }
1218
1219    /// Get a specific device of a user.
1220    ///
1221    /// # Arguments
1222    ///
1223    /// * `user_id` - The unique id of the user that the device belongs to.
1224    ///
1225    /// * `device_id` - The unique id of the device.
1226    ///
1227    /// Returns a `Device` if one is found and the crypto store didn't throw an
1228    /// error.
1229    ///
1230    /// This will always return None if the client hasn't been logged in.
1231    ///
1232    /// # Examples
1233    ///
1234    /// ```no_run
1235    /// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
1236    /// # use url::Url;
1237    /// # async {
1238    /// # let alice = user_id!("@alice:example.org");
1239    /// # let homeserver = Url::parse("http://example.com")?;
1240    /// # let client = Client::new(homeserver).await?;
1241    /// if let Some(device) =
1242    ///     client.encryption().get_device(alice, device_id!("DEVICEID")).await?
1243    /// {
1244    ///     println!("{:?}", device.is_verified());
1245    ///
1246    ///     if !device.is_verified() {
1247    ///         let verification = device.request_verification().await?;
1248    ///     }
1249    /// }
1250    /// # anyhow::Ok(()) };
1251    /// ```
1252    pub async fn get_device(
1253        &self,
1254        user_id: &UserId,
1255        device_id: &DeviceId,
1256    ) -> Result<Option<Device>, CryptoStoreError> {
1257        let olm = self.client.olm_machine().await;
1258        let Some(machine) = olm.as_ref() else { return Ok(None) };
1259        let device = machine.get_device(user_id, device_id, None).await?;
1260        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1261    }
1262
1263    /// A convenience method to retrieve your own device from the store.
1264    ///
1265    /// This is the same as calling [`Encryption::get_device()`] with your own
1266    /// user and device ID.
1267    ///
1268    /// This will always return a device, unless you are not logged in.
1269    pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1270        let olm = self.client.olm_machine().await;
1271        let Some(machine) = olm.as_ref() else { return Ok(None) };
1272        let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1273        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1274    }
1275
1276    /// Get a map holding all the devices of an user.
1277    ///
1278    /// This will always return an empty map if the client hasn't been logged
1279    /// in.
1280    ///
1281    /// # Arguments
1282    ///
1283    /// * `user_id` - The unique id of the user that the devices belong to.
1284    ///
1285    /// # Examples
1286    ///
1287    /// ```no_run
1288    /// # use matrix_sdk::{Client, ruma::user_id};
1289    /// # use url::Url;
1290    /// # async {
1291    /// # let alice = user_id!("@alice:example.org");
1292    /// # let homeserver = Url::parse("http://example.com")?;
1293    /// # let client = Client::new(homeserver).await?;
1294    /// let devices = client.encryption().get_user_devices(alice).await?;
1295    ///
1296    /// for device in devices.devices() {
1297    ///     println!("{device:?}");
1298    /// }
1299    /// # anyhow::Ok(()) };
1300    /// ```
1301    pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1302        let devices = self
1303            .client
1304            .olm_machine()
1305            .await
1306            .as_ref()
1307            .ok_or(Error::NoOlmMachine)?
1308            .get_user_devices(user_id, None)
1309            .await?;
1310
1311        Ok(UserDevices { inner: devices, client: self.client.clone() })
1312    }
1313
1314    /// Get the E2EE identity of a user from the crypto store.
1315    ///
1316    /// Usually, we only have the E2EE identity of a user locally if the user
1317    /// is tracked, meaning that we are both members of the same encrypted room.
1318    ///
1319    /// To get the E2EE identity of a user even if it is not available locally
1320    /// use [`Encryption::request_user_identity()`].
1321    ///
1322    /// # Arguments
1323    ///
1324    /// * `user_id` - The unique id of the user that the identity belongs to.
1325    ///
1326    /// Returns a `UserIdentity` if one is found and the crypto store
1327    /// didn't throw an error.
1328    ///
1329    /// This will always return None if the client hasn't been logged in.
1330    ///
1331    /// # Examples
1332    ///
1333    /// ```no_run
1334    /// # use matrix_sdk::{Client, ruma::user_id};
1335    /// # use url::Url;
1336    /// # async {
1337    /// # let alice = user_id!("@alice:example.org");
1338    /// # let homeserver = Url::parse("http://example.com")?;
1339    /// # let client = Client::new(homeserver).await?;
1340    /// let user = client.encryption().get_user_identity(alice).await?;
1341    ///
1342    /// if let Some(user) = user {
1343    ///     println!("{:?}", user.is_verified());
1344    ///
1345    ///     let verification = user.request_verification().await?;
1346    /// }
1347    /// # anyhow::Ok(()) };
1348    /// ```
1349    pub async fn get_user_identity(
1350        &self,
1351        user_id: &UserId,
1352    ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1353        let olm = self.client.olm_machine().await;
1354        let Some(olm) = olm.as_ref() else { return Ok(None) };
1355        let identity = olm.get_identity(user_id, None).await?;
1356
1357        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1358    }
1359
1360    /// Get the E2EE identity of a user from the homeserver.
1361    ///
1362    /// The E2EE identity returned is always guaranteed to be up-to-date. If the
1363    /// E2EE identity is not found, it should mean that the user did not set
1364    /// up cross-signing.
1365    ///
1366    /// If you want the E2EE identity of a user without making a request to the
1367    /// homeserver, use [`Encryption::get_user_identity()`] instead.
1368    ///
1369    /// # Arguments
1370    ///
1371    /// * `user_id` - The ID of the user that the identity belongs to.
1372    ///
1373    /// Returns a [`UserIdentity`] if one is found. Returns an error if there
1374    /// was an issue with the crypto store or with the request to the
1375    /// homeserver.
1376    ///
1377    /// This will always return `None` if the client hasn't been logged in.
1378    ///
1379    /// # Examples
1380    ///
1381    /// ```no_run
1382    /// # use matrix_sdk::{Client, ruma::user_id};
1383    /// # use url::Url;
1384    /// # async {
1385    /// # let alice = user_id!("@alice:example.org");
1386    /// # let homeserver = Url::parse("http://example.com")?;
1387    /// # let client = Client::new(homeserver).await?;
1388    /// let user = client.encryption().request_user_identity(alice).await?;
1389    ///
1390    /// if let Some(user) = user {
1391    ///     println!("User is verified: {:?}", user.is_verified());
1392    ///
1393    ///     let verification = user.request_verification().await?;
1394    /// }
1395    /// # anyhow::Ok(()) };
1396    /// ```
1397    pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1398        let olm = self.client.olm_machine().await;
1399        let Some(olm) = olm.as_ref() else { return Ok(None) };
1400
1401        let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1402        self.client.keys_query(&request_id, request.device_keys).await?;
1403
1404        let identity = olm.get_identity(user_id, None).await?;
1405        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1406    }
1407
1408    /// Returns a stream of device updates, allowing users to listen for
1409    /// notifications about new or changed devices.
1410    ///
1411    /// The stream produced by this method emits updates whenever a new device
1412    /// is discovered or when an existing device's information is changed. Users
1413    /// can subscribe to this stream and receive updates in real-time.
1414    ///
1415    /// # Examples
1416    ///
1417    /// ```no_run
1418    /// # use matrix_sdk::Client;
1419    /// # use ruma::{device_id, user_id};
1420    /// # use futures_util::{pin_mut, StreamExt};
1421    /// # let client: Client = unimplemented!();
1422    /// # async {
1423    /// let devices_stream = client.encryption().devices_stream().await?;
1424    /// let user_id = client
1425    ///     .user_id()
1426    ///     .expect("We should know our user id after we have logged in");
1427    /// pin_mut!(devices_stream);
1428    ///
1429    /// for device_updates in devices_stream.next().await {
1430    ///     if let Some(user_devices) = device_updates.new.get(user_id) {
1431    ///         for device in user_devices.values() {
1432    ///             println!("A new device has been added {}", device.device_id());
1433    ///         }
1434    ///     }
1435    /// }
1436    /// # anyhow::Ok(()) };
1437    /// ```
1438    pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1439        let olm = self.client.olm_machine().await;
1440        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1441        let client = self.client.to_owned();
1442
1443        Ok(olm
1444            .store()
1445            .devices_stream()
1446            .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1447    }
1448
1449    /// Returns a stream of user identity updates, allowing users to listen for
1450    /// notifications about new or changed user identities.
1451    ///
1452    /// The stream produced by this method emits updates whenever a new user
1453    /// identity is discovered or when an existing identities information is
1454    /// changed. Users can subscribe to this stream and receive updates in
1455    /// real-time.
1456    ///
1457    /// # Examples
1458    ///
1459    /// ```no_run
1460    /// # use matrix_sdk::Client;
1461    /// # use ruma::{device_id, user_id};
1462    /// # use futures_util::{pin_mut, StreamExt};
1463    /// # let client: Client = unimplemented!();
1464    /// # async {
1465    /// let identities_stream =
1466    ///     client.encryption().user_identities_stream().await?;
1467    /// pin_mut!(identities_stream);
1468    ///
1469    /// for identity_updates in identities_stream.next().await {
1470    ///     for (_, identity) in identity_updates.new {
1471    ///         println!("A new identity has been added {}", identity.user_id());
1472    ///     }
1473    /// }
1474    /// # anyhow::Ok(()) };
1475    /// ```
1476    pub async fn user_identities_stream(
1477        &self,
1478    ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1479        let olm = self.client.olm_machine().await;
1480        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1481        let client = self.client.to_owned();
1482
1483        Ok(olm
1484            .store()
1485            .user_identities_stream()
1486            .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1487    }
1488
1489    /// Create and upload a new cross signing identity.
1490    ///
1491    /// # Arguments
1492    ///
1493    /// * `auth_data` - This request requires user interactive auth, the first
1494    ///   request needs to set this to `None` and will always fail with an
1495    ///   `UiaaResponse`. The response will contain information for the
1496    ///   interactive auth and the same request needs to be made but this time
1497    ///   with some `auth_data` provided.
1498    ///
1499    /// # Examples
1500    ///
1501    /// ```no_run
1502    /// # use std::collections::BTreeMap;
1503    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1504    /// # use url::Url;
1505    /// # use serde_json::json;
1506    /// # async {
1507    /// # let homeserver = Url::parse("http://example.com")?;
1508    /// # let client = Client::new(homeserver).await?;
1509    /// if let Err(e) = client.encryption().bootstrap_cross_signing(None).await {
1510    ///     if let Some(response) = e.as_uiaa_response() {
1511    ///         let mut password = uiaa::Password::new(
1512    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new("example".to_owned())),
1513    ///             "wordpass".to_owned(),
1514    ///         );
1515    ///         password.session = response.session.clone();
1516    ///
1517    ///         client
1518    ///             .encryption()
1519    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(password)))
1520    ///             .await
1521    ///             .expect("Couldn't bootstrap cross signing")
1522    ///     } else {
1523    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1524    ///     }
1525    /// }
1526    /// # anyhow::Ok(()) };
1527    pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1528        let olm = self.client.olm_machine().await;
1529        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1530
1531        let CrossSigningBootstrapRequests {
1532            upload_signing_keys_req,
1533            upload_keys_req,
1534            upload_signatures_req,
1535        } = olm.bootstrap_cross_signing(false).await?;
1536
1537        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1538            auth: auth_data,
1539            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1540            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1541            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1542        });
1543
1544        if let Some(req) = upload_keys_req {
1545            self.client.send_outgoing_request(req).await?;
1546        }
1547        self.client.send(upload_signing_keys_req).await?;
1548        self.client.send(upload_signatures_req).await?;
1549
1550        Ok(())
1551    }
1552
1553    /// Reset the cross-signing keys.
1554    ///
1555    /// # Example
1556    ///
1557    /// ```no_run
1558    /// use matrix_sdk::{ruma::api::client::uiaa, encryption::CrossSigningResetAuthType};
1559    ///
1560    /// # async {
1561    /// # let homeserver = url::Url::parse("http://example.com")?;
1562    /// # let client = matrix_sdk::Client::new(homeserver).await?;
1563    /// # let user_id = unimplemented!();
1564    /// let encryption = client.encryption();
1565    ///
1566    /// if let Some(handle) = encryption.reset_cross_signing().await? {
1567    ///     match handle.auth_type() {
1568    ///         CrossSigningResetAuthType::Uiaa(uiaa) => {
1569    ///             use matrix_sdk::ruma::api::client::uiaa;
1570    ///
1571    ///             let password = "1234".to_owned();
1572    ///             let mut password = uiaa::Password::new(user_id, password);
1573    ///             password.session = uiaa.session;
1574    ///
1575    ///             handle.auth(Some(uiaa::AuthData::Password(password))).await?;
1576    ///         }
1577    ///         CrossSigningResetAuthType::OAuth(o) => {
1578    ///             println!(
1579    ///                 "To reset your end-to-end encryption cross-signing identity, \
1580    ///                 you first need to approve it at {}",
1581    ///                 o.approval_url
1582    ///             );
1583    ///
1584    ///             let mut oauth = uiaa::OAuth::new();
1585    ///             oauth.session = o.session;
1586    ///
1587    ///             handle.auth(Some(uiaa::AuthData::OAuth(oauth))).await?;
1588    ///         }
1589    ///     }
1590    /// }
1591    /// # anyhow::Ok(()) };
1592    /// ```
1593    pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1594        let olm = self.client.olm_machine().await;
1595        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1596
1597        let CrossSigningBootstrapRequests {
1598            upload_keys_req,
1599            upload_signing_keys_req,
1600            upload_signatures_req,
1601        } = olm.bootstrap_cross_signing(true).await?;
1602
1603        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1604            auth: None,
1605            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1606            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1607            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1608        });
1609
1610        if let Some(req) = upload_keys_req {
1611            self.client.send_outgoing_request(req).await?;
1612        }
1613
1614        if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1615            if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1616                let client = self.client.clone();
1617
1618                Ok(Some(CrossSigningResetHandle::new(
1619                    client,
1620                    upload_signing_keys_req,
1621                    upload_signatures_req,
1622                    auth_type,
1623                )))
1624            } else {
1625                Err(error.into())
1626            }
1627        } else {
1628            self.client.send(upload_signatures_req).await?;
1629
1630            Ok(None)
1631        }
1632    }
1633
1634    /// Query the user's own device keys, if, and only if, we didn't have their
1635    /// identity in the first place.
1636    async fn ensure_initial_key_query(&self) -> Result<()> {
1637        let olm_machine = self.client.olm_machine().await;
1638        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1639
1640        let user_id = olm_machine.user_id();
1641
1642        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1643            let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1644            self.client.keys_query(&request_id, request.device_keys).await?;
1645        }
1646
1647        Ok(())
1648    }
1649
1650    /// Create and upload a new cross signing identity, if that has not been
1651    /// done yet.
1652    ///
1653    /// This will only create a new cross-signing identity if the user had never
1654    /// done it before. If the user did it before, then this is a no-op.
1655    ///
1656    /// See also the documentation of [`Self::bootstrap_cross_signing`] for the
1657    /// behavior of this function.
1658    ///
1659    /// # Arguments
1660    ///
1661    /// * `auth_data` - This request requires user interactive auth, the first
1662    ///   request needs to set this to `None` and will always fail with an
1663    ///   `UiaaResponse`. The response will contain information for the
1664    ///   interactive auth and the same request needs to be made but this time
1665    ///   with some `auth_data` provided.
1666    ///
1667    /// # Examples
1668    /// ```no_run
1669    /// # use std::collections::BTreeMap;
1670    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1671    /// # use url::Url;
1672    /// # use serde_json::json;
1673    /// # async {
1674    /// # let homeserver = Url::parse("http://example.com")?;
1675    /// # let client = Client::new(homeserver).await?;
1676    /// if let Err(e) = client.encryption().bootstrap_cross_signing_if_needed(None).await {
1677    ///     if let Some(response) = e.as_uiaa_response() {
1678    ///         let mut password = uiaa::Password::new(
1679    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new("example".to_owned())),
1680    ///             "wordpass".to_owned(),
1681    ///         );
1682    ///         password.session = response.session.clone();
1683    ///
1684    ///         // Note, on the failed attempt we can use `bootstrap_cross_signing` immediately, to
1685    ///         // avoid checks.
1686    ///         client
1687    ///             .encryption()
1688    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(password)))
1689    ///             .await
1690    ///             .expect("Couldn't bootstrap cross signing")
1691    ///     } else {
1692    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1693    ///     }
1694    /// }
1695    /// # anyhow::Ok(()) };
1696    pub async fn bootstrap_cross_signing_if_needed(
1697        &self,
1698        auth_data: Option<AuthData>,
1699    ) -> Result<()> {
1700        let olm_machine = self.client.olm_machine().await;
1701        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1702        let user_id = olm_machine.user_id();
1703
1704        self.ensure_initial_key_query().await?;
1705
1706        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1707            self.bootstrap_cross_signing(auth_data).await?;
1708        }
1709
1710        Ok(())
1711    }
1712
1713    /// Export E2EE keys that match the given predicate encrypting them with the
1714    /// given passphrase.
1715    ///
1716    /// # Arguments
1717    ///
1718    /// * `path` - The file path where the exported key file will be saved.
1719    ///
1720    /// * `passphrase` - The passphrase that will be used to encrypt the
1721    ///   exported room keys.
1722    ///
1723    /// * `predicate` - A closure that will be called for every known
1724    ///   `InboundGroupSession`, which represents a room key. If the closure
1725    ///   returns `true` the `InboundGroupSessoin` will be included in the
1726    ///   export, if the closure returns `false` it will not be included.
1727    ///
1728    /// # Panics
1729    ///
1730    /// This method will panic if it isn't run on a Tokio runtime.
1731    ///
1732    /// This method will panic if it can't get enough randomness from the OS to
1733    /// encrypt the exported keys securely.
1734    ///
1735    /// # Examples
1736    ///
1737    /// ```no_run
1738    /// # use std::{path::PathBuf, time::Duration};
1739    /// # use matrix_sdk::{
1740    /// #     Client, config::SyncSettings,
1741    /// #     ruma::room_id,
1742    /// # };
1743    /// # use url::Url;
1744    /// # async {
1745    /// # let homeserver = Url::parse("http://localhost:8080")?;
1746    /// # let mut client = Client::new(homeserver).await?;
1747    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1748    /// // Export all room keys.
1749    /// client
1750    ///     .encryption()
1751    ///     .export_room_keys(path, "secret-passphrase", |_| true)
1752    ///     .await?;
1753    ///
1754    /// // Export only the room keys for a certain room.
1755    /// let path = PathBuf::from("/home/example/e2e-room-keys.txt");
1756    /// let room_id = room_id!("!test:localhost");
1757    ///
1758    /// client
1759    ///     .encryption()
1760    ///     .export_room_keys(path, "secret-passphrase", |s| s.room_id() == room_id)
1761    ///     .await?;
1762    /// # anyhow::Ok(()) };
1763    /// ```
1764    #[cfg(not(target_family = "wasm"))]
1765    pub async fn export_room_keys(
1766        &self,
1767        path: PathBuf,
1768        passphrase: &str,
1769        predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1770    ) -> Result<()> {
1771        let olm = self.client.olm_machine().await;
1772        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1773
1774        let keys = olm.store().export_room_keys(predicate).await?;
1775        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1776
1777        let encrypt = move || -> Result<()> {
1778            let export: String =
1779                matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1780            let mut file = std::fs::File::create(path)?;
1781            file.write_all(&export.into_bytes())?;
1782            Ok(())
1783        };
1784
1785        let task = tokio::task::spawn_blocking(encrypt);
1786        task.await.expect("Task join error")
1787    }
1788
1789    /// Import E2EE keys from the given file path.
1790    ///
1791    /// # Arguments
1792    ///
1793    /// * `path` - The file path where the exported key file will can be found.
1794    ///
1795    /// * `passphrase` - The passphrase that should be used to decrypt the
1796    ///   exported room keys.
1797    ///
1798    /// Returns a tuple of numbers that represent the number of sessions that
1799    /// were imported and the total number of sessions that were found in the
1800    /// key export.
1801    ///
1802    /// # Panics
1803    ///
1804    /// This method will panic if it isn't run on a Tokio runtime.
1805    ///
1806    /// ```no_run
1807    /// # use std::{path::PathBuf, time::Duration};
1808    /// # use matrix_sdk::{
1809    /// #     Client, config::SyncSettings,
1810    /// #     ruma::room_id,
1811    /// # };
1812    /// # use url::Url;
1813    /// # async {
1814    /// # let homeserver = Url::parse("http://localhost:8080")?;
1815    /// # let mut client = Client::new(homeserver).await?;
1816    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1817    /// let result =
1818    ///     client.encryption().import_room_keys(path, "secret-passphrase").await?;
1819    ///
1820    /// println!(
1821    ///     "Imported {} room keys out of {}",
1822    ///     result.imported_count, result.total_count
1823    /// );
1824    /// # anyhow::Ok(()) };
1825    /// ```
1826    #[cfg(not(target_family = "wasm"))]
1827    pub async fn import_room_keys(
1828        &self,
1829        path: PathBuf,
1830        passphrase: &str,
1831    ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1832        let olm = self.client.olm_machine().await;
1833        let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1834        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1835
1836        let decrypt = move || {
1837            let file = std::fs::File::open(path)?;
1838            matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1839        };
1840
1841        let task = tokio::task::spawn_blocking(decrypt);
1842        let import = task.await.expect("Task join error")?;
1843
1844        let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1845
1846        self.backups().maybe_trigger_backup();
1847
1848        Ok(ret)
1849    }
1850
1851    /// Receive notifications of room keys being received as a [`Stream`].
1852    ///
1853    /// Each time a room key is updated in any way, an update will be sent to
1854    /// the stream. Updates that happen at the same time are batched into a
1855    /// [`Vec`].
1856    ///
1857    /// If the reader of the stream lags too far behind, an error is broadcast
1858    /// containing the number of skipped items.
1859    ///
1860    /// # Examples
1861    ///
1862    /// ```no_run
1863    /// # use matrix_sdk::Client;
1864    /// # use url::Url;
1865    /// # async {
1866    /// # let homeserver = Url::parse("http://example.com")?;
1867    /// # let client = Client::new(homeserver).await?;
1868    /// use futures_util::StreamExt;
1869    ///
1870    /// let Some(mut room_keys_stream) =
1871    ///     client.encryption().room_keys_received_stream().await
1872    /// else {
1873    ///     return Ok(());
1874    /// };
1875    ///
1876    /// while let Some(update) = room_keys_stream.next().await {
1877    ///     println!("Received room keys {update:?}");
1878    /// }
1879    /// # anyhow::Ok(()) };
1880    /// ```
1881    pub async fn room_keys_received_stream(
1882        &self,
1883    ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1884    {
1885        let olm = self.client.olm_machine().await;
1886        let olm = olm.as_ref()?;
1887
1888        Some(olm.store().room_keys_received_stream())
1889    }
1890
1891    /// Receive notifications of historic room key bundles as a [`Stream`].
1892    ///
1893    /// Historic room key bundles are defined in [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
1894    ///
1895    /// Each time a historic room key bundle was received, an update will be
1896    /// sent to the stream. This stream is useful for informative purposes
1897    /// exclusively, historic room key bundles are handled by the SDK
1898    /// automatically.
1899    ///
1900    /// # Examples
1901    ///
1902    /// ```no_run
1903    /// # use matrix_sdk::Client;
1904    /// # use url::Url;
1905    /// # async {
1906    /// # let homeserver = Url::parse("http://example.com")?;
1907    /// # let client = Client::new(homeserver).await?;
1908    /// use futures_util::StreamExt;
1909    ///
1910    /// let Some(mut bundle_stream) =
1911    ///     client.encryption().historic_room_key_stream().await
1912    /// else {
1913    ///     return Ok(());
1914    /// };
1915    ///
1916    /// while let Some(bundle_info) = bundle_stream.next().await {
1917    ///     println!("Received a historic room key bundle {bundle_info:?}");
1918    /// }
1919    /// # anyhow::Ok(()) };
1920    /// ```
1921    pub async fn historic_room_key_stream(
1922        &self,
1923    ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1924        let olm = self.client.olm_machine().await;
1925        let olm = olm.as_ref()?;
1926
1927        Some(olm.store().historic_room_key_stream())
1928    }
1929
1930    /// Get the secret storage manager of the client.
1931    pub fn secret_storage(&self) -> SecretStorage {
1932        SecretStorage { client: self.client.to_owned() }
1933    }
1934
1935    /// Get the backups manager of the client.
1936    pub fn backups(&self) -> Backups {
1937        Backups { client: self.client.to_owned() }
1938    }
1939
1940    /// Get the recovery manager of the client.
1941    pub fn recovery(&self) -> Recovery {
1942        Recovery { client: self.client.to_owned() }
1943    }
1944
1945    /// Get the dehydrated-devices manager of the client.
1946    ///
1947    /// A dehydrated device is a virtual device that the homeserver holds on
1948    /// the user's behalf and that can receive end-to-end encrypted to-device
1949    /// events while the user is offline. See the
1950    /// [`dehydrated_devices`] module
1951    /// for the full lifecycle and an example.
1952    pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
1953        dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
1954    }
1955
1956    /// Enables the crypto-store cross-process lock.
1957    ///
1958    /// This may be required if there are multiple processes that may do writes
1959    /// to the same crypto store. In that case, it's necessary to create a
1960    /// lock, so that only one process writes to it, otherwise this may
1961    /// cause confusing issues because of stale data contained in in-memory
1962    /// caches.
1963    ///
1964    /// The provided `lock_value` must be a unique identifier for this process.
1965    /// Use [`Client::cross_process_lock_config`] to get the global value, if
1966    /// multi-process is enabled.
1967    pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1968        // If the lock has already been created, don't recreate it from scratch.
1969        if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1970            let prev_holder = prev_lock.lock_holder();
1971            if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
1972                return Ok(());
1973            }
1974            warn!(
1975                "Recreating cross-process store lock with a different holder value: \
1976                 prev was {prev_holder:?}, new is {lock_value}"
1977            );
1978        }
1979
1980        let olm_machine = self.client.base_client().olm_machine().await;
1981        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1982
1983        let lock = olm_machine.store().create_store_lock(
1984            "cross_process_lock".to_owned(),
1985            CrossProcessLockConfig::multi_process(lock_value.to_owned()),
1986        );
1987
1988        // Gently try to initialize the crypto store generation counter.
1989        //
1990        // If we don't get the lock immediately, then it is already acquired by another
1991        // process, and we'll get to reload next time we acquire the lock.
1992        {
1993            let lock_result = lock.try_lock_once().await?;
1994
1995            if lock_result.is_ok() {
1996                olm_machine
1997                    .initialize_crypto_store_generation(
1998                        &self.client.locks().crypto_store_generation,
1999                    )
2000                    .await?;
2001            }
2002        }
2003
2004        self.client
2005            .locks()
2006            .cross_process_crypto_store_lock
2007            .set(lock)
2008            .map_err(|_| Error::BadCryptoStoreState)?;
2009
2010        Ok(())
2011    }
2012
2013    /// Maybe reload the `OlmMachine` after acquiring the lock for the first
2014    /// time.
2015    ///
2016    /// Returns the current generation number.
2017    #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
2018    async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
2019        let olm_machine_guard = self.client.olm_machine().await;
2020        if let Some(olm_machine) = olm_machine_guard.as_ref() {
2021            let (new_gen, generation_number) = olm_machine
2022                .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
2023                .await?;
2024
2025            Span::current()
2026                .record("olm_machine_new_generation", new_gen)
2027                .record("olm_machine_generation", generation_number);
2028            debug!("OlmMachine generation maintained in CryptoStore");
2029
2030            // If the crypto store generation has changed,
2031            if new_gen {
2032                // (get rid of the reference to the current crypto store first)
2033                drop(olm_machine_guard);
2034                // Recreate the OlmMachine.
2035                self.client.base_client().regenerate_olm(None).await?;
2036            }
2037            Ok(generation_number)
2038        } else {
2039            // XXX: not sure this is reachable. Seems like the OlmMachine should always have
2040            // been initialised by the time we get here. Ideally we'd panic, or return an
2041            // error, but for now I'm just adding some logging to check if it
2042            // happens, and returning the magic number 0.
2043            warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
2044            Ok(0)
2045        }
2046    }
2047
2048    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
2049    /// spin-waits until the lock is available.
2050    ///
2051    /// May reload the `OlmMachine`, after obtaining the lock but not on the
2052    /// first time.
2053    ///
2054    /// Returns a guard to the lock, if it was obtained.
2055    pub async fn spin_lock_store(
2056        &self,
2057        max_backoff: Option<u32>,
2058    ) -> Result<Option<CrossProcessLockGuard>, Error> {
2059        self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
2060    }
2061
2062    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
2063    /// attempts to lock it once.
2064    ///
2065    /// May reload the `OlmMachine`, after obtaining the lock but not on the
2066    /// first time.
2067    ///
2068    /// Returns a guard to the lock, if it was obtained.
2069    pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
2070        match self.lock_store(CrossProcessLock::try_lock_once).await {
2071            Err(Error::CrossProcessLockError(e))
2072                if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
2073            {
2074                Ok(None)
2075            }
2076            other => other,
2077        }
2078    }
2079
2080    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
2081    /// locks the store with the given function, `acquire`.
2082    ///
2083    /// Reloads the `OlmMachine` after obtaining the lock, if the lock is dirty.
2084    ///
2085    /// Returns a guard to the lock if it was obtained.
2086    pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
2087        &self,
2088        acquire: F,
2089    ) -> Result<Option<CrossProcessLockGuard>, Error> {
2090        let wrap_err = |e: CryptoStoreError| {
2091            Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
2092        };
2093        if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2094            let guard = acquire(lock).await.map_err(wrap_err)??;
2095            let _ = self.on_lock_newly_acquired().await?;
2096            Ok(Some(guard.into_guard()))
2097        } else {
2098            Ok(None)
2099        }
2100    }
2101
2102    /// Testing purposes only.
2103    #[cfg(any(test, feature = "testing"))]
2104    pub async fn uploaded_key_count(&self) -> Result<u64> {
2105        let olm_machine = self.client.olm_machine().await;
2106        let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
2107        Ok(olm_machine.uploaded_key_count().await?)
2108    }
2109
2110    /// Bootstrap encryption and enables event listeners for the E2EE support.
2111    ///
2112    /// Based on the `EncryptionSettings`, this call might:
2113    /// - Bootstrap cross-signing if needed (POST `/device_signing/upload`)
2114    /// - Create a key backup if needed (POST `/room_keys/version`)
2115    /// - Create a secret storage if needed (PUT `/account_data/{type}`)
2116    ///
2117    /// As part of this process, and if needed, the current device keys would be
2118    /// uploaded to the server, new account data would be added, and cross
2119    /// signing keys and signatures might be uploaded.
2120    ///
2121    /// Should be called once we
2122    /// created a [`OlmMachine`], i.e. after logging in.
2123    ///
2124    /// # Arguments
2125    ///
2126    /// * `auth_data` - Some requests may require re-authentication. To prevent
2127    ///   the user from having to re-enter their password (or use other
2128    ///   methods), we can provide the authentication data here. This is
2129    ///   necessary for uploading cross-signing keys. However, please note that
2130    ///   there is a proposal (MSC3967) to remove this requirement, which would
2131    ///   allow for the initial upload of cross-signing keys without
2132    ///   authentication, rendering this parameter obsolete.
2133    pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2134        // It's fine to be async here as we're only getting the lock protecting the
2135        // `OlmMachine`. Since the lock shouldn't be that contested right after logging
2136        // in we won't delay the login or restoration of the Client.
2137        let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2138            Some(BundleReceiverTask::new(&self.client).await)
2139        } else {
2140            None
2141        };
2142
2143        let mut tasks = self.client.inner.e2ee.tasks.lock();
2144
2145        let this = self.clone();
2146
2147        tasks.setup_e2ee = Some(spawn(
2148            async move {
2149                // Update the current state first, so we don't have to wait for the result of
2150                // network requests
2151                this.update_verification_state().await;
2152
2153                if this.settings().auto_enable_cross_signing
2154                    && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2155                {
2156                    error!("Couldn't bootstrap cross signing {e:?}");
2157                }
2158
2159                if let Err(e) = this.backups().setup_and_resume().await {
2160                    error!("Couldn't setup and resume backups {e:?}");
2161                }
2162                if let Err(e) = this.recovery().setup().await {
2163                    error!("Couldn't setup and resume recovery {e:?}");
2164                }
2165            }
2166            .instrument(Span::current()),
2167        ));
2168
2169        tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2170
2171        self.setup_room_membership_session_discard_handler();
2172    }
2173
2174    /// Waits for end-to-end encryption initialization tasks to finish, if any
2175    /// was running in the background.
2176    pub async fn wait_for_e2ee_initialization_tasks(&self) {
2177        let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2178
2179        if let Some(task) = task
2180            && let Err(err) = task.await
2181        {
2182            warn!("Error when initializing backups: {err}");
2183        }
2184    }
2185
2186    /// Upload the device keys and initial set of one-time keys to the server.
2187    ///
2188    /// This should only be called when the user logs in for the first time,
2189    /// the method will ensure that other devices see our own device as an
2190    /// end-to-end encryption enabled one.
2191    ///
2192    /// **Warning**: Do not use this method if we're already calling
2193    /// [`Client::send_outgoing_request()`]. This method is intended for
2194    /// explicitly uploading the device keys before starting a sync.
2195    pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2196        let olm = self.client.olm_machine().await;
2197        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2198
2199        if let Some((request_id, request)) = olm.upload_device_keys().await? {
2200            self.client.keys_upload(&request_id, &request).await?;
2201
2202            let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2203            self.client.keys_query(&request_id, request.device_keys).await?;
2204        }
2205
2206        Ok(())
2207    }
2208
2209    pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2210        self.recovery().update_state_after_keys_query(response).await;
2211
2212        // Only update the verification_state if our own devices changed
2213        if let Some(user_id) = self.client.user_id() {
2214            let contains_own_device = response.device_keys.contains_key(user_id);
2215
2216            if contains_own_device {
2217                self.update_verification_state().await;
2218            }
2219        }
2220    }
2221
2222    async fn update_verification_state(&self) {
2223        match self.get_own_device().await {
2224            Ok(device) => {
2225                if let Some(device) = device {
2226                    let is_verified = device.is_cross_signed_by_owner();
2227
2228                    if is_verified {
2229                        self.client.inner.verification_state.set(VerificationState::Verified);
2230                    } else {
2231                        self.client.inner.verification_state.set(VerificationState::Unverified);
2232                    }
2233                } else {
2234                    warn!("Couldn't find out own device in the store.");
2235                    self.client.inner.verification_state.set(VerificationState::Unknown);
2236                }
2237            }
2238            Err(error) => {
2239                warn!("Failed retrieving own device: {error}");
2240                self.client.inner.verification_state.set(VerificationState::Unknown);
2241            }
2242        }
2243    }
2244
2245    /// Sets up a handler to rotate room keys when a user leaves a room.
2246    ///
2247    /// Previously, it was sufficient to check if we need to rotate the room key
2248    /// prior to sending a message. However, the history sharing feature
2249    /// ([MSC4268]) breaks this logic:
2250    ///
2251    /// 1. Alice sends a message M1 in room X;
2252    /// 2. Bob invites Charlie, who joins and immediately leaves the room;
2253    /// 3. Alice sends another message M2 in room X.
2254    ///
2255    /// Under the old logic, Alice would not rotate her key after Charlie
2256    /// leaves, resulting in M2 being encrypted with the same session as M1.
2257    /// This would allow Charlie to decrypt M2 if he ever gains access to
2258    /// the event.
2259    ///
2260    /// This handler listens for changes to the room membership, and discards
2261    /// the current room key if the event is a `leave` event.
2262    ///
2263    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
2264    fn setup_room_membership_session_discard_handler(&self) {
2265        let client = WeakClient::from_client(&self.client);
2266        self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2267            let Some(client) = client.get() else {
2268                // The main client has been dropped.
2269                return;
2270            };
2271            let Some(user_id) = client.user_id() else {
2272                // We aren't logged in, so this shouldn't ever happen.
2273                return;
2274            };
2275            let olm = client.olm_machine().await;
2276            let Some(olm) = olm.as_ref() else {
2277                warn!("Cannot discard session - Olm machine is not available");
2278                return;
2279            };
2280
2281            if matches!(
2282                ev.membership_change(),
2283                MembershipChange::Joined |
2284                MembershipChange::Invited |
2285                MembershipChange::KnockAccepted |
2286                MembershipChange::InvitationAccepted |
2287                MembershipChange::ProfileChanged { .. }
2288            ) || ev.sender == user_id {
2289                // We can ignore events that did not remove us, and those that we sent.
2290                return;
2291            }
2292
2293            debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2294
2295            // Attempt to discard the current room key. This won't do anything if we don't have one,
2296            // but that's fine since we will create a new room key whenever we try to send a message.
2297            if let Err(e) = olm.discard_room_key(room.room_id()).await {
2298                warn!(
2299                    room_id = ?room.room_id(),
2300                    "Error discarding room key after member leave: {e:?}"
2301                );
2302            }
2303        });
2304    }
2305
2306    /// Encrypts then send the given content via the `/sendToDevice` end-point
2307    /// using Olm encryption.
2308    ///
2309    /// If there are a lot of recipient devices multiple `/sendToDevice`
2310    /// requests might be sent out.
2311    ///
2312    /// # Returns
2313    /// A list of failures. The list of devices that couldn't get the messages.
2314    #[cfg(feature = "experimental-send-custom-to-device")]
2315    pub async fn encrypt_and_send_raw_to_device(
2316        &self,
2317        recipient_devices: Vec<&Device>,
2318        event_type: &str,
2319        content: Raw<AnyToDeviceEventContent>,
2320        share_strategy: CollectStrategy,
2321    ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2322        let users = recipient_devices.iter().map(|device| device.user_id());
2323
2324        // Will claim one-time-key for users that needs it
2325        // TODO: For later optimisation: This will establish missing olm sessions with
2326        // all this users devices, but we just want for some devices.
2327        self.client.claim_one_time_keys(users).await?;
2328
2329        let olm = self.client.olm_machine().await;
2330        let olm = olm.as_ref().expect("Olm machine wasn't started");
2331
2332        let (requests, withhelds) = olm
2333            .encrypt_content_for_devices(
2334                recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2335                event_type,
2336                &content
2337                    .deserialize_as::<serde_json::Value>()
2338                    .expect("Deserialize as Value will always work"),
2339                share_strategy,
2340            )
2341            .await?;
2342
2343        let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2344
2345        // Push the withhelds in the failures
2346        withhelds.iter().for_each(|(d, _)| {
2347            failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2348        });
2349
2350        // TODO: parallelize that? it's already grouping 250 devices per chunk.
2351        for request in requests {
2352            let ruma_request = RumaToDeviceRequest::new_raw(
2353                request.event_type.clone(),
2354                request.txn_id.clone(),
2355                request.messages.clone(),
2356            );
2357
2358            let send_result = self
2359                .client
2360                .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2361                .await;
2362
2363            // If the sending failed we need to collect the failures to report them
2364            if send_result.is_err() {
2365                // Mark the sending as failed
2366                for (user_id, device_map) in request.messages {
2367                    for device_id in device_map.keys() {
2368                        match device_id {
2369                            DeviceIdOrAllDevices::DeviceId(device_id) => {
2370                                failures.push((user_id.clone(), device_id.to_owned()));
2371                            }
2372                            DeviceIdOrAllDevices::AllDevices => {
2373                                // Cannot happen in this case
2374                            }
2375                        }
2376                    }
2377                }
2378            }
2379        }
2380
2381        Ok(failures)
2382    }
2383}
2384
2385#[cfg(all(test, not(target_family = "wasm")))]
2386mod tests {
2387    use std::{
2388        ops::Not,
2389        str::FromStr,
2390        sync::{
2391            Arc,
2392            atomic::{AtomicBool, Ordering},
2393        },
2394    };
2395
2396    use matrix_sdk_test::{
2397        DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2398        event_factory::EventFactory,
2399    };
2400    use ruma::{
2401        event_id,
2402        events::{reaction::ReactionEventContent, relation::Annotation},
2403        user_id,
2404    };
2405    use serde_json::json;
2406    use wiremock::{
2407        Mock, MockServer, Request, ResponseTemplate,
2408        matchers::{header, method, path_regex},
2409    };
2410
2411    use crate::{
2412        Client, assert_next_matches_with_timeout,
2413        config::RequestConfig,
2414        encryption::{
2415            DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2416        },
2417        test_utils::{
2418            client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2419        },
2420    };
2421
2422    #[async_test]
2423    async fn test_reaction_sending() {
2424        let server = MockServer::start().await;
2425        let client = logged_in_client(Some(server.uri())).await;
2426
2427        let event_id = event_id!("$2:example.org");
2428
2429        Mock::given(method("GET"))
2430            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2431            .and(header("authorization", "Bearer 1234"))
2432            .respond_with(
2433                ResponseTemplate::new(200)
2434                    .set_body_json(EventFactory::new().room_encryption().into_content()),
2435            )
2436            .mount(&server)
2437            .await;
2438
2439        Mock::given(method("PUT"))
2440            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2441            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2442                "event_id": event_id,
2443            })))
2444            .mount(&server)
2445            .await;
2446
2447        let f = EventFactory::new().sender(user_id!("@example:localhost"));
2448        let response = SyncResponseBuilder::default()
2449            .add_joined_room(
2450                JoinedRoomBuilder::default()
2451                    .add_state_event(
2452                        f.member(user_id!("@example:localhost")).display_name("example"),
2453                    )
2454                    .add_state_event(f.default_power_levels())
2455                    .add_state_event(f.room_encryption()),
2456            )
2457            .build_sync_response();
2458
2459        client.base_client().receive_sync_response(response).await.unwrap();
2460
2461        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2462        assert!(
2463            room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2464        );
2465
2466        let event_id = event_id!("$1:example.org");
2467        let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2468        room.send(reaction).await.expect("Sending the reaction should not fail");
2469
2470        room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2471    }
2472
2473    #[cfg(feature = "sqlite")]
2474    #[async_test]
2475    async fn test_generation_counter_invalidates_olm_machine() {
2476        // Create two clients using the same sqlite database.
2477
2478        use matrix_sdk_base::store::RoomLoadSettings;
2479        let tmp_dir = tempfile::tempdir().unwrap();
2480        let sqlite_path = tmp_dir.path().join("generation_counter_sqlite.db");
2481        let session = mock_matrix_session();
2482
2483        let client1 = Client::builder()
2484            .homeserver_url("http://localhost:1234")
2485            .request_config(RequestConfig::new().disable_retry())
2486            .sqlite_store(&sqlite_path, None)
2487            .build()
2488            .await
2489            .unwrap();
2490        client1
2491            .matrix_auth()
2492            .restore_session(session.clone(), RoomLoadSettings::default())
2493            .await
2494            .unwrap();
2495
2496        let client2 = Client::builder()
2497            .homeserver_url("http://localhost:1234")
2498            .request_config(RequestConfig::new().disable_retry())
2499            .sqlite_store(sqlite_path, None)
2500            .build()
2501            .await
2502            .unwrap();
2503        client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2504
2505        // When the lock isn't enabled, any attempt at locking won't return a guard.
2506        let guard = client1.encryption().try_lock_store_once().await.unwrap();
2507        assert!(guard.is_none());
2508
2509        client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2510        client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2511
2512        // One client can take the lock.
2513        let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2514        assert!(acquired1.is_some());
2515
2516        // Keep the olm machine, so we can see if it's changed later, by comparing Arcs.
2517        let initial_olm_machine =
2518            client1.olm_machine().await.clone().expect("must have an olm machine");
2519
2520        // Also enable backup to check that new machine has the same backup keys.
2521        let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2522        let backup_key = decryption_key.megolm_v1_public_key();
2523        backup_key.set_version("1".to_owned());
2524        initial_olm_machine
2525            .backup_machine()
2526            .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2527            .await
2528            .expect("Should save");
2529
2530        initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2531
2532        assert!(client1.encryption().backups().are_enabled().await);
2533
2534        // The other client can't take the lock too.
2535        let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2536        assert!(acquired2.is_none());
2537
2538        // Now have the first client release the lock,
2539        drop(acquired1);
2540
2541        // And re-take it.
2542        let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2543        assert!(acquired1.is_some());
2544
2545        // In that case, the Olm Machine shouldn't change.
2546        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2547        assert!(initial_olm_machine.same_as(&olm_machine));
2548
2549        // Ok, release again.
2550        drop(acquired1);
2551
2552        // Client2 can acquire the lock.
2553        let acquired2 = client2.encryption().spin_lock_store(None).await.unwrap();
2554        assert!(acquired2.is_some());
2555
2556        // And then release it.
2557        drop(acquired2);
2558
2559        // Client1 can acquire it again,
2560        let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2561        assert!(acquired1.is_some());
2562
2563        // But now its olm machine has been invalidated and thus regenerated!
2564        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2565
2566        assert!(!initial_olm_machine.same_as(&olm_machine));
2567
2568        let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2569        assert!(backup_key_new.decryption_key.is_some());
2570        assert_eq!(
2571            backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2572            backup_key.to_base64()
2573        );
2574        assert!(client1.encryption().backups().are_enabled().await);
2575    }
2576
2577    #[cfg(feature = "sqlite")]
2578    #[async_test]
2579    async fn test_generation_counter_no_spurious_invalidation() {
2580        // Create two clients using the same sqlite database.
2581
2582        use matrix_sdk_base::store::RoomLoadSettings;
2583        let tmp_dir = tempfile::tempdir().unwrap();
2584        let sqlite_path = tmp_dir.path().join("generation_counter_no_spurious_invalidations.db");
2585        let session = mock_matrix_session();
2586
2587        let client = Client::builder()
2588            .homeserver_url("http://localhost:1234")
2589            .request_config(RequestConfig::new().disable_retry())
2590            .sqlite_store(&sqlite_path, None)
2591            .build()
2592            .await
2593            .unwrap();
2594        client
2595            .matrix_auth()
2596            .restore_session(session.clone(), RoomLoadSettings::default())
2597            .await
2598            .unwrap();
2599
2600        let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2601
2602        client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2603
2604        // Enabling the lock doesn't update the olm machine.
2605        let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2606        assert!(initial_olm_machine.same_as(&after_enabling_lock));
2607
2608        {
2609            // Simulate that another client hold the lock before.
2610            let client2 = Client::builder()
2611                .homeserver_url("http://localhost:1234")
2612                .request_config(RequestConfig::new().disable_retry())
2613                .sqlite_store(sqlite_path, None)
2614                .build()
2615                .await
2616                .unwrap();
2617            client2
2618                .matrix_auth()
2619                .restore_session(session, RoomLoadSettings::default())
2620                .await
2621                .unwrap();
2622
2623            client2
2624                .encryption()
2625                .enable_cross_process_store_lock("client2".to_owned())
2626                .await
2627                .unwrap();
2628
2629            let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2630            assert!(guard.is_some());
2631
2632            drop(guard);
2633        }
2634
2635        {
2636            let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2637            assert!(acquired.is_some());
2638        }
2639
2640        // Taking the lock the first time will update the olm machine.
2641        let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2642        assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2643
2644        {
2645            let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2646            assert!(acquired.is_some());
2647        }
2648
2649        // Re-taking the lock doesn't update the olm machine.
2650        let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2651        assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2652    }
2653
2654    #[async_test]
2655    async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2656        // Given a client and a server
2657        let client = no_retry_test_client(None).await;
2658        let server = MockServer::start().await;
2659
2660        // When we subscribe to its verification state
2661        let mut verification_state = client.encryption().verification_state();
2662
2663        // We can get its initial value, and it's Unknown
2664        assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2665
2666        // We set up a mocked request to check this endpoint is not called before
2667        // reading the new state
2668        let keys_requested = Arc::new(AtomicBool::new(false));
2669        let inner_bool = keys_requested.clone();
2670
2671        Mock::given(method("GET"))
2672            .and(path_regex(
2673                r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2674            ))
2675            .respond_with(move |_req: &Request| {
2676                inner_bool.fetch_or(true, Ordering::SeqCst);
2677                ResponseTemplate::new(200).set_body_json(json!({}))
2678            })
2679            .mount(&server)
2680            .await;
2681
2682        // When the session is initialised and the encryption tasks spawn
2683        set_client_session(&client).await;
2684
2685        // Then we can get an updated value without waiting for any network requests
2686        assert!(keys_requested.load(Ordering::SeqCst).not());
2687        assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2688    }
2689
2690    #[test]
2691    fn test_oauth_reset_info_from_uiaa_info() {
2692        let auth_info = json!({
2693            "session": "dummy",
2694            "flows": [
2695                {
2696                    "stages": [
2697                        "org.matrix.cross_signing_reset"
2698                    ]
2699                }
2700            ],
2701            "params": {
2702                "org.matrix.cross_signing_reset": {
2703                    "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2704                }
2705            },
2706            "msg": "To reset..."
2707        });
2708
2709        let auth_info = serde_json::from_value(auth_info)
2710            .expect("We should be able to deserialize the UiaaInfo");
2711        OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2712            .expect("We should be able to fetch the cross-signing reset info from the auth info");
2713    }
2714
2715    #[test]
2716    fn test_duplicate_one_time_key_error_parsing() {
2717        let message = concat!(
2718            r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2719            r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2720            r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2721            r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2722            r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2723            r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2724            r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2725        );
2726        let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2727            .expect("We should be able to parse the error message");
2728
2729        assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2730        assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2731
2732        DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2733            .expect_err("We shouldn't be able to parse an incomplete error message");
2734    }
2735
2736    // Helper function for the test_devices_to_verify_against_* tests.  Make a
2737    // response to a /keys/query request using the given device keys and a
2738    // pre-defined set of cross-signing keys.
2739    fn devices_to_verify_against_keys_query_response(
2740        devices: Vec<serde_json::Value>,
2741    ) -> serde_json::Value {
2742        let device_keys: serde_json::Map<String, serde_json::Value> = devices
2743            .into_iter()
2744            .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2745            .collect();
2746        json!({
2747            "device_keys": {
2748                "@example:localhost": device_keys,
2749            },
2750            "master_keys": {
2751                "@example:localhost": {
2752                    "keys": {
2753                        "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2754                    },
2755                    "usage": ["master"],
2756                    "user_id": "@example:localhost",
2757                },
2758            },
2759            "self_signing_keys": {
2760                "@example:localhost": {
2761                    "keys": {
2762                        "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2763                    },
2764                    "usage": ["self_signing"],
2765                    "user_id": "@example:localhost",
2766                    "signatures": {
2767                        "@example:localhost": {
2768                            "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2769                        },
2770                    },
2771                },
2772            },
2773            "user_signing_keys": {
2774                "@example:localhost": {
2775                    "keys": {
2776                        "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2777                    },
2778                    "usage": ["user_signing"],
2779                    "user_id": "@example:localhost",
2780                    "signatures": {
2781                        "@example:localhost": {
2782                            "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2783                        },
2784                    },
2785                },
2786            }
2787        })
2788    }
2789
2790    // The following three tests test that we can detect whether the user has
2791    // other devices that they can verify against under different conditions.
2792    #[async_test]
2793    /// Test that we detect that can't verify against another device if we have
2794    /// no devices.
2795    async fn test_devices_to_verify_against_no_devices() {
2796        let server = MockServer::start().await;
2797        let client = logged_in_client(Some(server.uri())).await;
2798
2799        Mock::given(method("POST"))
2800            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2801            .respond_with(
2802                ResponseTemplate::new(200)
2803                    .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2804            )
2805            .mount(&server)
2806            .await;
2807
2808        assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2809    }
2810
2811    #[async_test]
2812    /// Test that we detect that we can verify against another cross-signed
2813    /// regular device.
2814    async fn test_devices_to_verify_against_cross_signed() {
2815        let server = MockServer::start().await;
2816        let client = logged_in_client(Some(server.uri())).await;
2817
2818        Mock::given(method("POST"))
2819            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2820            .respond_with(ResponseTemplate::new(200).set_body_json(
2821                devices_to_verify_against_keys_query_response(vec![
2822                    json!({
2823                        "algorithms": [
2824                            "m.olm.v1.curve25519-aes-sha2",
2825                            "m.megolm.v1.aes-sha2",
2826                        ],
2827                        "user_id": "@example:localhost",
2828                        "device_id": "SIGNEDDEVICE",
2829                        "keys": {
2830                            "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2831                            "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2832                        },
2833                        "signatures": {
2834                            "@example:localhost": {
2835                                "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2836                                "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2837                            },
2838                        },
2839                    })
2840                ])
2841            ))
2842            .mount(&server)
2843            .await;
2844
2845        assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2846    }
2847
2848    #[async_test]
2849    /// Test that we detect that we can't verify against a dehydrated or
2850    /// unsigned device.
2851    async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2852        let server = MockServer::start().await;
2853        let client = logged_in_client(Some(server.uri())).await;
2854        let user_id = client.user_id().unwrap();
2855        let olm_machine = client.olm_machine().await;
2856        let olm_machine = olm_machine.as_ref().unwrap();
2857
2858        Mock::given(method("POST"))
2859            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2860            .respond_with(ResponseTemplate::new(200).set_body_json(
2861                devices_to_verify_against_keys_query_response(vec![
2862                    json!({
2863                        "algorithms": [
2864                            "m.olm.v1.curve25519-aes-sha2",
2865                            "m.megolm.v1.aes-sha2",
2866                        ],
2867                        "user_id": "@example:localhost",
2868                        "device_id": "DEHYDRATEDDEVICE",
2869                        "keys": {
2870                            "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2871                            "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2872                        },
2873                        "dehydrated": true,
2874                        "signatures": {
2875                            "@example:localhost": {
2876                                "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2877                                "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2878                            },
2879                        },
2880                    }),
2881                    json!({
2882                        "algorithms": [
2883                            "m.olm.v1.curve25519-aes-sha2",
2884                            "m.megolm.v1.aes-sha2",
2885                        ],
2886                        "user_id": "@example:localhost",
2887                        "device_id": "UNSIGNEDDEVICE",
2888                        "keys": {
2889                            "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2890                            "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2891                        },
2892                        "signatures": {
2893                            "@example:localhost": {
2894                                "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2895                            },
2896                        },
2897                    }),
2898                ])
2899            ))
2900            .mount(&server)
2901            .await;
2902
2903        let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2904        client.keys_query(&request_id, request.device_keys).await.unwrap();
2905
2906        assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2907    }
2908
2909    #[cfg(feature = "experimental-send-custom-to-device")]
2910    mod resolve_recipient_devices {
2911        use matrix_sdk_test::async_test;
2912        use ruma::{
2913            OwnedDeviceId, device_id, owned_device_id, to_device::DeviceIdOrAllDevices, user_id,
2914        };
2915
2916        use super::super::Device;
2917        use crate::{Client, test_utils::mocks::MatrixMockServer};
2918
2919        const BOB_FIRST_DEVICE: &str = "B0B0B0B0B";
2920        const BOB_SECOND_DEVICE: &str = "B0B2B0B2";
2921        const BOB_THIRD_DEVICE: &str = "B0B3B0B3";
2922
2923        /// Set up Alice and Bob, where Bob has two devices that Alice knows
2924        /// about.
2925        ///
2926        /// The [`MatrixMockServer`] is returned so that it stays alive for the
2927        /// duration of the test.
2928        async fn alice_and_bob_with_two_bob_devices() -> (MatrixMockServer, Client, Client) {
2929            let server = MatrixMockServer::new().await;
2930            server.mock_crypto_endpoints_preset().await;
2931
2932            let (alice, bob) = server.set_up_alice_and_bob_for_encryption().await;
2933            assert_eq!(bob.device_id().unwrap(), device_id!(BOB_FIRST_DEVICE));
2934
2935            server
2936                .set_up_new_device_for_encryption(&bob, device_id!(BOB_SECOND_DEVICE), vec![&alice])
2937                .await;
2938
2939            // Let Alice download Bob's second device.
2940            server
2941                .mock_sync()
2942                .ok_and_run(&alice, |builder| {
2943                    builder.add_change_device(bob.user_id().unwrap());
2944                })
2945                .await;
2946
2947            (server, alice, bob)
2948        }
2949
2950        /// The device IDs of the given devices, sorted so that we can assert on
2951        /// them.
2952        fn device_ids(devices: Vec<Device>) -> Vec<OwnedDeviceId> {
2953            let mut device_ids: Vec<_> =
2954                devices.into_iter().map(|d| d.device_id().to_owned()).collect();
2955            device_ids.sort();
2956            device_ids
2957        }
2958
2959        #[async_test]
2960        async fn test_all_devices_resolves_to_every_known_device() {
2961            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2962
2963            let (devices, unknown_devices) = alice
2964                .resolve_recipient_devices(
2965                    bob.user_id().unwrap(),
2966                    vec![DeviceIdOrAllDevices::AllDevices],
2967                )
2968                .await
2969                .unwrap();
2970
2971            assert_eq!(
2972                device_ids(devices),
2973                vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
2974            );
2975            assert!(unknown_devices.is_empty(), "`AllDevices` can't reference an unknown device");
2976        }
2977
2978        #[async_test]
2979        async fn test_explicit_device_ids_are_filtered_to_the_requested_ones() {
2980            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2981
2982            let (devices, unknown_devices) = alice
2983                .resolve_recipient_devices(
2984                    bob.user_id().unwrap(),
2985                    vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_SECOND_DEVICE))],
2986                )
2987                .await
2988                .unwrap();
2989
2990            assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_SECOND_DEVICE)]);
2991            assert!(unknown_devices.is_empty());
2992        }
2993
2994        /// Asking for several device IDs resolves all of them, and only them.
2995        #[async_test]
2996        async fn test_several_explicit_device_ids_are_all_resolved() {
2997            let (server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2998
2999            server
3000                .set_up_new_device_for_encryption(&bob, device_id!(BOB_THIRD_DEVICE), vec![&alice])
3001                .await;
3002
3003            // Let Alice download Bob's third device.
3004            server
3005                .mock_sync()
3006                .ok_and_run(&alice, |builder| {
3007                    builder.add_change_device(bob.user_id().unwrap());
3008                })
3009                .await;
3010
3011            let (devices, unknown_devices) = alice
3012                .resolve_recipient_devices(
3013                    bob.user_id().unwrap(),
3014                    vec![
3015                        DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3016                        DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_THIRD_DEVICE)),
3017                    ],
3018                )
3019                .await
3020                .unwrap();
3021
3022            assert_eq!(
3023                device_ids(devices),
3024                vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_THIRD_DEVICE)],
3025                "the second device was not requested and must be left out"
3026            );
3027            assert!(unknown_devices.is_empty());
3028        }
3029
3030        #[async_test]
3031        async fn test_unknown_device_is_reported_back() {
3032            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3033
3034            let (devices, unknown_devices) = alice
3035                .resolve_recipient_devices(
3036                    bob.user_id().unwrap(),
3037                    vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE"))],
3038                )
3039                .await
3040                .unwrap();
3041
3042            assert!(devices.is_empty());
3043            assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3044        }
3045
3046        /// A single unknown device must not prevent the known ones from
3047        /// receiving the message.
3048        #[async_test]
3049        async fn test_known_and_unknown_devices_are_split() {
3050            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3051
3052            let (devices, unknown_devices) = alice
3053                .resolve_recipient_devices(
3054                    bob.user_id().unwrap(),
3055                    vec![
3056                        DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3057                        DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3058                    ],
3059                )
3060                .await
3061                .unwrap();
3062
3063            assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3064            assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3065        }
3066
3067        /// If `AllDevices` is mixed with explicit device IDs, `AllDevices`
3068        /// wins and the explicit entries are ignored, even if they are unknown
3069        /// to us.
3070        #[async_test]
3071        async fn test_all_devices_takes_precedence_over_explicit_device_ids() {
3072            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3073
3074            let (devices, unknown_devices) = alice
3075                .resolve_recipient_devices(
3076                    bob.user_id().unwrap(),
3077                    vec![
3078                        DeviceIdOrAllDevices::AllDevices,
3079                        DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3080                    ],
3081                )
3082                .await
3083                .unwrap();
3084
3085            assert_eq!(
3086                device_ids(devices),
3087                vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3088            );
3089            assert!(unknown_devices.is_empty());
3090        }
3091
3092        /// An empty recipient list targets nobody, it is not a synonym for
3093        /// `AllDevices`.
3094        #[async_test]
3095        async fn test_empty_recipient_list_resolves_to_no_device() {
3096            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3097
3098            let (devices, unknown_devices) =
3099                alice.resolve_recipient_devices(bob.user_id().unwrap(), vec![]).await.unwrap();
3100
3101            assert!(devices.is_empty());
3102            assert!(unknown_devices.is_empty());
3103        }
3104
3105        /// A user we don't know anything about has no devices, and every device
3106        /// explicitly asked for is unknown.
3107        #[async_test]
3108        async fn test_unknown_user_has_no_device() {
3109            let (_server, alice, _bob) = alice_and_bob_with_two_bob_devices().await;
3110            let unknown_user_id = user_id!("@carol:example.org");
3111
3112            let (devices, unknown_devices) = alice
3113                .resolve_recipient_devices(
3114                    unknown_user_id,
3115                    vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE))],
3116                )
3117                .await
3118                .unwrap();
3119
3120            assert!(devices.is_empty());
3121            assert_eq!(unknown_devices, vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3122
3123            // `AllDevices` for an unknown user is not an error either, it just
3124            // resolves to nothing.
3125            let (devices, unknown_devices) = alice
3126                .resolve_recipient_devices(unknown_user_id, vec![DeviceIdOrAllDevices::AllDevices])
3127                .await
3128                .unwrap();
3129
3130            assert!(devices.is_empty());
3131            assert!(unknown_devices.is_empty());
3132        }
3133    }
3134}