1use std::sync::Arc;
2
3use futures_util::StreamExt;
4use matrix_sdk::{
5 encryption,
6 encryption::{backups, recovery},
7};
8use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm};
9use thiserror::Error;
10use tracing::{error, info};
11use zeroize::Zeroize;
12
13use crate::{
14 client::Client, error::ClientError, ruma::AuthData, runtime::get_runtime_handle,
15 task_handle::TaskHandle,
16};
17
18#[derive(uniffi::Object)]
19pub struct Encryption {
20 pub(crate) inner: matrix_sdk::encryption::Encryption,
21
22 pub(crate) _client: Arc<Client>,
28}
29
30#[matrix_sdk_ffi_macros::export(callback_interface)]
31pub trait BackupStateListener: SyncOutsideWasm + SendOutsideWasm {
32 fn on_update(&self, status: BackupState);
33}
34
35#[matrix_sdk_ffi_macros::export(callback_interface)]
36pub trait BackupSteadyStateListener: SyncOutsideWasm + SendOutsideWasm {
37 fn on_update(&self, status: BackupUploadState);
38}
39
40#[matrix_sdk_ffi_macros::export(callback_interface)]
41pub trait RecoveryStateListener: SyncOutsideWasm + SendOutsideWasm {
42 fn on_update(&self, status: RecoveryState);
43}
44
45#[matrix_sdk_ffi_macros::export(callback_interface)]
46pub trait VerificationStateListener: SyncOutsideWasm + SendOutsideWasm {
47 fn on_update(&self, status: VerificationState);
48}
49
50#[derive(uniffi::Enum)]
51pub enum BackupUploadState {
52 Waiting,
53 Uploading { backed_up_count: u32, total_count: u32 },
54 Error,
55 Done,
56}
57
58#[derive(Debug, Error, uniffi::Error)]
59#[uniffi(flat_error)]
60pub enum SteadyStateError {
61 #[error("The backup got disabled while waiting for the room keys to be uploaded.")]
62 BackupDisabled,
63 #[error("There was a connection error.")]
64 Connection,
65 #[error("We couldn't read status updates from the upload task quickly enough.")]
66 Lagged,
67}
68
69#[derive(Debug, Error, uniffi::Error)]
70pub enum RecoveryError {
71 #[error(
74 "A backup already exists on the homeserver and the method does not allow to overwrite it"
75 )]
76 BackupExistsOnServer,
77
78 #[error(transparent)]
80 Client { source: crate::ClientError },
81
82 #[error("Error in the secret-storage subsystem: {error_message}")]
85 SecretStorage { error_message: String },
86
87 #[error("Error importing a secret: {error_message}")]
89 Import { error_message: String },
90}
91
92impl From<matrix_sdk::encryption::recovery::RecoveryError> for RecoveryError {
93 fn from(value: matrix_sdk::encryption::recovery::RecoveryError) -> Self {
94 match value {
95 recovery::RecoveryError::BackupExistsOnServer => Self::BackupExistsOnServer,
96 recovery::RecoveryError::Sdk(e) => Self::Client { source: ClientError::from(e) },
97 recovery::RecoveryError::SecretStorage(
98 matrix_sdk::encryption::secret_storage::SecretStorageError::ImportError { .. },
99 ) => Self::Import { error_message: value.to_string() },
100 recovery::RecoveryError::SecretStorage(e) => {
101 Self::SecretStorage { error_message: e.to_string() }
102 }
103 }
104 }
105}
106
107pub type Result<A, E = RecoveryError> = std::result::Result<A, E>;
108
109impl From<matrix_sdk::encryption::backups::futures::SteadyStateError> for SteadyStateError {
110 fn from(value: matrix_sdk::encryption::backups::futures::SteadyStateError) -> Self {
111 match value {
112 backups::futures::SteadyStateError::BackupDisabled => Self::BackupDisabled,
113 backups::futures::SteadyStateError::Connection => Self::Connection,
114 backups::futures::SteadyStateError::Lagged => Self::Lagged,
115 }
116 }
117}
118
119#[derive(uniffi::Enum)]
120pub enum BackupState {
121 Unknown,
122 Creating,
123 Enabling,
124 Resuming,
125 Enabled,
126 Downloading,
127 Disabling,
128}
129
130impl From<backups::BackupState> for BackupState {
131 fn from(value: backups::BackupState) -> Self {
132 match value {
133 backups::BackupState::Unknown => Self::Unknown,
134 backups::BackupState::Creating => Self::Creating,
135 backups::BackupState::Enabling => Self::Enabling,
136 backups::BackupState::Resuming => Self::Resuming,
137 backups::BackupState::Enabled => Self::Enabled,
138 backups::BackupState::Downloading => Self::Downloading,
139 backups::BackupState::Disabling => Self::Disabling,
140 }
141 }
142}
143
144impl From<backups::UploadState> for BackupUploadState {
145 fn from(value: backups::UploadState) -> Self {
146 match value {
147 backups::UploadState::Idle => Self::Waiting,
148 backups::UploadState::Uploading(count) => Self::Uploading {
149 backed_up_count: count.backed_up.try_into().unwrap_or(u32::MAX),
150 total_count: count.total.try_into().unwrap_or(u32::MAX),
151 },
152 backups::UploadState::Error => Self::Error,
153 backups::UploadState::Done => Self::Done,
154 }
155 }
156}
157
158#[derive(uniffi::Enum)]
159pub enum RecoveryState {
160 Unknown,
161 Enabled,
162 Disabled,
163 Incomplete,
164}
165
166impl From<recovery::RecoveryState> for RecoveryState {
167 fn from(value: recovery::RecoveryState) -> Self {
168 match value {
169 recovery::RecoveryState::Unknown => Self::Unknown,
170 recovery::RecoveryState::Enabled => Self::Enabled,
171 recovery::RecoveryState::Disabled => Self::Disabled,
172 recovery::RecoveryState::Incomplete => Self::Incomplete,
173 }
174 }
175}
176
177#[matrix_sdk_ffi_macros::export(callback_interface)]
178pub trait EnableRecoveryProgressListener: SyncOutsideWasm + SendOutsideWasm {
179 fn on_update(&self, status: EnableRecoveryProgress);
180}
181
182#[derive(uniffi::Enum)]
183pub enum EnableRecoveryProgress {
184 Starting,
185 CreatingBackup,
186 CreatingRecoveryKey,
187 BackingUp { backed_up_count: u32, total_count: u32 },
188 RoomKeyUploadError,
189 Done { recovery_key: String },
190}
191
192impl From<recovery::EnableProgress> for EnableRecoveryProgress {
193 fn from(value: recovery::EnableProgress) -> Self {
194 match &value {
195 recovery::EnableProgress::Starting => Self::Starting,
196 recovery::EnableProgress::CreatingBackup => Self::CreatingBackup,
197 recovery::EnableProgress::CreatingRecoveryKey => Self::CreatingRecoveryKey,
198 recovery::EnableProgress::BackingUp(counts) => Self::BackingUp {
199 backed_up_count: counts.backed_up.try_into().unwrap_or(u32::MAX),
200 total_count: counts.backed_up.try_into().unwrap_or(u32::MAX),
201 },
202 recovery::EnableProgress::RoomKeyUploadError => Self::RoomKeyUploadError,
203 recovery::EnableProgress::Done { recovery_key } => {
204 Self::Done { recovery_key: recovery_key.to_owned() }
205 }
206 }
207 }
208}
209
210#[derive(uniffi::Enum)]
211pub enum VerificationState {
212 Unknown,
213 Verified,
214 Unverified,
215}
216
217impl From<encryption::VerificationState> for VerificationState {
218 fn from(value: encryption::VerificationState) -> Self {
219 match &value {
220 encryption::VerificationState::Unknown => Self::Unknown,
221 encryption::VerificationState::Verified => Self::Verified,
222 encryption::VerificationState::Unverified => Self::Unverified,
223 }
224 }
225}
226
227#[matrix_sdk_ffi_macros::export]
228impl Encryption {
229 pub async fn ed25519_key(&self) -> Option<String> {
232 self.inner.ed25519_key().await
233 }
234
235 pub async fn curve25519_key(&self) -> Option<String> {
238 self.inner.curve25519_key().await.map(|k| k.to_base64())
239 }
240
241 pub fn backup_state_listener(&self, listener: Box<dyn BackupStateListener>) -> Arc<TaskHandle> {
242 let mut stream = self.inner.backups().state_stream();
243
244 let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
245 while let Some(state) = stream.next().await {
246 let Ok(state) = state else { continue };
247 listener.on_update(state.into());
248 }
249 }));
250
251 stream_task.into()
252 }
253
254 pub fn backup_state(&self) -> BackupState {
255 self.inner.backups().state().into()
256 }
257
258 pub async fn backup_exists_on_server(&self) -> Result<bool, ClientError> {
268 Ok(self.inner.backups().fetch_exists_on_server().await?)
269 }
270
271 pub fn recovery_state(&self) -> RecoveryState {
272 self.inner.recovery().state().into()
273 }
274
275 pub fn recovery_state_listener(
276 &self,
277 listener: Box<dyn RecoveryStateListener>,
278 ) -> Arc<TaskHandle> {
279 let mut stream = self.inner.recovery().state_stream();
280
281 let stream_task = TaskHandle::new(get_runtime_handle().spawn(async move {
282 while let Some(state) = stream.next().await {
283 listener.on_update(state.into());
284 }
285 }));
286
287 stream_task.into()
288 }
289
290 pub async fn enable_backups(&self) -> Result<()> {
291 Ok(self.inner.recovery().enable_backup().await?)
292 }
293
294 pub async fn is_last_device(&self) -> Result<bool> {
295 Ok(self.inner.recovery().is_last_device().await?)
296 }
297
298 pub async fn has_devices_to_verify_against(&self) -> Result<bool, ClientError> {
304 Ok(self.inner.has_devices_to_verify_against().await?)
305 }
306
307 pub async fn wait_for_backup_upload_steady_state(
308 &self,
309 progress_listener: Option<Box<dyn BackupSteadyStateListener>>,
310 ) -> Result<(), SteadyStateError> {
311 let backups = self.inner.backups();
312 let wait_for_steady_state = backups.wait_for_steady_state();
313
314 let task = if let Some(listener) = progress_listener {
315 let mut progress_stream = wait_for_steady_state.subscribe_to_progress();
316
317 Some(get_runtime_handle().spawn(async move {
318 while let Some(progress) = progress_stream.next().await {
319 let Ok(progress) = progress else { continue };
320 listener.on_update(progress.into());
321 }
322 }))
323 } else {
324 None
325 };
326
327 let result = wait_for_steady_state.await;
328
329 if let Some(task) = task {
330 task.abort();
331 }
332
333 Ok(result?)
334 }
335
336 pub async fn enable_recovery(
337 &self,
338 wait_for_backups_to_upload: bool,
339 mut passphrase: Option<String>,
340 progress_listener: Box<dyn EnableRecoveryProgressListener>,
341 ) -> Result<String> {
342 let recovery = self.inner.recovery();
343
344 let enable = if wait_for_backups_to_upload {
345 recovery.enable().wait_for_backups_to_upload()
346 } else {
347 recovery.enable()
348 };
349
350 let enable = if let Some(passphrase) = &passphrase {
351 enable.with_passphrase(passphrase)
352 } else {
353 enable
354 };
355
356 let mut progress_stream = enable.subscribe_to_progress();
357
358 let task = get_runtime_handle().spawn(async move {
359 while let Some(progress) = progress_stream.next().await {
360 let Ok(progress) = progress else { continue };
361 progress_listener.on_update(progress.into());
362 }
363 });
364
365 let ret = enable.await?;
366
367 task.abort();
368 passphrase.zeroize();
369
370 Ok(ret)
371 }
372
373 pub async fn disable_recovery(&self) -> Result<()> {
374 Ok(self.inner.recovery().disable().await?)
375 }
376
377 pub async fn reset_recovery_key(&self) -> Result<String> {
378 Ok(self.inner.recovery().reset_key().await?)
379 }
380
381 pub async fn recover_and_reset(&self, mut old_recovery_key: String) -> Result<String> {
382 let result = self.inner.recovery().recover_and_reset(&old_recovery_key).await;
383
384 old_recovery_key.zeroize();
385
386 Ok(result?)
387 }
388
389 pub async fn reset_identity(&self) -> Result<Option<Arc<IdentityResetHandle>>, ClientError> {
392 if let Some(reset_handle) =
393 self.inner.recovery().reset_identity().await.map_err(ClientError::from_err)?
394 {
395 return Ok(Some(Arc::new(IdentityResetHandle { inner: reset_handle })));
396 }
397
398 Ok(None)
399 }
400
401 pub async fn recover(&self, mut recovery_key: String) -> Result<()> {
402 let result = self.inner.recovery().recover(&recovery_key).await;
403
404 recovery_key.zeroize();
405
406 Ok(result?)
407 }
408
409 pub fn verification_state(&self) -> VerificationState {
410 self.inner.verification_state().get().into()
411 }
412
413 pub fn verification_state_listener(
414 self: Arc<Self>,
415 listener: Box<dyn VerificationStateListener>,
416 ) -> Arc<TaskHandle> {
417 let mut subscriber = self.inner.verification_state();
418
419 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
420 while let Some(verification_state) = subscriber.next().await {
421 listener.on_update(verification_state.into());
422 }
423 })))
424 }
425
426 pub async fn wait_for_e2ee_initialization_tasks(&self) {
429 self.inner.wait_for_e2ee_initialization_tasks().await;
430 }
431
432 pub async fn user_identity(
451 &self,
452 user_id: String,
453 fallback_to_server: bool,
454 ) -> Result<Option<Arc<UserIdentity>>, ClientError> {
455 match self.inner.get_user_identity(user_id.as_str().try_into()?).await {
456 Ok(Some(identity)) => {
457 return Ok(Some(Arc::new(UserIdentity { inner: identity })));
458 }
459 Ok(None) => {
460 info!("No identity found in the store.");
461 }
462 Err(error) => {
463 error!("Failed fetching identity from the store: {error}");
464 }
465 }
466
467 info!("Requesting identity from the server.");
468
469 if fallback_to_server {
470 let identity = self.inner.request_user_identity(user_id.as_str().try_into()?).await?;
471 Ok(identity.map(|identity| Arc::new(UserIdentity { inner: identity })))
472 } else {
473 Ok(None)
474 }
475 }
476}
477
478#[derive(uniffi::Object)]
480pub struct UserIdentity {
481 inner: matrix_sdk::encryption::identities::UserIdentity,
482}
483
484#[matrix_sdk_ffi_macros::export]
485impl UserIdentity {
486 pub(crate) async fn pin(&self) -> Result<(), ClientError> {
501 Ok(self.inner.pin().await?)
502 }
503
504 pub(crate) fn master_key(&self) -> Option<String> {
511 self.inner.master_key().get_first_key().map(|k| k.to_base64())
512 }
513
514 pub fn is_verified(&self) -> bool {
519 self.inner.is_verified()
520 }
521
522 pub fn was_previously_verified(&self) -> bool {
527 self.inner.was_previously_verified()
528 }
529
530 pub(crate) async fn withdraw_verification(&self) -> Result<(), ClientError> {
536 Ok(self.inner.withdraw_verification().await?)
537 }
538
539 pub fn has_verification_violation(&self) -> bool {
541 self.inner.has_verification_violation()
542 }
543}
544
545#[derive(uniffi::Object)]
546pub struct IdentityResetHandle {
547 pub(crate) inner: matrix_sdk::encryption::recovery::IdentityResetHandle,
548}
549
550#[matrix_sdk_ffi_macros::export]
551impl IdentityResetHandle {
552 pub fn auth_type(&self) -> CrossSigningResetAuthType {
555 self.inner.auth_type().into()
556 }
557
558 pub async fn reset(&self, auth: Option<AuthData>) -> Result<(), ClientError> {
566 if let Some(auth) = auth {
567 self.inner.reset(Some(auth.into())).await.map_err(ClientError::from_err)
568 } else {
569 self.inner.reset(None).await.map_err(ClientError::from_err)
570 }
571 }
572
573 pub async fn cancel(&self) {
574 self.inner.cancel().await;
575 }
576}
577
578#[derive(uniffi::Enum)]
579pub enum CrossSigningResetAuthType {
580 Uiaa,
582 Oidc {
585 info: OidcCrossSigningResetInfo,
586 },
587}
588
589impl From<&matrix_sdk::encryption::CrossSigningResetAuthType> for CrossSigningResetAuthType {
590 fn from(value: &matrix_sdk::encryption::CrossSigningResetAuthType) -> Self {
591 match value {
592 encryption::CrossSigningResetAuthType::Uiaa(_) => Self::Uiaa,
593 encryption::CrossSigningResetAuthType::OAuth(info) => Self::Oidc { info: info.into() },
594 }
595 }
596}
597
598#[derive(uniffi::Record)]
599pub struct OidcCrossSigningResetInfo {
600 pub approval_url: String,
602}
603
604impl From<&matrix_sdk::encryption::OAuthCrossSigningResetInfo> for OidcCrossSigningResetInfo {
605 fn from(value: &matrix_sdk::encryption::OAuthCrossSigningResetInfo) -> Self {
606 Self { approval_url: value.approval_url.to_string() }
607 }
608}