1use std::collections::{BTreeMap, BTreeSet};
24
25use futures_core::Stream;
26use futures_util::StreamExt;
27use matrix_sdk_base::crypto::{
28 backups::MegolmV1BackupKey,
29 store::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!("No backup found on the server, the backup likely got deleted, disabling backups.");
706
707 self.handle_deleted_backup_version(olm_machine).await?;
708 }
709 ErrorKind::WrongRoomKeysVersion { current_version } => {
710 warn!(
711 new_version = current_version,
712 "A new backup version was found on the server, disabling backups."
713 );
714
715 self.handle_deleted_backup_version(olm_machine).await?;
719 }
720
721 _ => (),
722 }
723 }
724
725 Err(error.into())
726 }
727 }
728 }
729
730 pub(crate) async fn backup_room_keys(&self) -> Result<(), Error> {
737 let _guard = self.client.locks().backup_upload_lock.lock().await;
738
739 let olm_machine = self.client.olm_machine().await;
740 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
741
742 while let Some((request_id, request)) = olm_machine.backup_machine().backup().await? {
743 self.send_backup_request(olm_machine, &request_id, request).await?;
744 }
745
746 self.client.inner.e2ee.backup_state.upload_progress.set(UploadState::Done);
747
748 Ok(())
749 }
750
751 pub(crate) async fn setup_and_resume(&self) -> Result<(), Error> {
754 info!("Setting up secret listeners and trying to resume backups");
755
756 self.client.add_event_handler(Self::secret_send_event_handler);
757
758 if self.client.inner.e2ee.encryption_settings.backup_download_strategy
759 == BackupDownloadStrategy::AfterDecryptionFailure
760 {
761 self.client.add_event_handler(Self::utd_event_handler);
762 }
763
764 self.maybe_resume_backups().await?;
765
766 Ok(())
767 }
768
769 #[instrument(skip_all)]
785 pub(crate) async fn maybe_enable_backups(
786 &self,
787 maybe_recovery_key: &str,
788 ) -> Result<bool, Error> {
789 let _guard = self.client.locks().backup_modify_lock.lock().await;
790
791 let future = async {
794 self.set_state(BackupState::Enabling);
795
796 let olm_machine = self.client.olm_machine().await;
797 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
798 let backup_machine = olm_machine.backup_machine();
799
800 let decryption_key =
801 BackupDecryptionKey::from_base64(maybe_recovery_key).map_err(|e| {
802 <serde_json::Error as serde::de::Error>::custom(format!(
803 "Couldn't deserialize the backup recovery key: {e:?}"
804 ))
805 })?;
806
807 let current_version = self.get_current_version().await?;
809
810 let Some(current_version) = current_version else {
811 warn!("Tried to enable backups, but no backup version was found on the server.");
812 return Ok(false);
813 };
814
815 Span::current().record("backup_version", ¤t_version.version);
816
817 let backup_info: RoomKeyBackupInfo = current_version.algorithm.deserialize_as()?;
818 let stored_keys = backup_machine.get_backup_keys().await?;
819
820 if stored_keys.backup_version.as_ref() == Some(¤t_version.version)
821 && self.are_enabled().await
822 {
823 Ok(true)
827 } else if decryption_key.backup_key_matches(&backup_info) {
828 info!(
829 "We have found the correct backup recovery key. Storing the backup recovery \
830 key and enabling backups."
831 );
832
833 backup_machine.disable_backup().await?;
836
837 let backup_key = decryption_key.megolm_v1_public_key();
838 backup_key.set_version(current_version.version.to_owned());
839
840 backup_machine
842 .save_decryption_key(
843 Some(decryption_key.to_owned()),
844 Some(current_version.version.to_owned()),
845 )
846 .await?;
847 backup_machine.enable_backup_v1(backup_key).await?;
848
849 if self.client.inner.e2ee.encryption_settings.backup_download_strategy
858 == BackupDownloadStrategy::OneShot
859 {
860 self.set_state(BackupState::Downloading);
861
862 if let Err(e) =
863 self.download_all_room_keys(decryption_key, current_version.version).await
864 {
865 warn!("Couldn't automatically download all room keys from backup: {e:?}");
866 }
867 }
868
869 self.maybe_trigger_backup();
871
872 Ok(true)
873 } else {
874 let derived_key = decryption_key.megolm_v1_public_key();
875 let downloaded_key = current_version.algorithm;
876
877 warn!(
878 ?derived_key,
879 ?downloaded_key,
880 "Found an active backup but the recovery key we received isn't the one used for \
881 this backup version"
882 );
883
884 Ok(false)
885 }
886 };
887
888 match future.await {
889 Ok(enabled) => {
890 if enabled {
891 self.set_state(BackupState::Enabled);
892 } else {
893 self.set_state(BackupState::Unknown);
894 }
895
896 Ok(enabled)
897 }
898 Err(e) => {
899 self.set_state(BackupState::Unknown);
900
901 Err(e)
902 }
903 }
904 }
905
906 async fn resume_backup_from_stored_backup_key(
911 &self,
912 olm_machine: &OlmMachine,
913 ) -> Result<bool, Error> {
914 let backup_keys = olm_machine.store().load_backup_keys().await?;
915
916 if let Some(decryption_key) = backup_keys.decryption_key {
917 if let Some(version) = backup_keys.backup_version {
918 let backup_key = decryption_key.megolm_v1_public_key();
919
920 self.enable(olm_machine, backup_key, version).await?;
921
922 Ok(true)
923 } else {
924 Ok(false)
925 }
926 } else {
927 Ok(false)
928 }
929 }
930
931 async fn maybe_resume_from_secret_inbox(&self, olm_machine: &OlmMachine) -> Result<(), Error> {
934 let secrets = olm_machine.store().get_secrets_from_inbox(&SecretName::RecoveryKey).await?;
935
936 for secret in secrets {
937 if self.maybe_enable_backups(&secret.event.content.secret).await? {
938 break;
939 }
940 }
941
942 olm_machine.store().delete_secrets_from_inbox(&SecretName::RecoveryKey).await?;
943
944 Ok(())
945 }
946
947 async fn maybe_resume_backups(&self) -> Result<(), Error> {
949 let olm_machine = self.client.olm_machine().await;
950 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
951
952 if !self.resume_backup_from_stored_backup_key(olm_machine).await? {
955 self.maybe_resume_from_secret_inbox(olm_machine).await?;
958 }
959
960 Ok(())
961 }
962
963 #[instrument(skip_all)]
966 pub(crate) async fn secret_send_event_handler(_: ToDeviceSecretSendEvent, client: Client) {
967 let olm_machine = client.olm_machine().await;
968
969 if let Some(olm_machine) = olm_machine.as_ref() {
974 if let Err(e) =
975 client.encryption().backups().maybe_resume_from_secret_inbox(olm_machine).await
976 {
977 error!("Could not handle `m.secret.send` event: {e:?}");
978 }
979 } else {
980 error!("Tried to handle a `m.secret.send` event but no OlmMachine was initialized");
981 }
982 }
983
984 #[allow(clippy::unused_async)] pub(crate) async fn utd_event_handler(
993 event: Raw<OriginalSyncRoomEncryptedEvent>,
994 room: Room,
995 client: Client,
996 ) {
997 client.encryption().backups().maybe_download_room_key(room.room_id().to_owned(), event);
998 }
999
1000 pub(crate) fn maybe_download_room_key(
1003 &self,
1004 room_id: OwnedRoomId,
1005 event: Raw<OriginalSyncRoomEncryptedEvent>,
1006 ) {
1007 let tasks = self.client.inner.e2ee.tasks.lock();
1008 if let Some(task) = tasks.download_room_keys.as_ref() {
1009 task.trigger_download_for_utd_event(room_id, event);
1010 }
1011 }
1012
1013 pub(crate) fn maybe_trigger_backup(&self) {
1016 let tasks = self.client.inner.e2ee.tasks.lock();
1017
1018 if let Some(tasks) = tasks.upload_room_keys.as_ref() {
1019 tasks.trigger_upload();
1020 }
1021 }
1022
1023 async fn handle_deleted_backup_version(&self, olm_machine: &OlmMachine) -> Result<(), Error> {
1026 olm_machine.backup_machine().disable_backup().await?;
1027 self.set_state(BackupState::Unknown);
1028
1029 Ok(())
1030 }
1031}
1032
1033#[cfg(all(test, not(target_arch = "wasm32")))]
1034mod test {
1035 use std::time::Duration;
1036
1037 use matrix_sdk_test::async_test;
1038 use serde_json::json;
1039 use wiremock::{
1040 matchers::{header, method, path},
1041 Mock, MockServer, ResponseTemplate,
1042 };
1043
1044 use super::*;
1045 use crate::test_utils::{logged_in_client, mocks::MatrixMockServer};
1046
1047 fn room_key() -> ExportedRoomKey {
1048 let json = json!({
1049 "algorithm": "m.megolm.v1.aes-sha2",
1050 "room_id": "!DovneieKSTkdHKpIXy:morpheus.localhost",
1051 "sender_key": "DeHIg4gwhClxzFYcmNntPNF9YtsdZbmMy8+3kzCMXHA",
1052 "session_id": "gM8i47Xhu0q52xLfgUXzanCMpLinoyVyH7R58cBuVBU",
1053 "session_key": "AQAAAABvWMNZjKFtebYIePKieQguozuoLgzeY6wKcyJjLJcJtQgy1dPqTBD12U+XrYLrRHn\
1054 lKmxoozlhFqJl456+9hlHCL+yq+6ScFuBHtJepnY1l2bdLb4T0JMDkNsNErkiLiLnD6yp3J\
1055 DSjIhkdHxmup/huygrmroq6/L5TaThEoqvW4DPIuO14btKudsS34FF82pwjKS4p6Mlch+0e\
1056 fHAblQV",
1057 "sender_claimed_keys":{},
1058 "forwarding_curve25519_key_chain":[]
1059 });
1060
1061 serde_json::from_value(json)
1062 .expect("We should be able to deserialize our exported room key")
1063 }
1064
1065 async fn backup_disabling_test_body(
1066 client: &Client,
1067 server: &MockServer,
1068 put_response: ResponseTemplate,
1069 ) {
1070 let _post_scope = Mock::given(method("POST"))
1071 .and(path("_matrix/client/unstable/room_keys/version"))
1072 .and(header("authorization", "Bearer 1234"))
1073 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1074 "version": "1"
1075 })))
1076 .expect(1)
1077 .named("POST for the backup creation")
1078 .mount_as_scoped(server)
1079 .await;
1080
1081 let _put_scope = Mock::given(method("PUT"))
1082 .and(path("_matrix/client/unstable/room_keys/keys"))
1083 .and(header("authorization", "Bearer 1234"))
1084 .respond_with(put_response)
1085 .expect(1)
1086 .named("POST for the backup creation")
1087 .mount_as_scoped(server)
1088 .await;
1089
1090 client
1091 .encryption()
1092 .backups()
1093 .create()
1094 .await
1095 .expect("We should be able to create a new backup");
1096
1097 assert_eq!(client.encryption().backups().state(), BackupState::Enabled);
1098
1099 client
1100 .encryption()
1101 .backups()
1102 .backup_room_keys()
1103 .await
1104 .expect_err("Backups should be disabled");
1105
1106 assert_eq!(client.encryption().backups().state(), BackupState::Unknown);
1107 }
1108
1109 #[async_test]
1110 async fn test_backup_disabling_after_remote_deletion() {
1111 let server = MockServer::start().await;
1112 let client = logged_in_client(Some(server.uri())).await;
1113
1114 {
1115 let machine = client.olm_machine().await;
1116 machine
1117 .as_ref()
1118 .unwrap()
1119 .store()
1120 .import_exported_room_keys(vec![room_key()], |_, _| {})
1121 .await
1122 .expect("We should be able to import a room key");
1123 }
1124
1125 backup_disabling_test_body(
1126 &client,
1127 &server,
1128 ResponseTemplate::new(404).set_body_json(json!({
1129 "errcode": "M_NOT_FOUND",
1130 "error": "Unknown backup version"
1131 })),
1132 )
1133 .await;
1134
1135 backup_disabling_test_body(
1136 &client,
1137 &server,
1138 ResponseTemplate::new(403).set_body_json(json!({
1139 "current_version": "42",
1140 "errcode": "M_WRONG_ROOM_KEYS_VERSION",
1141 "error": "Wrong backup version."
1142 })),
1143 )
1144 .await;
1145
1146 server.verify().await;
1147 }
1148
1149 #[async_test]
1150 async fn test_when_a_backup_exists_then_fetch_exists_on_server_returns_true() {
1151 let server = MatrixMockServer::new().await;
1152 let client = server.client_builder().build().await;
1153
1154 server.mock_room_keys_version().exists().expect(1).mount().await;
1155
1156 let exists = client
1157 .encryption()
1158 .backups()
1159 .fetch_exists_on_server()
1160 .await
1161 .expect("We should be able to check if backups exist on the server");
1162
1163 assert!(exists, "We should deduce that a backup exists on the server");
1164 }
1165
1166 #[async_test]
1167 async fn test_repeated_calls_to_fetch_exists_on_server_makes_repeated_requests() {
1168 let server = MatrixMockServer::new().await;
1169 let client = server.client_builder().build().await;
1170
1171 server.mock_room_keys_version().exists().expect(2).mount().await;
1173
1174 let backups = client.encryption().backups();
1175
1176 backups.fetch_exists_on_server().await.unwrap();
1178 let exists = backups.fetch_exists_on_server().await.unwrap();
1179
1180 assert!(exists, "We should deduce that a backup exists on the server");
1181 }
1182
1183 #[async_test]
1184 async fn test_when_no_backup_exists_then_fetch_exists_on_server_returns_false() {
1185 let server = MatrixMockServer::new().await;
1186 let client = server.client_builder().build().await;
1187
1188 server.mock_room_keys_version().none().expect(1).mount().await;
1189
1190 let exists = client
1191 .encryption()
1192 .backups()
1193 .fetch_exists_on_server()
1194 .await
1195 .expect("We should be able to check if backups exist on the server");
1196
1197 assert!(!exists, "We should deduce that no backup exists on the server");
1198 }
1199
1200 #[async_test]
1201 async fn test_when_server_returns_an_error_then_fetch_exists_on_server_returns_an_error() {
1202 let server = MatrixMockServer::new().await;
1203 let client = server.client_builder().build().await;
1204
1205 {
1206 let _scope =
1207 server.mock_room_keys_version().error429().expect(1).mount_as_scoped().await;
1208
1209 client.encryption().backups().fetch_exists_on_server().await.expect_err(
1210 "If the /version endpoint returns a non 404 error we should throw an error",
1211 );
1212 }
1213
1214 {
1215 let _scope =
1216 server.mock_room_keys_version().error404().expect(1).mount_as_scoped().await;
1217
1218 client.encryption().backups().fetch_exists_on_server().await.expect_err(
1219 "If the /version endpoint returns a non-Matrix 404 error we should throw an error",
1220 );
1221 }
1222 }
1223
1224 #[async_test]
1225 async fn test_when_a_backup_exists_then_exists_on_server_returns_true() {
1226 let server = MatrixMockServer::new().await;
1227 let client = server.client_builder().build().await;
1228
1229 server.mock_room_keys_version().exists().expect(1).mount().await;
1230
1231 let exists = client
1232 .encryption()
1233 .backups()
1234 .exists_on_server()
1235 .await
1236 .expect("We should be able to check if backups exist on the server");
1237
1238 assert!(exists, "We should deduce that a backup exists on the server");
1239 }
1240
1241 #[async_test]
1242 async fn test_when_no_backup_exists_then_exists_on_server_returns_false() {
1243 let server = MatrixMockServer::new().await;
1244 let client = server.client_builder().build().await;
1245
1246 server.mock_room_keys_version().none().expect(1).mount().await;
1247
1248 let exists = client
1249 .encryption()
1250 .backups()
1251 .exists_on_server()
1252 .await
1253 .expect("We should be able to check if backups exist on the server");
1254
1255 assert!(!exists, "We should deduce that no backup exists on the server");
1256 }
1257
1258 #[async_test]
1259 async fn test_when_server_returns_an_error_then_exists_on_server_returns_an_error() {
1260 let server = MatrixMockServer::new().await;
1261 let client = server.client_builder().build().await;
1262
1263 {
1264 let _scope =
1265 server.mock_room_keys_version().error429().expect(1).mount_as_scoped().await;
1266
1267 client.encryption().backups().exists_on_server().await.expect_err(
1268 "If the /version endpoint returns a non 404 error we should throw an error",
1269 );
1270 }
1271
1272 {
1273 let _scope =
1274 server.mock_room_keys_version().error404().expect(1).mount_as_scoped().await;
1275
1276 client.encryption().backups().exists_on_server().await.expect_err(
1277 "If the /version endpoint returns a non-Matrix 404 error we should throw an error",
1278 );
1279 }
1280 }
1281
1282 #[async_test]
1283 async fn test_repeated_calls_to_exists_on_server_do_not_make_additional_requests() {
1284 let server = MatrixMockServer::new().await;
1285 let client = server.client_builder().build().await;
1286
1287 server.mock_room_keys_version().exists().expect(1).mount().await;
1289
1290 let backups = client.encryption().backups();
1291
1292 backups.exists_on_server().await.unwrap();
1294 backups.exists_on_server().await.unwrap();
1295 backups.exists_on_server().await.unwrap();
1296
1297 let exists = backups
1298 .exists_on_server()
1299 .await
1300 .expect("We should be able to check if backups exist on the server");
1301
1302 assert!(exists, "We should deduce that a backup exists on the server");
1303
1304 }
1306
1307 #[async_test]
1308 async fn test_adding_a_backup_invalidates_exists_on_server_cache() {
1309 let server = MatrixMockServer::new().await;
1310 let client = server.client_builder().build().await;
1311 let backups = client.encryption().backups();
1312
1313 {
1314 let _scope = server.mock_room_keys_version().none().expect(1).mount_as_scoped().await;
1315
1316 let exists = backups.exists_on_server().await.unwrap();
1318 assert!(!exists, "No backup exists at this point");
1319 }
1320
1321 server.mock_add_room_keys_version().ok().expect(1).mount().await;
1323 backups.create().await.expect("Failed to create a backup");
1324
1325 server.mock_room_keys_version().exists().expect(1).mount().await;
1326 let exists = backups
1327 .exists_on_server()
1328 .await
1329 .expect("We should be able to check if backups exist on the server");
1330
1331 assert!(exists, "But now a backup does exist");
1332 }
1333
1334 #[async_test]
1335 async fn test_removing_a_backup_invalidates_exists_on_server_cache() {
1336 let server = MatrixMockServer::new().await;
1337 let client = server.client_builder().build().await;
1338 let backups = client.encryption().backups();
1339
1340 {
1341 let _scope = server.mock_room_keys_version().exists().expect(1).mount_as_scoped().await;
1342
1343 let exists = backups.exists_on_server().await.unwrap();
1345 assert!(exists, "A backup exists at this point");
1346 }
1347
1348 server.mock_delete_room_keys_version().ok().expect(1).mount().await;
1350 backups.delete_backup_from_server("1".to_owned()).await.expect("Failed to delete a backup");
1351
1352 server.mock_room_keys_version().none().expect(1).mount().await;
1353 let exists = backups
1354 .exists_on_server()
1355 .await
1356 .expect("We should be able to check if backups exist on the server");
1357
1358 assert!(!exists, "But now there is no backup");
1359 }
1360
1361 #[async_test]
1362 async fn test_waiting_for_steady_state_resets_the_delay() {
1363 let server = MatrixMockServer::new().await;
1364 let client = server.client_builder().build().await;
1365
1366 server.mock_add_room_keys_version().ok().expect(1).mount().await;
1367
1368 client
1369 .encryption()
1370 .backups()
1371 .create()
1372 .await
1373 .expect("We should be able to create a new backup");
1374
1375 let backups = client.encryption().backups();
1376
1377 let old_duration =
1378 { client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned() };
1379
1380 let wait_for_steady_state =
1381 backups.wait_for_steady_state().with_delay(Duration::from_nanos(100));
1382
1383 let mut progress_stream = wait_for_steady_state.subscribe_to_progress();
1384
1385 let task = matrix_sdk_common::executor::spawn({
1386 let client = client.to_owned();
1387 async move {
1388 while let Some(state) = progress_stream.next().await {
1389 let Ok(state) = state else {
1390 panic!("Error while waiting for the upload state")
1391 };
1392
1393 match state {
1394 UploadState::Idle => (),
1395 UploadState::Done => {
1396 let current_delay = {
1397 client
1398 .inner
1399 .e2ee
1400 .backup_state
1401 .upload_delay
1402 .read()
1403 .unwrap()
1404 .to_owned()
1405 };
1406
1407 assert_ne!(current_delay, old_duration);
1408 break;
1409 }
1410 _ => panic!("We should not have entered any other state"),
1411 }
1412 }
1413 }
1414 });
1415
1416 wait_for_steady_state.await.expect("We should be able to wait for the steady state");
1417 task.await.unwrap();
1418
1419 let current_duration =
1420 { client.inner.e2ee.backup_state.upload_delay.read().unwrap().to_owned() };
1421
1422 assert_eq!(old_duration, current_duration);
1423 }
1424}