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