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