1use std::collections::{BTreeMap, BTreeSet};
24
25use futures_core::Stream;
26use futures_util::StreamExt;
27use matrix_sdk_base::crypto::{
28 backups::MegolmV1BackupKey,
29 store::types::BackupDecryptionKey,
30 types::{requests::KeysBackupRequest, RoomKeyBackupInfo},
31 OlmMachine, RoomKeyImportResult,
32};
33use ruma::{
34 api::client::{
35 backup::{
36 add_backup_keys, create_backup_version, get_backup_keys, get_backup_keys_for_room,
37 get_backup_keys_for_session, get_latest_backup_info, RoomKeyBackup,
38 },
39 error::ErrorKind,
40 },
41 events::{
42 room::encrypted::OriginalSyncRoomEncryptedEvent,
43 secret::{request::SecretName, send::ToDeviceSecretSendEvent},
44 },
45 serde::Raw,
46 OwnedRoomId, RoomId, TransactionId,
47};
48use tokio_stream::wrappers::{errors::BroadcastStreamRecvError, BroadcastStream};
49use tracing::{error, info, instrument, trace, warn, Span};
50
51pub mod futures;
52pub(crate) mod types;
53
54pub use types::{BackupState, UploadState};
55
56use self::futures::WaitForSteadyState;
57use crate::{
58 crypto::olm::ExportedRoomKey, encryption::BackupDownloadStrategy, Client, Error, Room,
59};
60
61#[derive(Debug, Clone)]
63pub struct Backups {
64 pub(super) client: Client,
65}
66
67impl Backups {
68 pub async fn create(&self) -> Result<(), Error> {
93 self.client.inner.e2ee.backup_state.clear_backup_exists_on_server();
94 let _guard = self.client.locks().backup_modify_lock.lock().await;
95
96 self.set_state(BackupState::Creating);
97
98 let future = async {
101 let olm_machine = self.client.olm_machine().await;
102 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
103
104 let decryption_key = BackupDecryptionKey::new().expect(
106 "We should be able to generate enough randomness to create a new backup recovery \
107 key",
108 );
109
110 let mut backup_info = decryption_key.to_backup_info();
132
133 if let Err(e) = olm_machine.backup_machine().sign_backup(&mut backup_info).await {
134 warn!("Unable to sign the newly created backup version: {e:?}");
135 }
136
137 let algorithm = Raw::new(&backup_info)?.cast();
138 let request = create_backup_version::v3::Request::new(algorithm);
139 let response = self.client.send(request).await?;
140 let version = response.version;
141
142 olm_machine.backup_machine().disable_backup().await?;
145
146 let backup_key = decryption_key.megolm_v1_public_key();
147
148 olm_machine
150 .backup_machine()
151 .save_decryption_key(Some(decryption_key), Some(version.to_owned()))
152 .await?;
153
154 self.enable(olm_machine, backup_key, version).await?;
156
157 Ok(())
158 };
159
160 let result = future.await;
161
162 if result.is_err() {
163 self.set_state(BackupState::Unknown);
164 }
165
166 result
167 }
168
169 #[instrument(skip_all, fields(version))]
190 pub async fn disable(&self) -> Result<(), Error> {
191 let _guard = self.client.locks().backup_modify_lock.lock().await;
192
193 self.set_state(BackupState::Disabling);
194
195 let future = async {
197 let olm_machine = self.client.olm_machine().await;
198 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
199
200 let backup_keys = olm_machine.backup_machine().get_backup_keys().await?;
201
202 if let Some(version) = backup_keys.backup_version {
203 Span::current().record("version", &version);
204 info!("Deleting and disabling backup");
205
206 self.delete_backup_from_server(version).await?;
207 info!("Backup successfully deleted");
208
209 olm_machine.backup_machine().disable_backup().await?;
210
211 info!("Backup successfully disabled and deleted");
212
213 Ok(())
214 } else {
215 info!("Backup is not enabled, can't disable it");
216 Err(Error::BackupNotEnabled)
217 }
218 };
219
220 let result = future.await;
221
222 self.set_state(BackupState::Unknown);
223
224 result
225 }
226
227 pub async fn disable_and_delete(&self) -> Result<(), Error> {
253 let _guard = self.client.locks().backup_modify_lock.lock().await;
254
255 self.set_state(BackupState::Disabling);
256
257 let future = async {
259 let response = self.get_current_version().await?;
260
261 if let Some(response) = response {
262 self.delete_backup_from_server(response.version).await?;
263 }
264
265 let olm_machine = self.client.olm_machine().await;
266 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
267
268 olm_machine.backup_machine().disable_backup().await?;
269
270 Ok(())
271 };
272
273 let result = future.await;
274
275 self.set_state(BackupState::Unknown);
276
277 result
278 }
279
280 pub fn wait_for_steady_state(&self) -> WaitForSteadyState<'_> {
324 WaitForSteadyState {
325 backups: self,
326 progress: self.client.inner.e2ee.backup_state.upload_progress.clone(),
327 timeout: None,
328 }
329 }
330
331 pub fn state_stream(
362 &self,
363 ) -> impl Stream<Item = Result<BackupState, BroadcastStreamRecvError>> {
364 self.client.inner.e2ee.backup_state.global_state.subscribe()
365 }
366
367 pub fn state(&self) -> BackupState {
369 self.client.inner.e2ee.backup_state.global_state.get()
370 }
371
372 pub async fn are_enabled(&self) -> bool {
377 let olm_machine = self.client.olm_machine().await;
378
379 if let Some(machine) = olm_machine.as_ref() {
380 machine.backup_machine().enabled().await
381 } else {
382 false
383 }
384 }
385
386 pub async fn fetch_exists_on_server(&self) -> Result<bool, Error> {
391 let exists_on_server = self.get_current_version().await?.is_some();
392 self.client.inner.e2ee.backup_state.set_backup_exists_on_server(exists_on_server);
393 Ok(exists_on_server)
394 }
395
396 pub async fn exists_on_server(&self) -> Result<bool, Error> {
407 if let Some(cached_value) = self.client.inner.e2ee.backup_state.backup_exists_on_server() {
409 return Ok(cached_value);
410 }
411
412 self.fetch_exists_on_server().await
415 }
416
417 pub fn room_keys_for_room_stream(
420 &self,
421 room_id: &RoomId,
422 ) -> impl Stream<Item = Result<BTreeMap<String, BTreeSet<String>>, BroadcastStreamRecvError>>
423 {
424 let room_id = room_id.to_owned();
425
426 self.room_keys_stream().filter_map(move |import_result| {
434 let room_id = room_id.to_owned();
435
436 async move {
437 match import_result {
438 Ok(mut import_result) => import_result.keys.remove(&room_id).map(Ok),
439 Err(e) => Some(Err(e)),
440 }
441 }
442 })
443 }
444
445 pub async fn download_room_keys_for_room(&self, room_id: &RoomId) -> Result<(), Error> {
448 let olm_machine = self.client.olm_machine().await;
449 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
450
451 let backup_keys = olm_machine.store().load_backup_keys().await?;
452
453 if let Some(decryption_key) = backup_keys.decryption_key {
454 if let Some(version) = backup_keys.backup_version {
455 let request =
456 get_backup_keys_for_room::v3::Request::new(version.clone(), room_id.to_owned());
457 let response = self.client.send(request).await?;
458
459 let response = get_backup_keys::v3::Response::new(BTreeMap::from([(
461 room_id.to_owned(),
462 RoomKeyBackup::new(response.sessions),
463 )]));
464
465 self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine)
466 .await?;
467 }
468 }
469
470 Ok(())
471 }
472
473 pub async fn download_room_key(
480 &self,
481 room_id: &RoomId,
482 session_id: &str,
483 ) -> Result<bool, Error> {
484 let olm_machine = self.client.olm_machine().await;
485 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
486
487 let backup_keys = olm_machine.store().load_backup_keys().await?;
488
489 if let Some(decryption_key) = backup_keys.decryption_key {
490 if let Some(version) = backup_keys.backup_version {
491 let request = get_backup_keys_for_session::v3::Request::new(
492 version.clone(),
493 room_id.to_owned(),
494 session_id.to_owned(),
495 );
496 let response = self.client.send(request).await?;
497
498 let response = get_backup_keys::v3::Response::new(BTreeMap::from([(
500 room_id.to_owned(),
501 RoomKeyBackup::new(BTreeMap::from([(
502 session_id.to_owned(),
503 response.key_data,
504 )])),
505 )]));
506
507 self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine)
508 .await?;
509
510 Ok(true)
511 } else {
512 Ok(false)
513 }
514 } else {
515 Ok(false)
516 }
517 }
518
519 fn set_state(&self, new_state: BackupState) {
521 let old_state = self.client.inner.e2ee.backup_state.global_state.set(new_state);
522
523 if old_state != new_state {
524 info!("Backup state changed from {old_state:?} to {new_state:?}");
525 }
526 }
527
528 async fn enable(
531 &self,
532 olm_machine: &OlmMachine,
533 backup_key: MegolmV1BackupKey,
534 version: String,
535 ) -> Result<(), Error> {
536 backup_key.set_version(version);
537 olm_machine.backup_machine().enable_backup_v1(backup_key).await?;
538
539 self.set_state(BackupState::Enabled);
540
541 Ok(())
542 }
543
544 async fn handle_downloaded_room_keys(
547 &self,
548 backed_up_keys: get_backup_keys::v3::Response,
549 backup_decryption_key: BackupDecryptionKey,
550 backup_version: &str,
551 olm_machine: &OlmMachine,
552 ) -> Result<(), Error> {
553 let mut decrypted_room_keys: Vec<_> = Vec::new();
554
555 for (room_id, room_keys) in backed_up_keys.rooms {
556 for (session_id, room_key) in room_keys.sessions {
557 let room_key = match room_key.deserialize() {
558 Ok(k) => k,
559 Err(e) => {
560 warn!(
561 "Couldn't deserialize a room key we downloaded from backups, session \
562 ID: {session_id}, error: {e:?}"
563 );
564 continue;
565 }
566 };
567
568 let room_key =
569 match backup_decryption_key.decrypt_session_data(room_key.session_data) {
570 Ok(k) => k,
571 Err(e) => {
572 warn!(
573 "Couldn't decrypt a room key we downloaded from backups, session \
574 ID: {session_id}, error: {e:?}"
575 );
576 continue;
577 }
578 };
579
580 decrypted_room_keys.push(ExportedRoomKey::from_backed_up_room_key(
581 room_id.to_owned(),
582 session_id,
583 room_key,
584 ));
585 }
586 }
587
588 let result = olm_machine
589 .store()
590 .import_room_keys(decrypted_room_keys, Some(backup_version), |_, _| {})
591 .await?;
592
593 let _ = self.client.inner.e2ee.backup_state.room_keys_broadcaster.send(result);
596
597 Ok(())
598 }
599
600 async fn download_all_room_keys(
602 &self,
603 decryption_key: BackupDecryptionKey,
604 version: String,
605 ) -> Result<(), Error> {
606 let request = get_backup_keys::v3::Request::new(version.clone());
607 let response = self.client.send(request).await?;
608
609 let olm_machine = self.client.olm_machine().await;
610 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
611
612 self.handle_downloaded_room_keys(response, decryption_key, &version, olm_machine).await?;
613
614 Ok(())
615 }
616
617 fn room_keys_stream(
618 &self,
619 ) -> impl Stream<Item = Result<RoomKeyImportResult, BroadcastStreamRecvError>> {
620 BroadcastStream::new(self.client.inner.e2ee.backup_state.room_keys_broadcaster.subscribe())
621 }
622
623 async fn get_current_version(
625 &self,
626 ) -> Result<Option<get_latest_backup_info::v3::Response>, Error> {
627 let request = get_latest_backup_info::v3::Request::new();
628
629 match self.client.send(request).await {
630 Ok(r) => Ok(Some(r)),
631 Err(e) => {
632 if let Some(kind) = e.client_api_error_kind() {
633 if kind == &ErrorKind::NotFound {
634 Ok(None)
635 } else {
636 Err(e.into())
637 }
638 } else {
639 Err(e.into())
640 }
641 }
642 }
643 }
644
645 async fn delete_backup_from_server(&self, version: String) -> Result<(), Error> {
646 let request = ruma::api::client::backup::delete_backup_version::v3::Request::new(version);
647
648 let ret = match self.client.send(request).await {
649 Ok(_) => Ok(()),
650 Err(e) => {
651 if let Some(kind) = e.client_api_error_kind() {
652 if kind == &ErrorKind::NotFound {
653 Ok(())
654 } else {
655 Err(e.into())
656 }
657 } else {
658 Err(e.into())
659 }
660 }
661 };
662
663 self.client.inner.e2ee.backup_state.clear_backup_exists_on_server();
667
668 ret
669 }
670
671 #[instrument(skip(self, olm_machine, request))]
672 async fn send_backup_request(
673 &self,
674 olm_machine: &OlmMachine,
675 request_id: &TransactionId,
676 request: KeysBackupRequest,
677 ) -> Result<(), Error> {
678 trace!("Uploading some room keys");
679
680 let add_backup_keys = add_backup_keys::v3::Request::new(request.version, request.rooms);
681
682 match self.client.send(add_backup_keys).await {
683 Ok(response) => {
684 olm_machine.mark_request_as_sent(request_id, &response).await?;
685
686 let new_counts = olm_machine.backup_machine().room_key_counts().await?;
687
688 self.client
689 .inner
690 .e2ee
691 .backup_state
692 .upload_progress
693 .set(UploadState::Uploading(new_counts));
694
695 let delay =
696 self.client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned();
697 crate::sleep::sleep(delay).await;
698
699 Ok(())
700 }
701 Err(error) => {
702 if let Some(kind) = error.client_api_error_kind() {
703 match kind {
704 ErrorKind::NotFound => {
705 warn!(
706 "No backup found on the server, the backup likely got deleted, \
707 disabling backups."
708 );
709
710 self.handle_deleted_backup_version(olm_machine).await?;
711 }
712 ErrorKind::WrongRoomKeysVersion { current_version } => {
713 warn!(
714 new_version = current_version,
715 "A new backup version was found on the server, disabling backups."
716 );
717
718 self.handle_deleted_backup_version(olm_machine).await?;
722 }
723
724 _ => (),
725 }
726 }
727
728 Err(error.into())
729 }
730 }
731 }
732
733 pub(crate) async fn backup_room_keys(&self) -> Result<(), Error> {
740 let _guard = self.client.locks().backup_upload_lock.lock().await;
741
742 let olm_machine = self.client.olm_machine().await;
743 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
744
745 while let Some((request_id, request)) = olm_machine.backup_machine().backup().await? {
746 self.send_backup_request(olm_machine, &request_id, request).await?;
747 }
748
749 self.client.inner.e2ee.backup_state.upload_progress.set(UploadState::Done);
750
751 Ok(())
752 }
753
754 pub(crate) async fn setup_and_resume(&self) -> Result<(), Error> {
757 info!("Setting up secret listeners and trying to resume backups");
758
759 self.client.add_event_handler(Self::secret_send_event_handler);
760
761 if self.client.inner.e2ee.encryption_settings.backup_download_strategy
762 == BackupDownloadStrategy::AfterDecryptionFailure
763 {
764 self.client.add_event_handler(Self::utd_event_handler);
765 }
766
767 self.maybe_resume_backups().await?;
768
769 Ok(())
770 }
771
772 #[instrument(skip_all)]
788 pub(crate) async fn maybe_enable_backups(
789 &self,
790 maybe_recovery_key: &str,
791 ) -> Result<bool, Error> {
792 let _guard = self.client.locks().backup_modify_lock.lock().await;
793
794 let future = async {
797 self.set_state(BackupState::Enabling);
798
799 let olm_machine = self.client.olm_machine().await;
800 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
801 let backup_machine = olm_machine.backup_machine();
802
803 let decryption_key =
804 BackupDecryptionKey::from_base64(maybe_recovery_key).map_err(|e| {
805 <serde_json::Error as serde::de::Error>::custom(format!(
806 "Couldn't deserialize the backup recovery key: {e:?}"
807 ))
808 })?;
809
810 let current_version = self.get_current_version().await?;
812
813 let Some(current_version) = current_version else {
814 warn!("Tried to enable backups, but no backup version was found on the server.");
815 return Ok(false);
816 };
817
818 Span::current().record("backup_version", ¤t_version.version);
819
820 let backup_info: RoomKeyBackupInfo = current_version.algorithm.deserialize_as()?;
821 let stored_keys = backup_machine.get_backup_keys().await?;
822
823 if stored_keys.backup_version.as_ref() == Some(¤t_version.version)
824 && self.are_enabled().await
825 {
826 Ok(true)
830 } else if decryption_key.backup_key_matches(&backup_info) {
831 info!(
832 "We have found the correct backup recovery key. Storing the backup recovery \
833 key and enabling backups."
834 );
835
836 backup_machine.disable_backup().await?;
839
840 let backup_key = decryption_key.megolm_v1_public_key();
841 backup_key.set_version(current_version.version.to_owned());
842
843 backup_machine
845 .save_decryption_key(
846 Some(decryption_key.to_owned()),
847 Some(current_version.version.to_owned()),
848 )
849 .await?;
850 backup_machine.enable_backup_v1(backup_key).await?;
851
852 if self.client.inner.e2ee.encryption_settings.backup_download_strategy
861 == BackupDownloadStrategy::OneShot
862 {
863 self.set_state(BackupState::Downloading);
864
865 if let Err(e) =
866 self.download_all_room_keys(decryption_key, current_version.version).await
867 {
868 warn!("Couldn't automatically download all room keys from backup: {e:?}");
869 }
870 }
871
872 self.maybe_trigger_backup();
874
875 Ok(true)
876 } else {
877 let derived_key = decryption_key.megolm_v1_public_key();
878 let downloaded_key = current_version.algorithm;
879
880 warn!(
881 ?derived_key,
882 ?downloaded_key,
883 "Found an active backup but the recovery key we received isn't the one used for \
884 this backup version"
885 );
886
887 Ok(false)
888 }
889 };
890
891 match future.await {
892 Ok(enabled) => {
893 if enabled {
894 self.set_state(BackupState::Enabled);
895 } else {
896 self.set_state(BackupState::Unknown);
897 }
898
899 Ok(enabled)
900 }
901 Err(e) => {
902 self.set_state(BackupState::Unknown);
903
904 Err(e)
905 }
906 }
907 }
908
909 async fn resume_backup_from_stored_backup_key(
914 &self,
915 olm_machine: &OlmMachine,
916 ) -> Result<bool, Error> {
917 let backup_keys = olm_machine.store().load_backup_keys().await?;
918
919 if let Some(decryption_key) = backup_keys.decryption_key {
920 if let Some(version) = backup_keys.backup_version {
921 let backup_key = decryption_key.megolm_v1_public_key();
922
923 self.enable(olm_machine, backup_key, version).await?;
924
925 Ok(true)
926 } else {
927 Ok(false)
928 }
929 } else {
930 Ok(false)
931 }
932 }
933
934 async fn maybe_resume_from_secret_inbox(&self, olm_machine: &OlmMachine) -> Result<(), Error> {
937 let secrets = olm_machine.store().get_secrets_from_inbox(&SecretName::RecoveryKey).await?;
938
939 for secret in secrets {
940 if self.maybe_enable_backups(&secret.event.content.secret).await? {
941 break;
942 }
943 }
944
945 olm_machine.store().delete_secrets_from_inbox(&SecretName::RecoveryKey).await?;
946
947 Ok(())
948 }
949
950 async fn maybe_resume_backups(&self) -> Result<(), Error> {
952 let olm_machine = self.client.olm_machine().await;
953 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
954
955 if !self.resume_backup_from_stored_backup_key(olm_machine).await? {
958 self.maybe_resume_from_secret_inbox(olm_machine).await?;
961 }
962
963 Ok(())
964 }
965
966 #[instrument(skip_all)]
969 pub(crate) async fn secret_send_event_handler(_: ToDeviceSecretSendEvent, client: Client) {
970 let olm_machine = client.olm_machine().await;
971
972 if let Some(olm_machine) = olm_machine.as_ref() {
977 if let Err(e) =
978 client.encryption().backups().maybe_resume_from_secret_inbox(olm_machine).await
979 {
980 error!("Could not handle `m.secret.send` event: {e:?}");
981 }
982 } else {
983 error!("Tried to handle a `m.secret.send` event but no OlmMachine was initialized");
984 }
985 }
986
987 #[allow(clippy::unused_async)] pub(crate) async fn utd_event_handler(
996 event: Raw<OriginalSyncRoomEncryptedEvent>,
997 room: Room,
998 client: Client,
999 ) {
1000 client.encryption().backups().maybe_download_room_key(room.room_id().to_owned(), event);
1001 }
1002
1003 pub(crate) fn maybe_download_room_key(
1006 &self,
1007 room_id: OwnedRoomId,
1008 event: Raw<OriginalSyncRoomEncryptedEvent>,
1009 ) {
1010 let tasks = self.client.inner.e2ee.tasks.lock();
1011 if let Some(task) = tasks.download_room_keys.as_ref() {
1012 task.trigger_download_for_utd_event(room_id, event);
1013 }
1014 }
1015
1016 pub(crate) fn maybe_trigger_backup(&self) {
1019 let tasks = self.client.inner.e2ee.tasks.lock();
1020
1021 if let Some(tasks) = tasks.upload_room_keys.as_ref() {
1022 tasks.trigger_upload();
1023 }
1024 }
1025
1026 async fn handle_deleted_backup_version(&self, olm_machine: &OlmMachine) -> Result<(), Error> {
1029 olm_machine.backup_machine().disable_backup().await?;
1030 self.set_state(BackupState::Unknown);
1031
1032 Ok(())
1033 }
1034}
1035
1036#[cfg(all(test, not(target_family = "wasm")))]
1037mod test {
1038 use std::time::Duration;
1039
1040 use matrix_sdk_test::async_test;
1041 use serde_json::json;
1042 use wiremock::{
1043 matchers::{header, method, path},
1044 Mock, MockServer, ResponseTemplate,
1045 };
1046
1047 use super::*;
1048 use crate::test_utils::{logged_in_client, mocks::MatrixMockServer};
1049
1050 fn room_key() -> ExportedRoomKey {
1051 let json = json!({
1052 "algorithm": "m.megolm.v1.aes-sha2",
1053 "room_id": "!DovneieKSTkdHKpIXy:morpheus.localhost",
1054 "sender_key": "DeHIg4gwhClxzFYcmNntPNF9YtsdZbmMy8+3kzCMXHA",
1055 "session_id": "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU",
1056 "session_key": "AQAAAABvWMNZjKFtebYIePKieQguozuoLgzeY6wKcyJjLJcJtQgy1dPqTBD12U+XrYLrRHn\
1057 lKmxoozlhFqJl456+9hlHCL+yq+6ScFuBHtJepnY1l2bdLb4T0JMDkNsNErkiLiLnD6yp3J\
1058 DSjIhkdHxmup/huygrmroq6/L5TaThEoqvW4DPIuO14btKudsS34FF82pwjKS4p6Mlch+0e\
1059 fHAblQV",
1060 "sender_claimed_keys":{},
1061 "forwarding_curve25519_key_chain":[]
1062 });
1063
1064 serde_json::from_value(json)
1065 .expect("We should be able to deserialize our exported room key")
1066 }
1067
1068 async fn backup_disabling_test_body(
1069 client: &Client,
1070 server: &MockServer,
1071 put_response: ResponseTemplate,
1072 ) {
1073 let _post_scope = Mock::given(method("POST"))
1074 .and(path("_matrix/client/unstable/room_keys/version"))
1075 .and(header("authorization", "Bearer 1234"))
1076 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1077 "version": "1"
1078 })))
1079 .expect(1)
1080 .named("POST for the backup creation")
1081 .mount_as_scoped(server)
1082 .await;
1083
1084 let _put_scope = Mock::given(method("PUT"))
1085 .and(path("_matrix/client/unstable/room_keys/keys"))
1086 .and(header("authorization", "Bearer 1234"))
1087 .respond_with(put_response)
1088 .expect(1)
1089 .named("POST for the backup creation")
1090 .mount_as_scoped(server)
1091 .await;
1092
1093 client
1094 .encryption()
1095 .backups()
1096 .create()
1097 .await
1098 .expect("We should be able to create a new backup");
1099
1100 assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
1101
1102 client
1103 .encryption()
1104 .backups()
1105 .backup_room_keys()
1106 .await
1107 .expect_err("Backups should be disabled");
1108
1109 assert_eq!(client.encryption().backups().state(), BackupState::Unknown);
1110 }
1111
1112 #[async_test]
1113 async fn test_backup_disabling_after_remote_deletion() {
1114 let server = MockServer::start().await;
1115 let client = logged_in_client(Some(server.uri())).await;
1116
1117 {
1118 let machine = client.olm_machine().await;
1119 machine
1120 .as_ref()
1121 .unwrap()
1122 .store()
1123 .import_exported_room_keys(vec![room_key()], |_, _| {})
1124 .await
1125 .expect("We should be able to import a room key");
1126 }
1127
1128 backup_disabling_test_body(
1129 &client,
1130 &server,
1131 ResponseTemplate::new(404).set_body_json(json!({
1132 "errcode": "M_NOT_FOUND",
1133 "error": "Unknown backup version"
1134 })),
1135 )
1136 .await;
1137
1138 backup_disabling_test_body(
1139 &client,
1140 &server,
1141 ResponseTemplate::new(403).set_body_json(json!({
1142 "current_version": "42",
1143 "errcode": "M_WRONG_ROOM_KEYS_VERSION",
1144 "error": "Wrong backup version."
1145 })),
1146 )
1147 .await;
1148
1149 server.verify().await;
1150 }
1151
1152 #[async_test]
1153 async fn test_when_a_backup_exists_then_fetch_exists_on_server_returns_true() {
1154 let server = MatrixMockServer::new().await;
1155 let client = server.client_builder().build().await;
1156
1157 server.mock_room_keys_version().exists().expect(1).mount().await;
1158
1159 let exists = client
1160 .encryption()
1161 .backups()
1162 .fetch_exists_on_server()
1163 .await
1164 .expect("We should be able to check if backups exist on the server");
1165
1166 assert!(exists, "We should deduce that a backup exists on the server");
1167 }
1168
1169 #[async_test]
1170 async fn test_repeated_calls_to_fetch_exists_on_server_makes_repeated_requests() {
1171 let server = MatrixMockServer::new().await;
1172 let client = server.client_builder().build().await;
1173
1174 server.mock_room_keys_version().exists().expect(2).mount().await;
1176
1177 let backups = client.encryption().backups();
1178
1179 backups.fetch_exists_on_server().await.unwrap();
1181 let exists = backups.fetch_exists_on_server().await.unwrap();
1182
1183 assert!(exists, "We should deduce that a backup exists on the server");
1184 }
1185
1186 #[async_test]
1187 async fn test_when_no_backup_exists_then_fetch_exists_on_server_returns_false() {
1188 let server = MatrixMockServer::new().await;
1189 let client = server.client_builder().build().await;
1190
1191 server.mock_room_keys_version().none().expect(1).mount().await;
1192
1193 let exists = client
1194 .encryption()
1195 .backups()
1196 .fetch_exists_on_server()
1197 .await
1198 .expect("We should be able to check if backups exist on the server");
1199
1200 assert!(!exists, "We should deduce that no backup exists on the server");
1201 }
1202
1203 #[async_test]
1204 async fn test_when_server_returns_an_error_then_fetch_exists_on_server_returns_an_error() {
1205 let server = MatrixMockServer::new().await;
1206 let client = server.client_builder().build().await;
1207
1208 {
1209 let _scope =
1210 server.mock_room_keys_version().error429().expect(1).mount_as_scoped().await;
1211
1212 client.encryption().backups().fetch_exists_on_server().await.expect_err(
1213 "If the /version endpoint returns a non 404 error we should throw an error",
1214 );
1215 }
1216
1217 {
1218 let _scope =
1219 server.mock_room_keys_version().error404().expect(1).mount_as_scoped().await;
1220
1221 client.encryption().backups().fetch_exists_on_server().await.expect_err(
1222 "If the /version endpoint returns a non-Matrix 404 error we should throw an error",
1223 );
1224 }
1225 }
1226
1227 #[async_test]
1228 async fn test_when_a_backup_exists_then_exists_on_server_returns_true() {
1229 let server = MatrixMockServer::new().await;
1230 let client = server.client_builder().build().await;
1231
1232 server.mock_room_keys_version().exists().expect(1).mount().await;
1233
1234 let exists = client
1235 .encryption()
1236 .backups()
1237 .exists_on_server()
1238 .await
1239 .expect("We should be able to check if backups exist on the server");
1240
1241 assert!(exists, "We should deduce that a backup exists on the server");
1242 }
1243
1244 #[async_test]
1245 async fn test_when_no_backup_exists_then_exists_on_server_returns_false() {
1246 let server = MatrixMockServer::new().await;
1247 let client = server.client_builder().build().await;
1248
1249 server.mock_room_keys_version().none().expect(1).mount().await;
1250
1251 let exists = client
1252 .encryption()
1253 .backups()
1254 .exists_on_server()
1255 .await
1256 .expect("We should be able to check if backups exist on the server");
1257
1258 assert!(!exists, "We should deduce that no backup exists on the server");
1259 }
1260
1261 #[async_test]
1262 async fn test_when_server_returns_an_error_then_exists_on_server_returns_an_error() {
1263 let server = MatrixMockServer::new().await;
1264 let client = server.client_builder().build().await;
1265
1266 {
1267 let _scope =
1268 server.mock_room_keys_version().error429().expect(1).mount_as_scoped().await;
1269
1270 client.encryption().backups().exists_on_server().await.expect_err(
1271 "If the /version endpoint returns a non 404 error we should throw an error",
1272 );
1273 }
1274
1275 {
1276 let _scope =
1277 server.mock_room_keys_version().error404().expect(1).mount_as_scoped().await;
1278
1279 client.encryption().backups().exists_on_server().await.expect_err(
1280 "If the /version endpoint returns a non-Matrix 404 error we should throw an error",
1281 );
1282 }
1283 }
1284
1285 #[async_test]
1286 async fn test_repeated_calls_to_exists_on_server_do_not_make_additional_requests() {
1287 let server = MatrixMockServer::new().await;
1288 let client = server.client_builder().build().await;
1289
1290 server.mock_room_keys_version().exists().expect(1).mount().await;
1292
1293 let backups = client.encryption().backups();
1294
1295 backups.exists_on_server().await.unwrap();
1297 backups.exists_on_server().await.unwrap();
1298 backups.exists_on_server().await.unwrap();
1299
1300 let exists = backups
1301 .exists_on_server()
1302 .await
1303 .expect("We should be able to check if backups exist on the server");
1304
1305 assert!(exists, "We should deduce that a backup exists on the server");
1306
1307 }
1309
1310 #[async_test]
1311 async fn test_adding_a_backup_invalidates_exists_on_server_cache() {
1312 let server = MatrixMockServer::new().await;
1313 let client = server.client_builder().build().await;
1314 let backups = client.encryption().backups();
1315
1316 {
1317 let _scope = server.mock_room_keys_version().none().expect(1).mount_as_scoped().await;
1318
1319 let exists = backups.exists_on_server().await.unwrap();
1321 assert!(!exists, "No backup exists at this point");
1322 }
1323
1324 server.mock_add_room_keys_version().ok().expect(1).mount().await;
1326 backups.create().await.expect("Failed to create a backup");
1327
1328 server.mock_room_keys_version().exists().expect(1).mount().await;
1329 let exists = backups
1330 .exists_on_server()
1331 .await
1332 .expect("We should be able to check if backups exist on the server");
1333
1334 assert!(exists, "But now a backup does exist");
1335 }
1336
1337 #[async_test]
1338 async fn test_removing_a_backup_invalidates_exists_on_server_cache() {
1339 let server = MatrixMockServer::new().await;
1340 let client = server.client_builder().build().await;
1341 let backups = client.encryption().backups();
1342
1343 {
1344 let _scope = server.mock_room_keys_version().exists().expect(1).mount_as_scoped().await;
1345
1346 let exists = backups.exists_on_server().await.unwrap();
1348 assert!(exists, "A backup exists at this point");
1349 }
1350
1351 server.mock_delete_room_keys_version().ok().expect(1).mount().await;
1353 backups.delete_backup_from_server("1".to_owned()).await.expect("Failed to delete a backup");
1354
1355 server.mock_room_keys_version().none().expect(1).mount().await;
1356 let exists = backups
1357 .exists_on_server()
1358 .await
1359 .expect("We should be able to check if backups exist on the server");
1360
1361 assert!(!exists, "But now there is no backup");
1362 }
1363
1364 #[async_test]
1365 async fn test_waiting_for_steady_state_resets_the_delay() {
1366 let server = MatrixMockServer::new().await;
1367 let client = server.client_builder().build().await;
1368
1369 server.mock_add_room_keys_version().ok().expect(1).mount().await;
1370
1371 client
1372 .encryption()
1373 .backups()
1374 .create()
1375 .await
1376 .expect("We should be able to create a new backup");
1377
1378 let backups = client.encryption().backups();
1379
1380 let old_duration =
1381 { client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned() };
1382
1383 let wait_for_steady_state =
1384 backups.wait_for_steady_state().with_delay(Duration::from_nanos(100));
1385
1386 let mut progress_stream = wait_for_steady_state.subscribe_to_progress();
1387
1388 let task = matrix_sdk_common::executor::spawn({
1389 let client = client.to_owned();
1390 async move {
1391 while let Some(state) = progress_stream.next().await {
1392 let Ok(state) = state else {
1393 panic!("Error while waiting for the upload state")
1394 };
1395
1396 match state {
1397 UploadState::Idle => (),
1398 UploadState::Done => {
1399 let current_delay = {
1400 client
1401 .inner
1402 .e2ee
1403 .backup_state
1404 .upload_delay
1405 .read()
1406 .unwrap()
1407 .to_owned()
1408 };
1409
1410 assert_ne!(current_delay, old_duration);
1411 break;
1412 }
1413 _ => panic!("We should not have entered any other state"),
1414 }
1415 }
1416 }
1417 });
1418
1419 wait_for_steady_state.await.expect("We should be able to wait for the steady state");
1420 task.await.unwrap();
1421
1422 let current_duration =
1423 { client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned() };
1424
1425 assert_eq!(old_duration, current_duration);
1426 }
1427}