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
394                            // `m.oauth` stage where we 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
494        // key and the part containing the new key. The parts are conveniently
495        // separated 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
506        // key, we 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
517        // static 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
524        // single 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 its
643    /// 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
780                                // have. The server telling us that we already
781                                // have a one-time key uploaded means that we
782                                // forgot about some of our one-time keys. This
783                                // will lead to UTDs.
784                                {
785                                    let already_reported = self
786                                        .state_store()
787                                        .get_kv_data(StateStoreDataKey::OneTimeKeyAlreadyUploaded)
788                                        .await?
789                                        .is_some();
790
791                                    if message.starts_with("One time key") && !already_reported {
792                                        let error_message =
793                                            DuplicateOneTimeKeyErrorMessage::from_str(message);
794
795                                        if let Ok(message) = &error_message {
796                                            error!(
797                                                sentry = true,
798                                                old_key = %message.old_key,
799                                                new_key = %message.new_key,
800                                                "Duplicate one-time keys have been uploaded"
801                                            );
802                                        } else {
803                                            error!(
804                                                sentry = true,
805                                                "Duplicate one-time keys have been uploaded"
806                                            );
807                                        }
808
809                                        self.state_store()
810                                            .set_kv_data(
811                                                StateStoreDataKey::OneTimeKeyAlreadyUploaded,
812                                                StateStoreDataValue::OneTimeKeyAlreadyUploaded,
813                                            )
814                                            .await?;
815
816                                        if let Err(e) = self
817                                            .inner
818                                            .duplicate_key_upload_error_sender
819                                            .send(error_message.ok())
820                                        {
821                                            error!(
822                                                "Failed to dispatch duplicate key upload error notification: {}",
823                                                e
824                                            );
825                                        }
826                                    }
827                                }
828                            }
829                        }
830                        _ => {}
831                    }
832
833                    response?;
834                }
835            }
836            AnyOutgoingRequest::ToDeviceRequest(request) => {
837                let response = self.send_to_device(request).await?;
838                self.mark_request_as_sent(r.request_id(), &response).await?;
839            }
840            AnyOutgoingRequest::SignatureUpload(request) => {
841                let response = self.send(request.clone()).await?;
842                self.mark_request_as_sent(r.request_id(), &response).await?;
843            }
844            AnyOutgoingRequest::RoomMessage(request) => {
845                let response = self.room_send_helper(request).await?;
846                self.mark_request_as_sent(r.request_id(), &response).await?;
847            }
848            AnyOutgoingRequest::KeysClaim(request) => {
849                let response = self.send(request.clone()).await?;
850                self.mark_request_as_sent(r.request_id(), &response).await?;
851            }
852        }
853
854        Ok(())
855    }
856
857    #[instrument(skip_all)]
858    pub(crate) async fn send_outgoing_requests(&self) -> Result<()> {
859        const MAX_CONCURRENT_REQUESTS: usize = 20;
860
861        // This is needed because sometimes we need to automatically claim some
862        // one-time keys to unwedge an existing Olm session.
863        if let Err(e) = self.claim_one_time_keys(iter::empty()).await {
864            warn!("Error while claiming one-time keys {:?}", e);
865        }
866
867        let outgoing_requests = stream::iter(
868            self.olm_machine()
869                .await
870                .as_ref()
871                .ok_or(Error::NoOlmMachine)?
872                .outgoing_requests()
873                .await?,
874        )
875        .map(|r| self.send_outgoing_request(r));
876
877        let requests = outgoing_requests.buffer_unordered(MAX_CONCURRENT_REQUESTS);
878
879        requests
880            .for_each(|r| async move {
881                match r {
882                    Ok(_) => (),
883                    Err(e) => warn!(error = ?e, "Error when sending out an outgoing E2EE request"),
884                }
885            })
886            .await;
887
888        Ok(())
889    }
890}
891
892#[cfg(feature = "experimental-send-custom-to-device")]
893impl Client {
894    /// Olm-encrypt a raw to-device message and send it to a set of recipient
895    /// devices.
896    ///
897    /// If there are a lot of recipient devices multiple `/sendToDevice`
898    /// requests might be sent out.
899    ///
900    /// The content is always encrypted.
901    ///
902    /// # Arguments
903    ///
904    /// - `event_type` - The type of the to-device event to send.
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
915    /// `user id -> device ids` map. A device can end up in there because it is
916    /// unknown to us, because the sharing strategy excluded it, or because
917    /// encrypting for it or sending to it failed. An empty map means every
918    /// recipient was served.
919    ///
920    /// [`send_event_to_device`]: ruma::api::client::to_device::send_event_to_device
921    pub async fn send_encrypted_to_device(
922        &self,
923        event_type: &ToDeviceEventType,
924        recipients: BTreeMap<OwnedUserId, Vec<DeviceIdOrAllDevices>>,
925        content: Raw<AnyToDeviceEventContent>,
926    ) -> Result<BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>> {
927        let mut failures: BTreeMap<OwnedUserId, Vec<OwnedDeviceId>> = BTreeMap::new();
928        let mut recipient_devices = Vec::<_>::new();
929
930        // Find out the actual user devices from their name/wildcard
931        for (user_id, recipient_device_ids) in recipients {
932            let (devices, unknown_devices) =
933                self.resolve_recipient_devices(&user_id, recipient_device_ids).await?;
934            recipient_devices.extend(devices);
935            if !unknown_devices.is_empty() {
936                failures.insert(user_id, unknown_devices);
937            }
938        }
939
940        if !recipient_devices.is_empty() {
941            let encrypt_and_send_failures = self
942                .encryption()
943                .encrypt_and_send_raw_to_device(
944                    recipient_devices.iter().collect(),
945                    &event_type.to_string(),
946                    content,
947                    CollectStrategy::AllDevices,
948                )
949                .await?;
950
951            for (user_id, device_id) in encrypt_and_send_failures {
952                failures.entry(user_id).or_default().push(device_id)
953            }
954        }
955
956        Ok(failures)
957    }
958
959    /// Resolve the devices ([`Device`]) of a single user from a list of
960    /// DeviceIdOrAllDevices.
961    ///
962    /// # Returns
963    ///
964    /// A tuple of the [`Device`]s we know about and should send to, and the
965    /// device IDs that were explicitly requested but are unknown to us.
966    /// [`DeviceIdOrAllDevices::AllDevices`] never yields unknown devices.
967    async fn resolve_recipient_devices(
968        &self,
969        user_id: &UserId,
970        recipient_device_ids: Vec<DeviceIdOrAllDevices>,
971    ) -> Result<(Vec<Device>, Vec<OwnedDeviceId>)> {
972        let user_devices = self.encryption().get_user_devices(user_id).await?;
973
974        if recipient_device_ids.contains(&DeviceIdOrAllDevices::AllDevices) {
975            // If the user wants to send to all devices, there's nothing to
976            // filter and no need to inspect other entries in the user's device
977            // list.
978            let devices: Vec<_> = user_devices.devices().collect();
979
980            if devices.is_empty() {
981                warn!(
982                    "Recipient list contains `AllDevices` but no devices found for user {user_id}."
983                );
984            }
985            if recipient_device_ids.len() > 1 {
986                warn!(
987                    "The recipient_device_ids list for {user_id} contains both `AllDevices` and explicit `DeviceId` entries. Only consider `AllDevices`",
988                );
989            }
990
991            Ok((devices, Vec::new()))
992        } else {
993            // If the user wants to send to only some devices, filter out any
994            // devices that aren't part of the recipient_device_ids list.
995            let (found_device_ids, devices): (BTreeSet<_>, Vec<_>) = user_devices
996                .devices()
997                .map(|device| (device.device_id().to_owned(), device))
998                .filter(|(device_id, _)| {
999                    recipient_device_ids
1000                        .contains(&DeviceIdOrAllDevices::DeviceId(device_id.clone()))
1001                })
1002                .unzip();
1003
1004            let requested_device_ids: BTreeSet<_> = recipient_device_ids
1005                .into_iter()
1006                .filter_map(|d| as_variant!(d, DeviceIdOrAllDevices::DeviceId))
1007                .collect();
1008
1009            // Let's now find any devices that are part of the
1010            // recipient_device_ids list but were not found in our store.
1011            let missing_devices =
1012                requested_device_ids.difference(&found_device_ids).map(ToOwned::to_owned).collect();
1013
1014            Ok((devices, missing_devices))
1015        }
1016    }
1017}
1018
1019#[cfg(any(feature = "testing", test))]
1020impl Client {
1021    /// Get the olm machine, for testing purposes only.
1022    pub async fn olm_machine_for_testing(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1023        self.olm_machine().await
1024    }
1025
1026    /// Aborts the client's bundle receiver task, for testing purposes only.
1027    pub fn abort_bundle_receiver_task(&self) {
1028        let tasks = self.inner.e2ee.tasks.lock();
1029        if let Some(task) = tasks.receive_historic_room_key_bundles.as_ref() {
1030            task.abort()
1031        }
1032    }
1033}
1034
1035/// A high-level API to manage the client's encryption.
1036///
1037/// To get this, use [`Client::encryption()`].
1038#[derive(Debug, Clone)]
1039pub struct Encryption {
1040    /// The underlying client.
1041    client: Client,
1042}
1043
1044impl Encryption {
1045    pub(crate) fn new(client: Client) -> Self {
1046        Self { client }
1047    }
1048
1049    /// Returns the current encryption settings for this client.
1050    pub(crate) fn settings(&self) -> EncryptionSettings {
1051        self.client.inner.e2ee.encryption_settings
1052    }
1053
1054    /// Get the public ed25519 key of our own device. This is usually what is
1055    /// called the fingerprint of the device.
1056    pub async fn ed25519_key(&self) -> Option<String> {
1057        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().ed25519.to_base64())
1058    }
1059
1060    /// Get the public Curve25519 key of our own device.
1061    pub async fn curve25519_key(&self) -> Option<Curve25519PublicKey> {
1062        self.client.olm_machine().await.as_ref().map(|o| o.identity_keys().curve25519)
1063    }
1064
1065    /// Get the current device creation timestamp.
1066    pub async fn device_creation_timestamp(&self) -> MilliSecondsSinceUnixEpoch {
1067        match self.get_own_device().await {
1068            Ok(Some(device)) => device.first_time_seen_ts(),
1069            // Should not happen, there should always be an own device
1070            _ => MilliSecondsSinceUnixEpoch::now(),
1071        }
1072    }
1073
1074    /// This method will import all the private cross-signing keys and, if
1075    /// available, the private part of a backup key and its accompanying version
1076    /// into the store.
1077    ///
1078    /// Importing all the secrets will mark the device as verified and enable
1079    /// backups if a backup key was available in the bundle.
1080    ///
1081    /// **Warning**: Only import this from a trusted source, i.e. if an existing
1082    /// device is sharing this with a new device.
1083    ///
1084    /// **Warning*: Only call this method right after logging in and before the
1085    /// initial sync has been started.
1086    pub async fn import_secrets_bundle(
1087        &self,
1088        bundle: &SecretsBundle,
1089    ) -> Result<(), BundleImportError> {
1090        self.import_secrets_bundle_impl(bundle).await?;
1091
1092        // Upload the device keys, this will ensure that other devices see us as
1093        // a fully verified device as soon as this method returns.
1094        self.ensure_device_keys_upload().await?;
1095        self.wait_for_e2ee_initialization_tasks().await;
1096
1097        // If our initialization tasks completed before we imported the secrets
1098        // bundle, backups might not have been enabled.
1099        //
1100        // In this case attempt to enable them again.
1101        if !self.backups().are_enabled().await {
1102            self.backups().maybe_resume_backups().await?;
1103        }
1104
1105        Ok(())
1106    }
1107
1108    pub(crate) async fn import_secrets_bundle_impl(
1109        &self,
1110        bundle: &SecretsBundle,
1111    ) -> Result<(), SecretImportError> {
1112        let olm_machine = self.client.olm_machine().await;
1113        let olm_machine =
1114            olm_machine.as_ref().expect("This should only be called once we have an OlmMachine");
1115
1116        olm_machine.store().import_secrets_bundle(bundle).await
1117    }
1118
1119    /// Get the status of the private cross signing keys.
1120    ///
1121    /// This can be used to check which private cross signing keys we have
1122    /// stored locally.
1123    pub async fn cross_signing_status(&self) -> Option<CrossSigningStatus> {
1124        let olm = self.client.olm_machine().await;
1125        let machine = olm.as_ref()?;
1126        Some(machine.cross_signing_status().await)
1127    }
1128
1129    /// Does the user have other devices that the current device can verify
1130    /// against?
1131    ///
1132    /// The device must be signed by the user's cross-signing key, must have an
1133    /// identity, and must not be a dehydrated device.
1134    pub async fn has_devices_to_verify_against(&self) -> Result<bool> {
1135        let olm_machine = self.client.olm_machine().await;
1136        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1137        let user_id = olm_machine.user_id();
1138
1139        self.ensure_initial_key_query().await?;
1140
1141        let devices = self.get_user_devices(user_id).await?;
1142
1143        let ret = devices.devices().any(|device| {
1144            device.is_cross_signed_by_owner()
1145                && device.curve25519_key().is_some()
1146                && !device.is_dehydrated()
1147        });
1148
1149        Ok(ret)
1150    }
1151
1152    /// Get all the tracked users we know about
1153    ///
1154    /// Tracked users are users for which we keep the device list of E2EE
1155    /// capable devices up to date.
1156    pub async fn tracked_users(&self) -> Result<HashSet<OwnedUserId>, CryptoStoreError> {
1157        if let Some(machine) = self.client.olm_machine().await.as_ref() {
1158            machine.tracked_users().await
1159        } else {
1160            Ok(HashSet::new())
1161        }
1162    }
1163
1164    /// Get a [`Subscriber`] for the [`VerificationState`].
1165    ///
1166    /// # Examples
1167    ///
1168    /// ```no_run
1169    /// use matrix_sdk::{Client, encryption};
1170    /// use url::Url;
1171    ///
1172    /// # async {
1173    /// let homeserver = Url::parse("http://example.com")?;
1174    /// let client = Client::new(homeserver).await?;
1175    /// let mut subscriber = client.encryption().verification_state();
1176    ///
1177    /// let current_value = subscriber.get();
1178    ///
1179    /// println!("The current verification state is: {current_value:?}");
1180    ///
1181    /// if let Some(verification_state) = subscriber.next().await {
1182    ///     println!("Received verification state update {:?}", verification_state)
1183    /// }
1184    /// # anyhow::Ok(()) };
1185    /// ```
1186    pub fn verification_state(&self) -> Subscriber<VerificationState> {
1187        self.client.inner.verification_state.subscribe_reset()
1188    }
1189
1190    /// Get a verification object with the given flow id.
1191    pub async fn get_verification(&self, user_id: &UserId, flow_id: &str) -> Option<Verification> {
1192        let olm = self.client.olm_machine().await;
1193        let olm = olm.as_ref()?;
1194        #[allow(clippy::bind_instead_of_map)]
1195        olm.get_verification(user_id, flow_id).and_then(|v| match v {
1196            matrix_sdk_base::crypto::Verification::SasV1(sas) => {
1197                Some(SasVerification { inner: sas, client: self.client.clone() }.into())
1198            }
1199            #[cfg(feature = "qrcode")]
1200            matrix_sdk_base::crypto::Verification::QrV1(qr) => {
1201                Some(verification::QrVerification { inner: qr, client: self.client.clone() }.into())
1202            }
1203            _ => None,
1204        })
1205    }
1206
1207    /// Get a `VerificationRequest` object for the given user with the given
1208    /// flow id.
1209    pub async fn get_verification_request(
1210        &self,
1211        user_id: &UserId,
1212        flow_id: impl AsRef<str>,
1213    ) -> Option<VerificationRequest> {
1214        let olm = self.client.olm_machine().await;
1215        let olm = olm.as_ref()?;
1216
1217        olm.get_verification_request(user_id, flow_id)
1218            .map(|r| VerificationRequest { inner: r, client: self.client.clone() })
1219    }
1220
1221    /// Get a specific device of a user.
1222    ///
1223    /// # Arguments
1224    ///
1225    /// - `user_id` - The unique id of the user that the device belongs to.
1226    /// - `device_id` - The unique id of the device.
1227    ///
1228    /// Returns a `Device` if one is found and the crypto store didn't throw an
1229    /// error.
1230    ///
1231    /// This will always return None if the client hasn't been logged in.
1232    ///
1233    /// # Examples
1234    ///
1235    /// ```no_run
1236    /// # use matrix_sdk::{Client, ruma::{device_id, user_id}};
1237    /// # use url::Url;
1238    /// # async {
1239    /// # let alice = user_id!("@alice:example.org");
1240    /// # let homeserver = Url::parse("http://example.com")?;
1241    /// # let client = Client::new(homeserver).await?;
1242    /// if let Some(device) =
1243    ///     client.encryption().get_device(alice, device_id!("DEVICEID")).await?
1244    /// {
1245    ///     println!("{:?}", device.is_verified());
1246    ///
1247    ///     if !device.is_verified() {
1248    ///         let verification = device.request_verification().await?;
1249    ///     }
1250    /// }
1251    /// # anyhow::Ok(()) };
1252    /// ```
1253    pub async fn get_device(
1254        &self,
1255        user_id: &UserId,
1256        device_id: &DeviceId,
1257    ) -> Result<Option<Device>, CryptoStoreError> {
1258        let olm = self.client.olm_machine().await;
1259        let Some(machine) = olm.as_ref() else { return Ok(None) };
1260        let device = machine.get_device(user_id, device_id, None).await?;
1261        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1262    }
1263
1264    /// A convenience method to retrieve your own device from the store.
1265    ///
1266    /// This is the same as calling [`Encryption::get_device()`] with your own
1267    /// user and device ID.
1268    ///
1269    /// This will always return a device, unless you are not logged in.
1270    pub async fn get_own_device(&self) -> Result<Option<Device>, CryptoStoreError> {
1271        let olm = self.client.olm_machine().await;
1272        let Some(machine) = olm.as_ref() else { return Ok(None) };
1273        let device = machine.get_device(machine.user_id(), machine.device_id(), None).await?;
1274        Ok(device.map(|d| Device { inner: d, client: self.client.clone() }))
1275    }
1276
1277    /// Get a map holding all the devices of an user.
1278    ///
1279    /// This will always return an empty map if the client hasn't been logged
1280    /// in.
1281    ///
1282    /// # Arguments
1283    ///
1284    /// - `user_id` - The unique id of the user that the devices belong to.
1285    ///
1286    /// # Examples
1287    ///
1288    /// ```no_run
1289    /// # use matrix_sdk::{Client, ruma::user_id};
1290    /// # use url::Url;
1291    /// # async {
1292    /// # let alice = user_id!("@alice:example.org");
1293    /// # let homeserver = Url::parse("http://example.com")?;
1294    /// # let client = Client::new(homeserver).await?;
1295    /// let devices = client.encryption().get_user_devices(alice).await?;
1296    ///
1297    /// for device in devices.devices() {
1298    ///     println!("{device:?}");
1299    /// }
1300    /// # anyhow::Ok(()) };
1301    /// ```
1302    pub async fn get_user_devices(&self, user_id: &UserId) -> Result<UserDevices, Error> {
1303        let devices = self
1304            .client
1305            .olm_machine()
1306            .await
1307            .as_ref()
1308            .ok_or(Error::NoOlmMachine)?
1309            .get_user_devices(user_id, None)
1310            .await?;
1311
1312        Ok(UserDevices { inner: devices, client: self.client.clone() })
1313    }
1314
1315    /// Get the E2EE identity of a user from the crypto store.
1316    ///
1317    /// Usually, we only have the E2EE identity of a user locally if the user is
1318    /// tracked, meaning that we are both members of the same encrypted room.
1319    ///
1320    /// To get the E2EE identity of a user even if it is not available locally
1321    /// use [`Encryption::request_user_identity()`].
1322    ///
1323    /// # Arguments
1324    ///
1325    /// - `user_id` - The unique id of the user that the identity belongs to.
1326    ///
1327    /// Returns a `UserIdentity` if one is found and the crypto store didn't
1328    /// throw an error.
1329    ///
1330    /// This will always return None if the client hasn't been logged in.
1331    ///
1332    /// # Examples
1333    ///
1334    /// ```no_run
1335    /// # use matrix_sdk::{Client, ruma::user_id};
1336    /// # use url::Url;
1337    /// # async {
1338    /// # let alice = user_id!("@alice:example.org");
1339    /// # let homeserver = Url::parse("http://example.com")?;
1340    /// # let client = Client::new(homeserver).await?;
1341    /// let user = client.encryption().get_user_identity(alice).await?;
1342    ///
1343    /// if let Some(user) = user {
1344    ///     println!("{:?}", user.is_verified());
1345    ///
1346    ///     let verification = user.request_verification().await?;
1347    /// }
1348    /// # anyhow::Ok(()) };
1349    /// ```
1350    pub async fn get_user_identity(
1351        &self,
1352        user_id: &UserId,
1353    ) -> Result<Option<UserIdentity>, CryptoStoreError> {
1354        let olm = self.client.olm_machine().await;
1355        let Some(olm) = olm.as_ref() else { return Ok(None) };
1356        let identity = olm.get_identity(user_id, None).await?;
1357
1358        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1359    }
1360
1361    /// Get the E2EE identity of a user from the homeserver.
1362    ///
1363    /// The E2EE identity returned is always guaranteed to be up-to-date. If the
1364    /// E2EE identity is not found, it should mean that the user did not set up
1365    /// cross-signing.
1366    ///
1367    /// If you want the E2EE identity of a user without making a request to the
1368    /// homeserver, use [`Encryption::get_user_identity()`] instead.
1369    ///
1370    /// # Arguments
1371    ///
1372    /// - `user_id` - The ID of the user that the identity belongs to.
1373    ///
1374    /// Returns a [`UserIdentity`] if one is found. Returns an error if there
1375    /// was an issue with the crypto store or with the request to the
1376    /// homeserver.
1377    ///
1378    /// This will always return `None` if the client hasn't been logged in.
1379    ///
1380    /// # Examples
1381    ///
1382    /// ```no_run
1383    /// # use matrix_sdk::{Client, ruma::user_id};
1384    /// # use url::Url;
1385    /// # async {
1386    /// # let alice = user_id!("@alice:example.org");
1387    /// # let homeserver = Url::parse("http://example.com")?;
1388    /// # let client = Client::new(homeserver).await?;
1389    /// let user = client.encryption().request_user_identity(alice).await?;
1390    ///
1391    /// if let Some(user) = user {
1392    ///     println!("User is verified: {:?}", user.is_verified());
1393    ///
1394    ///     let verification = user.request_verification().await?;
1395    /// }
1396    /// # anyhow::Ok(()) };
1397    /// ```
1398    pub async fn request_user_identity(&self, user_id: &UserId) -> Result<Option<UserIdentity>> {
1399        let olm = self.client.olm_machine().await;
1400        let Some(olm) = olm.as_ref() else { return Ok(None) };
1401
1402        let (request_id, request) = olm.query_keys_for_users(iter::once(user_id));
1403        self.client.keys_query(&request_id, request.device_keys).await?;
1404
1405        let identity = olm.get_identity(user_id, None).await?;
1406        Ok(identity.map(|i| UserIdentity::new(self.client.clone(), i)))
1407    }
1408
1409    /// Returns a stream of device updates, allowing users to listen for
1410    /// notifications about new or changed devices.
1411    ///
1412    /// The stream produced by this method emits updates whenever a new device
1413    /// is discovered or when an existing device's information is changed. Users
1414    /// can subscribe to this stream and receive updates in real-time.
1415    ///
1416    /// # Examples
1417    ///
1418    /// ```no_run
1419    /// # use matrix_sdk::Client;
1420    /// # use ruma::{device_id, user_id};
1421    /// # use futures_util::{pin_mut, StreamExt};
1422    /// # let client: Client = unimplemented!();
1423    /// # async {
1424    /// let devices_stream = client.encryption().devices_stream().await?;
1425    /// let user_id = client
1426    ///     .user_id()
1427    ///     .expect("We should know our user id after we have logged in");
1428    /// pin_mut!(devices_stream);
1429    ///
1430    /// for device_updates in devices_stream.next().await {
1431    ///     if let Some(user_devices) = device_updates.new.get(user_id) {
1432    ///         for device in user_devices.values() {
1433    ///             println!("A new device has been added {}", device.device_id());
1434    ///         }
1435    ///     }
1436    /// }
1437    /// # anyhow::Ok(()) };
1438    /// ```
1439    pub async fn devices_stream(&self) -> Result<impl Stream<Item = DeviceUpdates> + use<>> {
1440        let olm = self.client.olm_machine().await;
1441        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1442        let client = self.client.to_owned();
1443
1444        Ok(olm
1445            .store()
1446            .devices_stream()
1447            .map(move |updates| DeviceUpdates::new(client.to_owned(), updates)))
1448    }
1449
1450    /// Returns a stream of user identity updates, allowing users to listen for
1451    /// notifications about new or changed user identities.
1452    ///
1453    /// The stream produced by this method emits updates whenever a new user
1454    /// identity is discovered or when an existing identities information is
1455    /// changed. Users can subscribe to this stream and receive updates in
1456    /// real-time.
1457    ///
1458    /// # Examples
1459    ///
1460    /// ```no_run
1461    /// # use matrix_sdk::Client;
1462    /// # use ruma::{device_id, user_id};
1463    /// # use futures_util::{pin_mut, StreamExt};
1464    /// # let client: Client = unimplemented!();
1465    /// # async {
1466    /// let identities_stream =
1467    ///     client.encryption().user_identities_stream().await?;
1468    /// pin_mut!(identities_stream);
1469    ///
1470    /// for identity_updates in identities_stream.next().await {
1471    ///     for (_, identity) in identity_updates.new {
1472    ///         println!("A new identity has been added {}", identity.user_id());
1473    ///     }
1474    /// }
1475    /// # anyhow::Ok(()) };
1476    /// ```
1477    pub async fn user_identities_stream(
1478        &self,
1479    ) -> Result<impl Stream<Item = IdentityUpdates> + use<>> {
1480        let olm = self.client.olm_machine().await;
1481        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1482        let client = self.client.to_owned();
1483
1484        Ok(olm
1485            .store()
1486            .user_identities_stream()
1487            .map(move |updates| IdentityUpdates::new(client.to_owned(), updates)))
1488    }
1489
1490    /// Create and upload a new cross signing identity.
1491    ///
1492    /// # Arguments
1493    ///
1494    /// - `auth_data` - This request requires user interactive auth, the first
1495    ///   request needs to set this to `None` and will always fail with an
1496    ///   `UiaaResponse`. The response will contain information for the
1497    ///   interactive auth and the same request needs to be made but this time
1498    ///   with some `auth_data` provided.
1499    ///
1500    /// # Examples
1501    ///
1502    /// ```no_run
1503    /// # use std::collections::BTreeMap;
1504    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1505    /// # use url::Url;
1506    /// # use serde_json::json;
1507    /// # async {
1508    /// # let homeserver = Url::parse("http://example.com")?;
1509    /// # let client = Client::new(homeserver).await?;
1510    /// if let Err(e) = client.encryption().bootstrap_cross_signing(None).await {
1511    ///     if let Some(response) = e.as_uiaa_response() {
1512    ///         let mut password = uiaa::Password::new(
1513    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new(
1514    ///                 "example".to_owned(),
1515    ///             )),
1516    ///             "wordpass".to_owned(),
1517    ///         );
1518    ///         password.session = response.session.clone();
1519    ///
1520    ///         client
1521    ///             .encryption()
1522    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(
1523    ///                 password,
1524    ///             )))
1525    ///             .await
1526    ///             .expect("Couldn't bootstrap cross signing")
1527    ///     } else {
1528    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1529    ///     }
1530    /// }
1531    /// # anyhow::Ok(()) };
1532    /// ```
1533    pub async fn bootstrap_cross_signing(&self, auth_data: Option<AuthData>) -> Result<()> {
1534        let olm = self.client.olm_machine().await;
1535        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1536
1537        let CrossSigningBootstrapRequests {
1538            upload_signing_keys_req,
1539            upload_keys_req,
1540            upload_signatures_req,
1541        } = olm.bootstrap_cross_signing(false).await?;
1542
1543        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1544            auth: auth_data,
1545            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1546            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1547            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1548        });
1549
1550        if let Some(req) = upload_keys_req {
1551            self.client.send_outgoing_request(req).await?;
1552        }
1553        self.client.send(upload_signing_keys_req).await?;
1554        self.client.send(upload_signatures_req).await?;
1555
1556        Ok(())
1557    }
1558
1559    /// Reset the cross-signing keys.
1560    ///
1561    /// # Example
1562    ///
1563    /// ```no_run
1564    /// use matrix_sdk::{ruma::api::client::uiaa, encryption::CrossSigningResetAuthType};
1565    ///
1566    /// # async {
1567    /// # let homeserver = url::Url::parse("http://example.com")?;
1568    /// # let client = matrix_sdk::Client::new(homeserver).await?;
1569    /// # let user_id = unimplemented!();
1570    /// let encryption = client.encryption();
1571    ///
1572    /// if let Some(handle) = encryption.reset_cross_signing().await? {
1573    ///     match handle.auth_type() {
1574    ///         CrossSigningResetAuthType::Uiaa(uiaa) => {
1575    ///             use matrix_sdk::ruma::api::client::uiaa;
1576    ///
1577    ///             let password = "1234".to_owned();
1578    ///             let mut password = uiaa::Password::new(user_id, password);
1579    ///             password.session = uiaa.session;
1580    ///
1581    ///             handle.auth(Some(uiaa::AuthData::Password(password))).await?;
1582    ///         }
1583    ///         CrossSigningResetAuthType::OAuth(o) => {
1584    ///             println!(
1585    ///                 "To reset your end-to-end encryption cross-signing identity, \
1586    ///                 you first need to approve it at {}",
1587    ///                 o.approval_url
1588    ///             );
1589    ///
1590    ///             let mut oauth = uiaa::OAuth::new();
1591    ///             oauth.session = o.session;
1592    ///
1593    ///             handle.auth(Some(uiaa::AuthData::OAuth(oauth))).await?;
1594    ///         }
1595    ///     }
1596    /// }
1597    /// # anyhow::Ok(()) };
1598    /// ```
1599    pub async fn reset_cross_signing(&self) -> Result<Option<CrossSigningResetHandle>> {
1600        let olm = self.client.olm_machine().await;
1601        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1602
1603        let CrossSigningBootstrapRequests {
1604            upload_keys_req,
1605            upload_signing_keys_req,
1606            upload_signatures_req,
1607        } = olm.bootstrap_cross_signing(true).await?;
1608
1609        let upload_signing_keys_req = assign!(UploadSigningKeysRequest::new(), {
1610            auth: None,
1611            master_key: upload_signing_keys_req.master_key.map(|c| c.to_raw()),
1612            self_signing_key: upload_signing_keys_req.self_signing_key.map(|c| c.to_raw()),
1613            user_signing_key: upload_signing_keys_req.user_signing_key.map(|c| c.to_raw()),
1614        });
1615
1616        if let Some(req) = upload_keys_req {
1617            self.client.send_outgoing_request(req).await?;
1618        }
1619
1620        if let Err(error) = self.client.send(upload_signing_keys_req.clone()).await {
1621            if let Ok(Some(auth_type)) = CrossSigningResetAuthType::new(&error) {
1622                let client = self.client.clone();
1623
1624                Ok(Some(CrossSigningResetHandle::new(
1625                    client,
1626                    upload_signing_keys_req,
1627                    upload_signatures_req,
1628                    auth_type,
1629                )))
1630            } else {
1631                Err(error.into())
1632            }
1633        } else {
1634            self.client.send(upload_signatures_req).await?;
1635
1636            Ok(None)
1637        }
1638    }
1639
1640    /// Query the user's own device keys, if, and only if, we didn't have their
1641    /// identity in the first place.
1642    async fn ensure_initial_key_query(&self) -> Result<()> {
1643        let olm_machine = self.client.olm_machine().await;
1644        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1645
1646        let user_id = olm_machine.user_id();
1647
1648        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1649            let (request_id, request) = olm_machine.query_keys_for_users([olm_machine.user_id()]);
1650            self.client.keys_query(&request_id, request.device_keys).await?;
1651        }
1652
1653        Ok(())
1654    }
1655
1656    /// Create and upload a new cross signing identity, if that has not been
1657    /// done yet.
1658    ///
1659    /// This will only create a new cross-signing identity if the user had never
1660    /// done it before. If the user did it before, then this is a no-op.
1661    ///
1662    /// See also the documentation of [`Self::bootstrap_cross_signing`] for the
1663    /// behavior of this function.
1664    ///
1665    /// # Arguments
1666    ///
1667    /// - `auth_data` - This request requires user interactive auth, the first
1668    ///   request needs to set this to `None` and will always fail with an
1669    ///   `UiaaResponse`. The response will contain information for the
1670    ///   interactive auth and the same request needs to be made but this time
1671    ///   with some `auth_data` provided.
1672    ///
1673    /// # Examples
1674    ///
1675    /// ```no_run
1676    /// # use std::collections::BTreeMap;
1677    /// # use matrix_sdk::{ruma::api::client::uiaa, Client};
1678    /// # use url::Url;
1679    /// # use serde_json::json;
1680    /// # async {
1681    /// # let homeserver = Url::parse("http://example.com")?;
1682    /// # let client = Client::new(homeserver).await?;
1683    /// if let Err(e) = client.encryption().bootstrap_cross_signing_if_needed(None).await {
1684    ///     if let Some(response) = e.as_uiaa_response() {
1685    ///         let mut password = uiaa::Password::new(
1686    ///             uiaa::UserIdentifier::Matrix(uiaa::MatrixUserIdentifier::new("example".to_owned())),
1687    ///             "wordpass".to_owned(),
1688    ///         );
1689    ///         password.session = response.session.clone();
1690    ///
1691    ///         // Note, on the failed attempt we can use `bootstrap_cross_signing` immediately, to
1692    ///         // avoid checks.
1693    ///         client
1694    ///             .encryption()
1695    ///             .bootstrap_cross_signing(Some(uiaa::AuthData::Password(password)))
1696    ///             .await
1697    ///             .expect("Couldn't bootstrap cross signing")
1698    ///     } else {
1699    ///         panic!("Error during cross signing bootstrap {:#?}", e);
1700    ///     }
1701    /// }
1702    /// # anyhow::Ok(()) };
1703    /// ```
1704    pub async fn bootstrap_cross_signing_if_needed(
1705        &self,
1706        auth_data: Option<AuthData>,
1707    ) -> Result<()> {
1708        let olm_machine = self.client.olm_machine().await;
1709        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1710        let user_id = olm_machine.user_id();
1711
1712        self.ensure_initial_key_query().await?;
1713
1714        if self.client.encryption().get_user_identity(user_id).await?.is_none() {
1715            self.bootstrap_cross_signing(auth_data).await?;
1716        }
1717
1718        Ok(())
1719    }
1720
1721    /// Export E2EE keys that match the given predicate encrypting them with the
1722    /// given passphrase.
1723    ///
1724    /// # Arguments
1725    ///
1726    /// - `path` - The file path where the exported key file will be saved.
1727    /// - `passphrase` - The passphrase that will be used to encrypt the
1728    ///   exported room keys.
1729    ///
1730    /// - `predicate` - A closure that will be called for every known
1731    ///   `InboundGroupSession`, which represents a room key. If the closure
1732    ///   returns `true` the `InboundGroupSessoin` will be included in the
1733    ///   export, if the closure returns `false` it will not be included.
1734    ///
1735    /// # Panics
1736    ///
1737    /// This method will panic if it isn't run on a Tokio runtime.
1738    ///
1739    /// This method will panic if it can't get enough randomness from the OS to
1740    /// encrypt the exported keys securely.
1741    ///
1742    /// # Examples
1743    ///
1744    /// ```no_run
1745    /// # use std::{path::PathBuf, time::Duration};
1746    /// # use matrix_sdk::{
1747    /// #     Client, config::SyncSettings,
1748    /// #     ruma::room_id,
1749    /// # };
1750    /// # use url::Url;
1751    /// # async {
1752    /// # let homeserver = Url::parse("http://localhost:8080")?;
1753    /// # let mut client = Client::new(homeserver).await?;
1754    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1755    /// // Export all room keys.
1756    /// client
1757    ///     .encryption()
1758    ///     .export_room_keys(path, "secret-passphrase", |_| true)
1759    ///     .await?;
1760    ///
1761    /// // Export only the room keys for a certain room.
1762    /// let path = PathBuf::from("/home/example/e2e-room-keys.txt");
1763    /// let room_id = room_id!("!test:localhost");
1764    ///
1765    /// client
1766    ///     .encryption()
1767    ///     .export_room_keys(path, "secret-passphrase", |s| s.room_id() == room_id)
1768    ///     .await?;
1769    /// # anyhow::Ok(()) };
1770    /// ```
1771    #[cfg(not(target_family = "wasm"))]
1772    pub async fn export_room_keys(
1773        &self,
1774        path: PathBuf,
1775        passphrase: &str,
1776        predicate: impl FnMut(&matrix_sdk_base::crypto::olm::InboundGroupSession) -> bool,
1777    ) -> Result<()> {
1778        let olm = self.client.olm_machine().await;
1779        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
1780
1781        let keys = olm.store().export_room_keys(predicate).await?;
1782        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1783
1784        let encrypt = move || -> Result<()> {
1785            let export: String =
1786                matrix_sdk_base::crypto::encrypt_room_key_export(&keys, &passphrase, 500_000)?;
1787            let mut file = std::fs::File::create(path)?;
1788            file.write_all(&export.into_bytes())?;
1789            Ok(())
1790        };
1791
1792        let task = tokio::task::spawn_blocking(encrypt);
1793        task.await.expect("Task join error")
1794    }
1795
1796    /// Import E2EE keys from the given file path.
1797    ///
1798    /// # Arguments
1799    ///
1800    /// - `path` - The file path where the exported key file will can be found.
1801    /// - `passphrase` - The passphrase that should be used to decrypt the
1802    ///   exported room keys.
1803    ///
1804    /// Returns a tuple of numbers that represent the number of sessions that
1805    /// were imported and the total number of sessions that were found in the
1806    /// key export.
1807    ///
1808    /// # Panics
1809    ///
1810    /// This method will panic if it isn't run on a Tokio runtime.
1811    ///
1812    /// ```no_run
1813    /// # use std::{path::PathBuf, time::Duration};
1814    /// # use matrix_sdk::{
1815    /// #     Client, config::SyncSettings,
1816    /// #     ruma::room_id,
1817    /// # };
1818    /// # use url::Url;
1819    /// # async {
1820    /// # let homeserver = Url::parse("http://localhost:8080")?;
1821    /// # let mut client = Client::new(homeserver).await?;
1822    /// let path = PathBuf::from("/home/example/e2e-keys.txt");
1823    /// let result =
1824    ///     client.encryption().import_room_keys(path, "secret-passphrase").await?;
1825    ///
1826    /// println!(
1827    ///     "Imported {} room keys out of {}",
1828    ///     result.imported_count, result.total_count
1829    /// );
1830    /// # anyhow::Ok(()) };
1831    /// ```
1832    #[cfg(not(target_family = "wasm"))]
1833    pub async fn import_room_keys(
1834        &self,
1835        path: PathBuf,
1836        passphrase: &str,
1837    ) -> Result<RoomKeyImportResult, RoomKeyImportError> {
1838        let olm = self.client.olm_machine().await;
1839        let olm = olm.as_ref().ok_or(RoomKeyImportError::StoreClosed)?;
1840        let passphrase = zeroize::Zeroizing::new(passphrase.to_owned());
1841
1842        let decrypt = move || {
1843            let file = std::fs::File::open(path)?;
1844            matrix_sdk_base::crypto::decrypt_room_key_export(file, &passphrase)
1845        };
1846
1847        let task = tokio::task::spawn_blocking(decrypt);
1848        let import = task.await.expect("Task join error")?;
1849
1850        let ret = olm.store().import_exported_room_keys(import, |_, _| {}).await?;
1851
1852        self.backups().maybe_trigger_backup();
1853
1854        Ok(ret)
1855    }
1856
1857    /// Receive notifications of room keys being received as a [`Stream`].
1858    ///
1859    /// Each time a room key is updated in any way, an update will be sent to
1860    /// the stream. Updates that happen at the same time are batched into a
1861    /// [`Vec`].
1862    ///
1863    /// If the reader of the stream lags too far behind, an error is broadcast
1864    /// containing the number of skipped items.
1865    ///
1866    /// # Examples
1867    ///
1868    /// ```no_run
1869    /// # use matrix_sdk::Client;
1870    /// # use url::Url;
1871    /// # async {
1872    /// # let homeserver = Url::parse("http://example.com")?;
1873    /// # let client = Client::new(homeserver).await?;
1874    /// use futures_util::StreamExt;
1875    ///
1876    /// let Some(mut room_keys_stream) =
1877    ///     client.encryption().room_keys_received_stream().await
1878    /// else {
1879    ///     return Ok(());
1880    /// };
1881    ///
1882    /// while let Some(update) = room_keys_stream.next().await {
1883    ///     println!("Received room keys {update:?}");
1884    /// }
1885    /// # anyhow::Ok(()) };
1886    /// ```
1887    pub async fn room_keys_received_stream(
1888        &self,
1889    ) -> Option<impl Stream<Item = Result<Vec<RoomKeyInfo>, BroadcastStreamRecvError>> + use<>>
1890    {
1891        let olm = self.client.olm_machine().await;
1892        let olm = olm.as_ref()?;
1893
1894        Some(olm.store().room_keys_received_stream())
1895    }
1896
1897    /// Receive notifications of historic room key bundles as a [`Stream`].
1898    ///
1899    /// Historic room key bundles are defined in
1900    /// [MSC4268](https://github.com/matrix-org/matrix-spec-proposals/pull/4268).
1901    ///
1902    /// Each time a historic room key bundle was received, an update will be
1903    /// sent to the stream. This stream is useful for informative purposes
1904    /// exclusively, historic room key bundles are handled by the SDK
1905    /// automatically.
1906    ///
1907    /// # Examples
1908    ///
1909    /// ```no_run
1910    /// # use matrix_sdk::Client;
1911    /// # use url::Url;
1912    /// # async {
1913    /// # let homeserver = Url::parse("http://example.com")?;
1914    /// # let client = Client::new(homeserver).await?;
1915    /// use futures_util::StreamExt;
1916    ///
1917    /// let Some(mut bundle_stream) =
1918    ///     client.encryption().historic_room_key_stream().await
1919    /// else {
1920    ///     return Ok(());
1921    /// };
1922    ///
1923    /// while let Some(bundle_info) = bundle_stream.next().await {
1924    ///     println!("Received a historic room key bundle {bundle_info:?}");
1925    /// }
1926    /// # anyhow::Ok(()) };
1927    /// ```
1928    pub async fn historic_room_key_stream(
1929        &self,
1930    ) -> Option<impl Stream<Item = RoomKeyBundleInfo> + use<>> {
1931        let olm = self.client.olm_machine().await;
1932        let olm = olm.as_ref()?;
1933
1934        Some(olm.store().historic_room_key_stream())
1935    }
1936
1937    /// Get the secret storage manager of the client.
1938    pub fn secret_storage(&self) -> SecretStorage {
1939        SecretStorage { client: self.client.to_owned() }
1940    }
1941
1942    /// Get the backups manager of the client.
1943    pub fn backups(&self) -> Backups {
1944        Backups { client: self.client.to_owned() }
1945    }
1946
1947    /// Get the recovery manager of the client.
1948    pub fn recovery(&self) -> Recovery {
1949        Recovery { client: self.client.to_owned() }
1950    }
1951
1952    /// Get the dehydrated-devices manager of the client.
1953    ///
1954    /// A dehydrated device is a virtual device that the homeserver holds on the
1955    /// user's behalf and that can receive end-to-end encrypted to-device events
1956    /// while the user is offline. See the [`dehydrated_devices`] module for the
1957    /// full lifecycle and an example.
1958    pub fn dehydrated_devices(&self) -> dehydrated_devices::DehydratedDevices {
1959        dehydrated_devices::DehydratedDevices { client: self.client.to_owned() }
1960    }
1961
1962    /// Enables the crypto-store cross-process lock.
1963    ///
1964    /// This may be required if there are multiple processes that may do writes
1965    /// to the same crypto store. In that case, it's necessary to create a lock,
1966    /// so that only one process writes to it, otherwise this may cause
1967    /// confusing issues because of stale data contained in in-memory caches.
1968    ///
1969    /// The provided `lock_value` must be a unique identifier for this process.
1970    /// Use [`Client::cross_process_lock_config`] to get the global value, if
1971    /// multi-process is enabled.
1972    pub async fn enable_cross_process_store_lock(&self, lock_value: String) -> Result<(), Error> {
1973        // If the lock has already been created, don't recreate it from scratch.
1974        if let Some(prev_lock) = self.client.locks().cross_process_crypto_store_lock.get() {
1975            let prev_holder = prev_lock.lock_holder();
1976            if prev_holder.is_some() && prev_holder.unwrap() == lock_value {
1977                return Ok(());
1978            }
1979            warn!(
1980                "Recreating cross-process store lock with a different holder value: \
1981                 prev was {prev_holder:?}, new is {lock_value}"
1982            );
1983        }
1984
1985        let olm_machine = self.client.base_client().olm_machine().await;
1986        let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
1987
1988        let lock = olm_machine.store().create_store_lock(
1989            "cross_process_lock".to_owned(),
1990            CrossProcessLockConfig::multi_process(lock_value.to_owned()),
1991        );
1992
1993        // Gently try to initialize the crypto store generation counter.
1994        //
1995        // If we don't get the lock immediately, then it is already acquired by
1996        // another process, and we'll get to reload next time we acquire the
1997        // lock.
1998        {
1999            let lock_result = lock.try_lock_once().await?;
2000
2001            if lock_result.is_ok() {
2002                olm_machine
2003                    .initialize_crypto_store_generation(
2004                        &self.client.locks().crypto_store_generation,
2005                    )
2006                    .await?;
2007            }
2008        }
2009
2010        self.client
2011            .locks()
2012            .cross_process_crypto_store_lock
2013            .set(lock)
2014            .map_err(|_| Error::BadCryptoStoreState)?;
2015
2016        Ok(())
2017    }
2018
2019    /// Maybe reload the `OlmMachine` after acquiring the lock for the first
2020    /// time.
2021    ///
2022    /// Returns the current generation number.
2023    #[instrument(skip(self), fields(olm_machine_new_generation, olm_machine_generation))]
2024    async fn on_lock_newly_acquired(&self) -> Result<u64, Error> {
2025        let olm_machine_guard = self.client.olm_machine().await;
2026        if let Some(olm_machine) = olm_machine_guard.as_ref() {
2027            let (new_gen, generation_number) = olm_machine
2028                .maintain_crypto_store_generation(&self.client.locks().crypto_store_generation)
2029                .await?;
2030
2031            Span::current()
2032                .record("olm_machine_new_generation", new_gen)
2033                .record("olm_machine_generation", generation_number);
2034            debug!("OlmMachine generation maintained in CryptoStore");
2035
2036            // If the crypto store generation has changed,
2037            if new_gen {
2038                // (get rid of the reference to the current crypto store first)
2039                drop(olm_machine_guard);
2040                // Recreate the OlmMachine.
2041                self.client.base_client().regenerate_olm(None).await?;
2042            }
2043            Ok(generation_number)
2044        } else {
2045            // XXX: not sure this is reachable. Seems like the OlmMachine should
2046            // always have been initialised by the time we get here. Ideally
2047            // we'd panic, or return an error, but for now I'm just adding some
2048            // logging to check if it happens, and returning the magic number 0.
2049            warn!("Encryption::on_lock_newly_acquired: called before OlmMachine initialised");
2050            Ok(0)
2051        }
2052    }
2053
2054    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
2055    /// spin-waits until the lock is available.
2056    ///
2057    /// May reload the `OlmMachine`, after obtaining the lock but not on the
2058    /// first time.
2059    ///
2060    /// Returns a guard to the lock, if it was obtained.
2061    pub async fn spin_lock_store(
2062        &self,
2063        max_backoff: Option<u32>,
2064    ) -> Result<Option<CrossProcessLockGuard>, Error> {
2065        self.lock_store(async move |lock| lock.spin_lock(max_backoff).await).await
2066    }
2067
2068    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
2069    /// attempts to lock it once.
2070    ///
2071    /// May reload the `OlmMachine`, after obtaining the lock but not on the
2072    /// first time.
2073    ///
2074    /// Returns a guard to the lock, if it was obtained.
2075    pub async fn try_lock_store_once(&self) -> Result<Option<CrossProcessLockGuard>, Error> {
2076        match self.lock_store(CrossProcessLock::try_lock_once).await {
2077            Err(Error::CrossProcessLockError(e))
2078                if matches!(*e, CrossProcessLockError::Unobtained(_)) =>
2079            {
2080                Ok(None)
2081            }
2082            other => other,
2083        }
2084    }
2085
2086    /// If a lock was created with [`Self::enable_cross_process_store_lock`],
2087    /// locks the store with the given function, `acquire`.
2088    ///
2089    /// Reloads the `OlmMachine` after obtaining the lock, if the lock is dirty.
2090    ///
2091    /// Returns a guard to the lock if it was obtained.
2092    pub async fn lock_store<F: AcquireCrossProcessLockFn<LockableCryptoStore>>(
2093        &self,
2094        acquire: F,
2095    ) -> Result<Option<CrossProcessLockGuard>, Error> {
2096        let wrap_err = |e: CryptoStoreError| {
2097            Error::CrossProcessLockError(Box::new(CrossProcessLockError::TryLock(Arc::new(e))))
2098        };
2099        if let Some(lock) = self.client.locks().cross_process_crypto_store_lock.get() {
2100            let guard = acquire(lock).await.map_err(wrap_err)??;
2101            let _ = self.on_lock_newly_acquired().await?;
2102            Ok(Some(guard.into_guard()))
2103        } else {
2104            Ok(None)
2105        }
2106    }
2107
2108    /// Testing purposes only.
2109    #[cfg(any(test, feature = "testing"))]
2110    pub async fn uploaded_key_count(&self) -> Result<u64> {
2111        let olm_machine = self.client.olm_machine().await;
2112        let olm_machine = olm_machine.as_ref().ok_or(Error::AuthenticationRequired)?;
2113        Ok(olm_machine.uploaded_key_count().await?)
2114    }
2115
2116    /// Bootstrap encryption and enables event listeners for the E2EE support.
2117    ///
2118    /// Based on the `EncryptionSettings`, this call might:
2119    ///
2120    /// - Bootstrap cross-signing if needed (POST `/device_signing/upload`)
2121    /// - Create a key backup if needed (POST `/room_keys/version`)
2122    /// - Create a secret storage if needed (PUT `/account_data/{type}`)
2123    ///
2124    /// As part of this process, and if needed, the current device keys would be
2125    /// uploaded to the server, new account data would be added, and cross
2126    /// signing keys and signatures might be uploaded.
2127    ///
2128    /// Should be called once we created a [`OlmMachine`], i.e. after logging
2129    /// in.
2130    ///
2131    /// # Arguments
2132    ///
2133    /// - `auth_data` - Some requests may require re-authentication. To prevent
2134    ///   the user from having to re-enter their password (or use other
2135    ///   methods), we can provide the authentication data here. This is
2136    ///   necessary for uploading cross-signing keys. However, please note that
2137    ///   there is a proposal (MSC3967) to remove this requirement, which would
2138    ///   allow for the initial upload of cross-signing keys without
2139    ///   authentication, rendering this parameter obsolete.
2140    pub(crate) async fn spawn_initialization_task(&self, auth_data: Option<AuthData>) {
2141        // It's fine to be async here as we're only getting the lock protecting
2142        // the `OlmMachine`. Since the lock shouldn't be that contested right
2143        // after logging in we won't delay the login or restoration of the
2144        // Client.
2145        let bundle_receiver_task = if self.client.inner.enable_share_history_on_invite {
2146            Some(BundleReceiverTask::new(&self.client).await)
2147        } else {
2148            None
2149        };
2150
2151        let mut tasks = self.client.inner.e2ee.tasks.lock();
2152
2153        let this = self.clone();
2154
2155        tasks.setup_e2ee = Some(spawn(
2156            async move {
2157                // Update the current state first, so we don't have to wait for
2158                // the result of network requests
2159                this.update_verification_state().await;
2160
2161                if this.settings().auto_enable_cross_signing
2162                    && let Err(e) = this.bootstrap_cross_signing_if_needed(auth_data).await
2163                {
2164                    error!("Couldn't bootstrap cross signing {e:?}");
2165                }
2166
2167                if let Err(e) = this.backups().setup_and_resume().await {
2168                    error!("Couldn't setup and resume backups {e:?}");
2169                }
2170                if let Err(e) = this.recovery().setup().await {
2171                    error!("Couldn't setup and resume recovery {e:?}");
2172                }
2173            }
2174            .instrument(Span::current()),
2175        ));
2176
2177        tasks.receive_historic_room_key_bundles = bundle_receiver_task;
2178
2179        self.setup_room_membership_session_discard_handler();
2180    }
2181
2182    /// Waits for end-to-end encryption initialization tasks to finish, if any
2183    /// was running in the background.
2184    pub async fn wait_for_e2ee_initialization_tasks(&self) {
2185        let task = self.client.inner.e2ee.tasks.lock().setup_e2ee.take();
2186
2187        if let Some(task) = task
2188            && let Err(err) = task.await
2189        {
2190            warn!("Error when initializing backups: {err}");
2191        }
2192    }
2193
2194    /// Upload the device keys and initial set of one-time keys to the server.
2195    ///
2196    /// This should only be called when the user logs in for the first time, the
2197    /// method will ensure that other devices see our own device as an
2198    /// end-to-end encryption enabled one.
2199    ///
2200    /// **Warning**: Do not use this method if we're already calling
2201    /// [`Client::send_outgoing_request()`]. This method is intended for
2202    /// explicitly uploading the device keys before starting a sync.
2203    pub(crate) async fn ensure_device_keys_upload(&self) -> Result<()> {
2204        let olm = self.client.olm_machine().await;
2205        let olm = olm.as_ref().ok_or(Error::NoOlmMachine)?;
2206
2207        if let Some((request_id, request)) = olm.upload_device_keys().await? {
2208            self.client.keys_upload(&request_id, &request).await?;
2209
2210            let (request_id, request) = olm.query_keys_for_users([olm.user_id()]);
2211            self.client.keys_query(&request_id, request.device_keys).await?;
2212        }
2213
2214        Ok(())
2215    }
2216
2217    pub(crate) async fn update_state_after_keys_query(&self, response: &get_keys::v3::Response) {
2218        self.recovery().update_state_after_keys_query(response).await;
2219
2220        // Only update the verification_state if our own devices changed
2221        if let Some(user_id) = self.client.user_id() {
2222            let contains_own_device = response.device_keys.contains_key(user_id);
2223
2224            if contains_own_device {
2225                self.update_verification_state().await;
2226            }
2227        }
2228    }
2229
2230    async fn update_verification_state(&self) {
2231        match self.get_own_device().await {
2232            Ok(device) => {
2233                if let Some(device) = device {
2234                    let is_verified = device.is_cross_signed_by_owner();
2235
2236                    if is_verified {
2237                        self.client.inner.verification_state.set(VerificationState::Verified);
2238                    } else {
2239                        self.client.inner.verification_state.set(VerificationState::Unverified);
2240                    }
2241                } else {
2242                    warn!("Couldn't find out own device in the store.");
2243                    self.client.inner.verification_state.set(VerificationState::Unknown);
2244                }
2245            }
2246            Err(error) => {
2247                warn!("Failed retrieving own device: {error}");
2248                self.client.inner.verification_state.set(VerificationState::Unknown);
2249            }
2250        }
2251    }
2252
2253    /// Sets up a handler to rotate room keys when a user leaves a room.
2254    ///
2255    /// Previously, it was sufficient to check if we need to rotate the room key
2256    /// prior to sending a message. However, the history sharing feature
2257    /// ([MSC4268]) breaks this logic:
2258    ///
2259    /// 1. Alice sends a message M1 in room X;
2260    /// 2. Bob invites Charlie, who joins and immediately leaves the room;
2261    /// 3. Alice sends another message M2 in room X.
2262    ///
2263    /// Under the old logic, Alice would not rotate her key after Charlie
2264    /// leaves, resulting in M2 being encrypted with the same session as M1.
2265    /// This would allow Charlie to decrypt M2 if he ever gains access to the
2266    /// event.
2267    ///
2268    /// This handler listens for changes to the room membership, and discards
2269    /// the current room key if the event is a `leave` event.
2270    ///
2271    /// [MSC4268]: https://github.com/matrix-org/matrix-spec-proposals/pull/4268
2272    fn setup_room_membership_session_discard_handler(&self) {
2273        let client = WeakClient::from_client(&self.client);
2274        self.client.add_event_handler(|ev: OriginalSyncRoomMemberEvent, room: Room| async move {
2275            let Some(client) = client.get() else {
2276                // The main client has been dropped.
2277                return;
2278            };
2279            let Some(user_id) = client.user_id() else {
2280                // We aren't logged in, so this shouldn't ever happen.
2281                return;
2282            };
2283            let olm = client.olm_machine().await;
2284            let Some(olm) = olm.as_ref() else {
2285                warn!("Cannot discard session - Olm machine is not available");
2286                return;
2287            };
2288
2289            if matches!(
2290                ev.membership_change(),
2291                MembershipChange::Joined |
2292                MembershipChange::Invited |
2293                MembershipChange::KnockAccepted |
2294                MembershipChange::InvitationAccepted |
2295                MembershipChange::ProfileChanged { .. }
2296            ) || ev.sender == user_id {
2297                // We can ignore events that did not remove us, and those that we sent.
2298                return;
2299            }
2300
2301            debug!(room_id = ?room.room_id(), member_id = ?ev.sender, "Discarding session as a user left the room");
2302
2303            // Attempt to discard the current room key. This won't do anything
2304            // if we don't have one, but that's fine since we will create a new
2305            // room key whenever we try to send a message.
2306            if let Err(e) = olm.discard_room_key(room.room_id()).await {
2307                warn!(
2308                    room_id = ?room.room_id(),
2309                    "Error discarding room key after member leave: {e:?}"
2310                );
2311            }
2312        });
2313    }
2314
2315    /// Encrypts then send the given content via the `/sendToDevice` end-point
2316    /// using Olm encryption.
2317    ///
2318    /// If there are a lot of recipient devices multiple `/sendToDevice`
2319    /// requests might be sent out.
2320    ///
2321    /// # Returns
2322    ///
2323    /// A list of failures. The list of devices that couldn't get the messages.
2324    #[cfg(feature = "experimental-send-custom-to-device")]
2325    pub async fn encrypt_and_send_raw_to_device(
2326        &self,
2327        recipient_devices: Vec<&Device>,
2328        event_type: &str,
2329        content: Raw<AnyToDeviceEventContent>,
2330        share_strategy: CollectStrategy,
2331    ) -> Result<Vec<(OwnedUserId, OwnedDeviceId)>> {
2332        let users = recipient_devices.iter().map(|device| device.user_id());
2333
2334        // Will claim one-time-key for users that needs it TODO: For later
2335        // optimisation: This will establish missing olm sessions with all this
2336        // users devices, but we just want for some devices.
2337        self.client.claim_one_time_keys(users).await?;
2338
2339        let olm = self.client.olm_machine().await;
2340        let olm = olm.as_ref().expect("Olm machine wasn't started");
2341
2342        let (requests, withhelds) = olm
2343            .encrypt_content_for_devices(
2344                recipient_devices.into_iter().map(|d| d.deref().clone()).collect(),
2345                event_type,
2346                &content
2347                    .deserialize_as::<serde_json::Value>()
2348                    .expect("Deserialize as Value will always work"),
2349                share_strategy,
2350            )
2351            .await?;
2352
2353        let mut failures: Vec<(OwnedUserId, OwnedDeviceId)> = Default::default();
2354
2355        // Push the withhelds in the failures
2356        withhelds.iter().for_each(|(d, _)| {
2357            failures.push((d.user_id().to_owned(), d.device_id().to_owned()));
2358        });
2359
2360        // TODO: parallelize that? it's already grouping 250 devices per chunk.
2361        for request in requests {
2362            let ruma_request = RumaToDeviceRequest::new_raw(
2363                request.event_type.clone(),
2364                request.txn_id.clone(),
2365                request.messages.clone(),
2366            );
2367
2368            let send_result = self
2369                .client
2370                .send_inner(ruma_request, Some(RequestConfig::short_retry()), Default::default())
2371                .await;
2372
2373            // If the sending failed we need to collect the failures to report
2374            // them
2375            if send_result.is_err() {
2376                // Mark the sending as failed
2377                for (user_id, device_map) in request.messages {
2378                    for device_id in device_map.keys() {
2379                        match device_id {
2380                            DeviceIdOrAllDevices::DeviceId(device_id) => {
2381                                failures.push((user_id.clone(), device_id.to_owned()));
2382                            }
2383                            DeviceIdOrAllDevices::AllDevices => {
2384                                // Cannot happen in this case
2385                            }
2386                        }
2387                    }
2388                }
2389            }
2390        }
2391
2392        Ok(failures)
2393    }
2394}
2395
2396#[cfg(all(test, not(target_family = "wasm")))]
2397mod tests {
2398    use std::{
2399        ops::Not,
2400        str::FromStr,
2401        sync::{
2402            Arc,
2403            atomic::{AtomicBool, Ordering},
2404        },
2405    };
2406
2407    use matrix_sdk_test::{
2408        DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, async_test,
2409        event_factory::EventFactory,
2410    };
2411    use ruma::{
2412        event_id,
2413        events::{reaction::ReactionEventContent, relation::Annotation},
2414        user_id,
2415    };
2416    use serde_json::json;
2417    use wiremock::{
2418        Mock, MockServer, Request, ResponseTemplate,
2419        matchers::{header, method, path_regex},
2420    };
2421
2422    use crate::{
2423        Client, assert_next_matches_with_timeout,
2424        config::RequestConfig,
2425        encryption::{
2426            DuplicateOneTimeKeyErrorMessage, OAuthCrossSigningResetInfo, VerificationState,
2427        },
2428        test_utils::{
2429            client::mock_matrix_session, logged_in_client, no_retry_test_client, set_client_session,
2430        },
2431    };
2432
2433    #[async_test]
2434    async fn test_reaction_sending() {
2435        let server = MockServer::start().await;
2436        let client = logged_in_client(Some(server.uri())).await;
2437
2438        let event_id = event_id!("$2:example.org");
2439
2440        Mock::given(method("GET"))
2441            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/state/m.*room.*encryption.?"))
2442            .and(header("authorization", "Bearer 1234"))
2443            .respond_with(
2444                ResponseTemplate::new(200)
2445                    .set_body_json(EventFactory::new().room_encryption().into_content()),
2446            )
2447            .mount(&server)
2448            .await;
2449
2450        Mock::given(method("PUT"))
2451            .and(path_regex(r"^/_matrix/client/r0/rooms/.*/send/m\.reaction/.*".to_owned()))
2452            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2453                "event_id": event_id,
2454            })))
2455            .mount(&server)
2456            .await;
2457
2458        let f = EventFactory::new().sender(user_id!("@example:localhost"));
2459        let response = SyncResponseBuilder::default()
2460            .add_joined_room(
2461                JoinedRoomBuilder::default()
2462                    .add_state_event(
2463                        f.member(user_id!("@example:localhost")).display_name("example"),
2464                    )
2465                    .add_state_event(f.default_power_levels())
2466                    .add_state_event(f.room_encryption()),
2467            )
2468            .build_sync_response();
2469
2470        client.base_client().receive_sync_response(response).await.unwrap();
2471
2472        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Room should exist");
2473        assert!(
2474            room.latest_encryption_state().await.expect("Getting encryption state").is_encrypted()
2475        );
2476
2477        let event_id = event_id!("$1:example.org");
2478        let reaction = ReactionEventContent::new(Annotation::new(event_id.into(), "🐈".to_owned()));
2479        room.send(reaction).await.expect("Sending the reaction should not fail");
2480
2481        room.send_raw("m.reaction", json!({})).await.expect("Sending the reaction should not fail");
2482    }
2483
2484    #[cfg(feature = "sqlite")]
2485    #[async_test]
2486    async fn test_generation_counter_invalidates_olm_machine() {
2487        // Create two clients using the same sqlite database.
2488
2489        use matrix_sdk_base::store::RoomLoadSettings;
2490        let tmp_dir = tempfile::tempdir().unwrap();
2491        let sqlite_path = tmp_dir.path().join("generation_counter_sqlite.db");
2492        let session = mock_matrix_session();
2493
2494        let client1 = Client::builder()
2495            .homeserver_url("http://localhost:1234")
2496            .request_config(RequestConfig::new().disable_retry())
2497            .sqlite_store(&sqlite_path, None)
2498            .build()
2499            .await
2500            .unwrap();
2501        client1
2502            .matrix_auth()
2503            .restore_session(session.clone(), RoomLoadSettings::default())
2504            .await
2505            .unwrap();
2506
2507        let client2 = Client::builder()
2508            .homeserver_url("http://localhost:1234")
2509            .request_config(RequestConfig::new().disable_retry())
2510            .sqlite_store(sqlite_path, None)
2511            .build()
2512            .await
2513            .unwrap();
2514        client2.matrix_auth().restore_session(session, RoomLoadSettings::default()).await.unwrap();
2515
2516        // When the lock isn't enabled, any attempt at locking won't return a
2517        // guard.
2518        let guard = client1.encryption().try_lock_store_once().await.unwrap();
2519        assert!(guard.is_none());
2520
2521        client1.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2522        client2.encryption().enable_cross_process_store_lock("client2".to_owned()).await.unwrap();
2523
2524        // One client can take the lock.
2525        let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2526        assert!(acquired1.is_some());
2527
2528        // Keep the olm machine, so we can see if it's changed later, by
2529        // comparing Arcs.
2530        let initial_olm_machine =
2531            client1.olm_machine().await.clone().expect("must have an olm machine");
2532
2533        // Also enable backup to check that new machine has the same backup
2534        // keys.
2535        let decryption_key = matrix_sdk_base::crypto::store::types::BackupDecryptionKey::new();
2536        let backup_key = decryption_key.megolm_v1_public_key();
2537        backup_key.set_version("1".to_owned());
2538        initial_olm_machine
2539            .backup_machine()
2540            .save_decryption_key(Some(decryption_key.to_owned()), Some("1".to_owned()))
2541            .await
2542            .expect("Should save");
2543
2544        initial_olm_machine.backup_machine().enable_backup_v1(backup_key.clone()).await.unwrap();
2545
2546        assert!(client1.encryption().backups().are_enabled().await);
2547
2548        // The other client can't take the lock too.
2549        let acquired2 = client2.encryption().try_lock_store_once().await.unwrap();
2550        assert!(acquired2.is_none());
2551
2552        // Now have the first client release the lock,
2553        drop(acquired1);
2554
2555        // And re-take it.
2556        let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2557        assert!(acquired1.is_some());
2558
2559        // In that case, the Olm Machine shouldn't change.
2560        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2561        assert!(initial_olm_machine.same_as(&olm_machine));
2562
2563        // Ok, release again.
2564        drop(acquired1);
2565
2566        // Client2 can acquire the lock.
2567        let acquired2 = client2.encryption().spin_lock_store(None).await.unwrap();
2568        assert!(acquired2.is_some());
2569
2570        // And then release it.
2571        drop(acquired2);
2572
2573        // Client1 can acquire it again,
2574        let acquired1 = client1.encryption().spin_lock_store(None).await.unwrap();
2575        assert!(acquired1.is_some());
2576
2577        // But now its olm machine has been invalidated and thus regenerated!
2578        let olm_machine = client1.olm_machine().await.clone().expect("must have an olm machine");
2579
2580        assert!(!initial_olm_machine.same_as(&olm_machine));
2581
2582        let backup_key_new = olm_machine.backup_machine().get_backup_keys().await.unwrap();
2583        assert!(backup_key_new.decryption_key.is_some());
2584        assert_eq!(
2585            backup_key_new.decryption_key.unwrap().megolm_v1_public_key().to_base64(),
2586            backup_key.to_base64()
2587        );
2588        assert!(client1.encryption().backups().are_enabled().await);
2589    }
2590
2591    #[cfg(feature = "sqlite")]
2592    #[async_test]
2593    async fn test_generation_counter_no_spurious_invalidation() {
2594        // Create two clients using the same sqlite database.
2595
2596        use matrix_sdk_base::store::RoomLoadSettings;
2597        let tmp_dir = tempfile::tempdir().unwrap();
2598        let sqlite_path = tmp_dir.path().join("generation_counter_no_spurious_invalidations.db");
2599        let session = mock_matrix_session();
2600
2601        let client = Client::builder()
2602            .homeserver_url("http://localhost:1234")
2603            .request_config(RequestConfig::new().disable_retry())
2604            .sqlite_store(&sqlite_path, None)
2605            .build()
2606            .await
2607            .unwrap();
2608        client
2609            .matrix_auth()
2610            .restore_session(session.clone(), RoomLoadSettings::default())
2611            .await
2612            .unwrap();
2613
2614        let initial_olm_machine = client.olm_machine().await.as_ref().unwrap().clone();
2615
2616        client.encryption().enable_cross_process_store_lock("client1".to_owned()).await.unwrap();
2617
2618        // Enabling the lock doesn't update the olm machine.
2619        let after_enabling_lock = client.olm_machine().await.as_ref().unwrap().clone();
2620        assert!(initial_olm_machine.same_as(&after_enabling_lock));
2621
2622        {
2623            // Simulate that another client hold the lock before.
2624            let client2 = Client::builder()
2625                .homeserver_url("http://localhost:1234")
2626                .request_config(RequestConfig::new().disable_retry())
2627                .sqlite_store(sqlite_path, None)
2628                .build()
2629                .await
2630                .unwrap();
2631            client2
2632                .matrix_auth()
2633                .restore_session(session, RoomLoadSettings::default())
2634                .await
2635                .unwrap();
2636
2637            client2
2638                .encryption()
2639                .enable_cross_process_store_lock("client2".to_owned())
2640                .await
2641                .unwrap();
2642
2643            let guard = client2.encryption().spin_lock_store(None).await.unwrap();
2644            assert!(guard.is_some());
2645
2646            drop(guard);
2647        }
2648
2649        {
2650            let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2651            assert!(acquired.is_some());
2652        }
2653
2654        // Taking the lock the first time will update the olm machine.
2655        let after_taking_lock_first_time = client.olm_machine().await.as_ref().unwrap().clone();
2656        assert!(!initial_olm_machine.same_as(&after_taking_lock_first_time));
2657
2658        {
2659            let acquired = client.encryption().spin_lock_store(None).await.unwrap();
2660            assert!(acquired.is_some());
2661        }
2662
2663        // Re-taking the lock doesn't update the olm machine.
2664        let after_taking_lock_second_time = client.olm_machine().await.as_ref().unwrap().clone();
2665        assert!(after_taking_lock_first_time.same_as(&after_taking_lock_second_time));
2666    }
2667
2668    #[async_test]
2669    async fn test_update_verification_state_is_updated_before_any_requests_happen() {
2670        // Given a client and a server
2671        let client = no_retry_test_client(None).await;
2672        let server = MockServer::start().await;
2673
2674        // When we subscribe to its verification state
2675        let mut verification_state = client.encryption().verification_state();
2676
2677        // We can get its initial value, and it's Unknown
2678        assert_next_matches_with_timeout!(verification_state, VerificationState::Unknown);
2679
2680        // We set up a mocked request to check this endpoint is not called
2681        // before reading the new state
2682        let keys_requested = Arc::new(AtomicBool::new(false));
2683        let inner_bool = keys_requested.clone();
2684
2685        Mock::given(method("GET"))
2686            .and(path_regex(
2687                r"/_matrix/client/r0/user/.*/account_data/m.secret_storage.default_key",
2688            ))
2689            .respond_with(move |_req: &Request| {
2690                inner_bool.fetch_or(true, Ordering::SeqCst);
2691                ResponseTemplate::new(200).set_body_json(json!({}))
2692            })
2693            .mount(&server)
2694            .await;
2695
2696        // When the session is initialised and the encryption tasks spawn
2697        set_client_session(&client).await;
2698
2699        // Then we can get an updated value without waiting for any network
2700        // requests
2701        assert!(keys_requested.load(Ordering::SeqCst).not());
2702        assert_next_matches_with_timeout!(verification_state, VerificationState::Unverified);
2703    }
2704
2705    #[test]
2706    fn test_oauth_reset_info_from_uiaa_info() {
2707        let auth_info = json!({
2708            "session": "dummy",
2709            "flows": [
2710                {
2711                    "stages": [
2712                        "org.matrix.cross_signing_reset"
2713                    ]
2714                }
2715            ],
2716            "params": {
2717                "org.matrix.cross_signing_reset": {
2718                    "url": "https://example.org/account/account?action=org.matrix.cross_signing_reset"
2719                }
2720            },
2721            "msg": "To reset..."
2722        });
2723
2724        let auth_info = serde_json::from_value(auth_info)
2725            .expect("We should be able to deserialize the UiaaInfo");
2726        OAuthCrossSigningResetInfo::from_auth_info(&auth_info)
2727            .expect("We should be able to fetch the cross-signing reset info from the auth info");
2728    }
2729
2730    #[test]
2731    fn test_duplicate_one_time_key_error_parsing() {
2732        let message = concat!(
2733            r#"One time key signed_curve25519:AAAAAAAAAAA already exists. "#,
2734            r#"Old key: {"key":"dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs","#,
2735            r#""signatures":{"@example:matrix.org":{"ed25519:AAAAAAAAAA":""#,
2736            r#"Fk45zHAbrd+1j9wZXLjL2Y/+DU/Mnz9yuvlfYBOOT7qExN2Jdud+5BAuNs8nZ/caS4wTF39Kg3zQpzaGERoCBg"}}};"#,
2737            r#" new key: {'key': 'CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno', "#,
2738            r#"'signatures': {'@example:matrix.org': {'ed25519:AAAAAAAAAA': "#,
2739            r#"'BQ9Gp0p+6srF+c8OyruqKKd9R4yaub3THYAyyBB/7X/rG8BwcAqFynzl1aGyFYun4Q+087a5OSiglCXI+/kQAA'}}}"#
2740        );
2741        let message = DuplicateOneTimeKeyErrorMessage::from_str(message)
2742            .expect("We should be able to parse the error message");
2743
2744        assert_eq!(message.old_key.to_base64(), "dBcZBzQaiQYWf6rBPh2QypIOB/dxSoTeyaFaxNNbeHs");
2745        assert_eq!(message.new_key.to_base64(), "CY0TWVK1/Kj3ZADuBcGe3UKvpT+IKAPMUsMeJhSDqno");
2746
2747        DuplicateOneTimeKeyErrorMessage::from_str("One time key already exists.")
2748            .expect_err("We shouldn't be able to parse an incomplete error message");
2749    }
2750
2751    // Helper function for the test_devices_to_verify_against_* tests. Make a
2752    // response to a /keys/query request using the given device keys and a
2753    // pre-defined set of cross-signing keys.
2754    fn devices_to_verify_against_keys_query_response(
2755        devices: Vec<serde_json::Value>,
2756    ) -> serde_json::Value {
2757        let device_keys: serde_json::Map<String, serde_json::Value> = devices
2758            .into_iter()
2759            .map(|device| (device.get("device_id").unwrap().as_str().unwrap().to_owned(), device))
2760            .collect();
2761        json!({
2762            "device_keys": {
2763                "@example:localhost": device_keys,
2764            },
2765            "master_keys": {
2766                "@example:localhost": {
2767                    "keys": {
2768                        "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU",
2769                    },
2770                    "usage": ["master"],
2771                    "user_id": "@example:localhost",
2772                },
2773            },
2774            "self_signing_keys": {
2775                "@example:localhost": {
2776                    "keys": {
2777                        "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM",
2778                    },
2779                    "usage": ["self_signing"],
2780                    "user_id": "@example:localhost",
2781                    "signatures": {
2782                        "@example:localhost": {
2783                            "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "etO1bB+rCk+TQ/FcjQ8eWu/RsRNQNNQ1Ek+PD6//j8yz6igRjfvuHZaMvr/quAFrirfgExph2TdOwlDgN5bFCQ",
2784                        },
2785                    },
2786                },
2787            },
2788            "user_signing_keys": {
2789                "@example:localhost": {
2790                    "keys": {
2791                        "ed25519:CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0": "CBaovtekFxzf2Ijjhk4B49drOH0/qmhBbptFlVW7HC0",
2792                    },
2793                    "usage": ["user_signing"],
2794                    "user_id": "@example:localhost",
2795                    "signatures": {
2796                        "@example:localhost": {
2797                            "ed25519:PJklDgml7Xtt1Wr8jsWvB+lC5YD/bVDpHL+fYuItNxU": "E/DFi/hQTIb/7eSB+HbCXeTLFaLjqWHzLO9GwjL1qdhfO7ew4p6YdtXSH3T2YYr1dKCPteH/4nMYVwOhww2CBg",
2798                        },
2799                    },
2800                },
2801            }
2802        })
2803    }
2804
2805    // The following three tests test that we can detect whether the user has
2806    // other devices that they can verify against under different conditions.
2807    #[async_test]
2808    /// Test that we detect that can't verify against another device if we have
2809    /// no devices.
2810    async fn test_devices_to_verify_against_no_devices() {
2811        let server = MockServer::start().await;
2812        let client = logged_in_client(Some(server.uri())).await;
2813
2814        Mock::given(method("POST"))
2815            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2816            .respond_with(
2817                ResponseTemplate::new(200)
2818                    .set_body_json(devices_to_verify_against_keys_query_response(vec![])),
2819            )
2820            .mount(&server)
2821            .await;
2822
2823        assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2824    }
2825
2826    #[async_test]
2827    /// Test that we detect that we can verify against another cross-signed
2828    /// regular device.
2829    async fn test_devices_to_verify_against_cross_signed() {
2830        let server = MockServer::start().await;
2831        let client = logged_in_client(Some(server.uri())).await;
2832
2833        Mock::given(method("POST"))
2834            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2835            .respond_with(ResponseTemplate::new(200).set_body_json(
2836                devices_to_verify_against_keys_query_response(vec![
2837                    json!({
2838                        "algorithms": [
2839                            "m.olm.v1.curve25519-aes-sha2",
2840                            "m.megolm.v1.aes-sha2",
2841                        ],
2842                        "user_id": "@example:localhost",
2843                        "device_id": "SIGNEDDEVICE",
2844                        "keys": {
2845                            "curve25519:SIGNEDDEVICE": "o1LqUtH/sqd3WF+BB2Qr77uw3sDmZhMOz68/IV9aHxs",
2846                            "ed25519:SIGNEDDEVICE": "iVoEfMOoUqxXVMLdpZCOgvQuCrT3/kQWkBmB3Phi/lo",
2847                        },
2848                        "signatures": {
2849                            "@example:localhost": {
2850                                "ed25519:SIGNEDDEVICE": "C7yRu1fNrdD2EobVdtANMqk3LBtWtTRWrIU22xVS8/Om1kmA/luzek64R3N6JsZhYczVmZYBKhUC9kRvHHwOBg",
2851                                "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "frfh2HP28GclmGvwTic00Fj4nZCvm4RlRA6U56mnD5920hOi04+L055ojzp6ybZXvC/GQYfyTHwQXlUN1nvxBA",
2852                            },
2853                        },
2854                    })
2855                ])
2856            ))
2857            .mount(&server)
2858            .await;
2859
2860        assert!(client.encryption().has_devices_to_verify_against().await.unwrap());
2861    }
2862
2863    #[async_test]
2864    /// Test that we detect that we can't verify against a dehydrated or
2865    /// unsigned device.
2866    async fn test_devices_to_verify_against_dehydrated_and_unsigned() {
2867        let server = MockServer::start().await;
2868        let client = logged_in_client(Some(server.uri())).await;
2869        let user_id = client.user_id().unwrap();
2870        let olm_machine = client.olm_machine().await;
2871        let olm_machine = olm_machine.as_ref().unwrap();
2872
2873        Mock::given(method("POST"))
2874            .and(path_regex(r"^/_matrix/client/r0/keys/query".to_owned()))
2875            .respond_with(ResponseTemplate::new(200).set_body_json(
2876                devices_to_verify_against_keys_query_response(vec![
2877                    json!({
2878                        "algorithms": [
2879                            "m.olm.v1.curve25519-aes-sha2",
2880                            "m.megolm.v1.aes-sha2",
2881                        ],
2882                        "user_id": "@example:localhost",
2883                        "device_id": "DEHYDRATEDDEVICE",
2884                        "keys": {
2885                            "curve25519:DEHYDRATEDDEVICE": "XOn5VguAgokZ3p9mBz2yOB395fn6j75G8jIPcXEWQGY",
2886                            "ed25519:DEHYDRATEDDEVICE": "4GG5xmBT7z4rgUgmWNlKZ+ABE3QlGgTorF+luCnKfYI",
2887                        },
2888                        "dehydrated": true,
2889                        "signatures": {
2890                            "@example:localhost": {
2891                                "ed25519:DEHYDRATEDDEVICE": "+OMasB7nzVlMV+zRDxkh4h8h/Q0bY42P1SPv7X2IURIelT5G+d+AYSmg30N4maphxEDBqt/vI8/lIr71exc3Dg",
2892                                "ed25519:jobZVcxG+PBLwZMsF4XEJSJTVqOgDxd0Ud3J/bw3HYM": "8DzynAgbYgXX1Md5d4Vw91Zstpoi4dpG7levFeVhi4psCAWuBnV76Qu1s2TGjQQ0CLDXEqcxxuX9X4eUK5TGCg",
2893                            },
2894                        },
2895                    }),
2896                    json!({
2897                        "algorithms": [
2898                            "m.olm.v1.curve25519-aes-sha2",
2899                            "m.megolm.v1.aes-sha2",
2900                        ],
2901                        "user_id": "@example:localhost",
2902                        "device_id": "UNSIGNEDDEVICE",
2903                        "keys": {
2904                            "curve25519:UNSIGNEDDEVICE": "mMby6NpprkHxj+ONfO9Z5lBqVUHJBMkrPFSNJhogBkg",
2905                            "ed25519:UNSIGNEDDEVICE": "Zifq39ZDrlIaSRf0Hh22owEqXCPE+1JSSgs6LDlubwQ",
2906                        },
2907                        "signatures": {
2908                            "@example:localhost": {
2909                                "ed25519:UNSIGNEDDEVICE": "+L29RoDKoTufPGm/Bae65KHno7Z1H7GYhxSKpB4RQZRS7NrR29AMW1PVhEsIozYuDVEFuMZ0L8H3dlcaHxagBA",
2910                            },
2911                        },
2912                    }),
2913                ])
2914            ))
2915            .mount(&server)
2916            .await;
2917
2918        let (request_id, request) = olm_machine.query_keys_for_users([user_id]);
2919        client.keys_query(&request_id, request.device_keys).await.unwrap();
2920
2921        assert!(!client.encryption().has_devices_to_verify_against().await.unwrap());
2922    }
2923
2924    #[cfg(feature = "experimental-send-custom-to-device")]
2925    mod resolve_recipient_devices {
2926        use matrix_sdk_test::async_test;
2927        use ruma::{
2928            OwnedDeviceId, device_id, owned_device_id, to_device::DeviceIdOrAllDevices, user_id,
2929        };
2930
2931        use super::super::Device;
2932        use crate::{Client, test_utils::mocks::MatrixMockServer};
2933
2934        const BOB_FIRST_DEVICE: &str = "B0B0B0B0B";
2935        const BOB_SECOND_DEVICE: &str = "B0B2B0B2";
2936        const BOB_THIRD_DEVICE: &str = "B0B3B0B3";
2937
2938        /// Set up Alice and Bob, where Bob has two devices that Alice knows
2939        /// about.
2940        ///
2941        /// The [`MatrixMockServer`] is returned so that it stays alive for the
2942        /// duration of the test.
2943        async fn alice_and_bob_with_two_bob_devices() -> (MatrixMockServer, Client, Client) {
2944            let server = MatrixMockServer::new().await;
2945            server.mock_crypto_endpoints_preset().await;
2946
2947            let (alice, bob) = server.set_up_alice_and_bob_for_encryption().await;
2948            assert_eq!(bob.device_id().unwrap(), device_id!(BOB_FIRST_DEVICE));
2949
2950            server
2951                .set_up_new_device_for_encryption(&bob, device_id!(BOB_SECOND_DEVICE), vec![&alice])
2952                .await;
2953
2954            // Let Alice download Bob's second device.
2955            server
2956                .mock_sync()
2957                .ok_and_run(&alice, |builder| {
2958                    builder.add_change_device(bob.user_id().unwrap());
2959                })
2960                .await;
2961
2962            (server, alice, bob)
2963        }
2964
2965        /// The device IDs of the given devices, sorted so that we can assert on
2966        /// them.
2967        fn device_ids(devices: Vec<Device>) -> Vec<OwnedDeviceId> {
2968            let mut device_ids: Vec<_> =
2969                devices.into_iter().map(|d| d.device_id().to_owned()).collect();
2970            device_ids.sort();
2971            device_ids
2972        }
2973
2974        #[async_test]
2975        async fn test_all_devices_resolves_to_every_known_device() {
2976            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2977
2978            let (devices, unknown_devices) = alice
2979                .resolve_recipient_devices(
2980                    bob.user_id().unwrap(),
2981                    vec![DeviceIdOrAllDevices::AllDevices],
2982                )
2983                .await
2984                .unwrap();
2985
2986            assert_eq!(
2987                device_ids(devices),
2988                vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
2989            );
2990            assert!(unknown_devices.is_empty(), "`AllDevices` can't reference an unknown device");
2991        }
2992
2993        #[async_test]
2994        async fn test_explicit_device_ids_are_filtered_to_the_requested_ones() {
2995            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
2996
2997            let (devices, unknown_devices) = alice
2998                .resolve_recipient_devices(
2999                    bob.user_id().unwrap(),
3000                    vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_SECOND_DEVICE))],
3001                )
3002                .await
3003                .unwrap();
3004
3005            assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_SECOND_DEVICE)]);
3006            assert!(unknown_devices.is_empty());
3007        }
3008
3009        /// Asking for several device IDs resolves all of them, and only them.
3010        #[async_test]
3011        async fn test_several_explicit_device_ids_are_all_resolved() {
3012            let (server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3013
3014            server
3015                .set_up_new_device_for_encryption(&bob, device_id!(BOB_THIRD_DEVICE), vec![&alice])
3016                .await;
3017
3018            // Let Alice download Bob's third device.
3019            server
3020                .mock_sync()
3021                .ok_and_run(&alice, |builder| {
3022                    builder.add_change_device(bob.user_id().unwrap());
3023                })
3024                .await;
3025
3026            let (devices, unknown_devices) = alice
3027                .resolve_recipient_devices(
3028                    bob.user_id().unwrap(),
3029                    vec![
3030                        DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3031                        DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_THIRD_DEVICE)),
3032                    ],
3033                )
3034                .await
3035                .unwrap();
3036
3037            assert_eq!(
3038                device_ids(devices),
3039                vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_THIRD_DEVICE)],
3040                "the second device was not requested and must be left out"
3041            );
3042            assert!(unknown_devices.is_empty());
3043        }
3044
3045        #[async_test]
3046        async fn test_unknown_device_is_reported_back() {
3047            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3048
3049            let (devices, unknown_devices) = alice
3050                .resolve_recipient_devices(
3051                    bob.user_id().unwrap(),
3052                    vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE"))],
3053                )
3054                .await
3055                .unwrap();
3056
3057            assert!(devices.is_empty());
3058            assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3059        }
3060
3061        /// A single unknown device must not prevent the known ones from
3062        /// receiving the message.
3063        #[async_test]
3064        async fn test_known_and_unknown_devices_are_split() {
3065            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3066
3067            let (devices, unknown_devices) = alice
3068                .resolve_recipient_devices(
3069                    bob.user_id().unwrap(),
3070                    vec![
3071                        DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE)),
3072                        DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3073                    ],
3074                )
3075                .await
3076                .unwrap();
3077
3078            assert_eq!(device_ids(devices), vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3079            assert_eq!(unknown_devices, vec![owned_device_id!("UNKNOWNDEVICE")]);
3080        }
3081
3082        /// If `AllDevices` is mixed with explicit device IDs, `AllDevices` wins
3083        /// and the explicit entries are ignored, even if they are unknown to
3084        /// us.
3085        #[async_test]
3086        async fn test_all_devices_takes_precedence_over_explicit_device_ids() {
3087            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3088
3089            let (devices, unknown_devices) = alice
3090                .resolve_recipient_devices(
3091                    bob.user_id().unwrap(),
3092                    vec![
3093                        DeviceIdOrAllDevices::AllDevices,
3094                        DeviceIdOrAllDevices::DeviceId(owned_device_id!("UNKNOWNDEVICE")),
3095                    ],
3096                )
3097                .await
3098                .unwrap();
3099
3100            assert_eq!(
3101                device_ids(devices),
3102                vec![owned_device_id!(BOB_FIRST_DEVICE), owned_device_id!(BOB_SECOND_DEVICE)]
3103            );
3104            assert!(unknown_devices.is_empty());
3105        }
3106
3107        /// An empty recipient list targets nobody, it is not a synonym for
3108        /// `AllDevices`.
3109        #[async_test]
3110        async fn test_empty_recipient_list_resolves_to_no_device() {
3111            let (_server, alice, bob) = alice_and_bob_with_two_bob_devices().await;
3112
3113            let (devices, unknown_devices) =
3114                alice.resolve_recipient_devices(bob.user_id().unwrap(), vec![]).await.unwrap();
3115
3116            assert!(devices.is_empty());
3117            assert!(unknown_devices.is_empty());
3118        }
3119
3120        /// A user we don't know anything about has no devices, and every device
3121        /// explicitly asked for is unknown.
3122        #[async_test]
3123        async fn test_unknown_user_has_no_device() {
3124            let (_server, alice, _bob) = alice_and_bob_with_two_bob_devices().await;
3125            let unknown_user_id = user_id!("@carol:example.org");
3126
3127            let (devices, unknown_devices) = alice
3128                .resolve_recipient_devices(
3129                    unknown_user_id,
3130                    vec![DeviceIdOrAllDevices::DeviceId(owned_device_id!(BOB_FIRST_DEVICE))],
3131                )
3132                .await
3133                .unwrap();
3134
3135            assert!(devices.is_empty());
3136            assert_eq!(unknown_devices, vec![owned_device_id!(BOB_FIRST_DEVICE)]);
3137
3138            // `AllDevices` for an unknown user is not an error either, it just
3139            // resolves to nothing.
3140            let (devices, unknown_devices) = alice
3141                .resolve_recipient_devices(unknown_user_id, vec![DeviceIdOrAllDevices::AllDevices])
3142                .await
3143                .unwrap();
3144
3145            assert!(devices.is_empty());
3146            assert!(unknown_devices.is_empty());
3147        }
3148    }
3149}