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