1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for that specific language governing permissions and
13// limitations under the License.
1415use std::{fmt::Debug, sync::Arc, time::Duration};
1617use async_compat::get_runtime_handle;
18use futures_util::pin_mut;
19use matrix_sdk::{crypto::types::events::UtdCause, Client};
20use matrix_sdk_ui::{
21 sync_service::{
22 State as MatrixSyncServiceState, SyncService as MatrixSyncService,
23 SyncServiceBuilder as MatrixSyncServiceBuilder,
24 },
25 unable_to_decrypt_hook::{
26 UnableToDecryptHook, UnableToDecryptInfo as SdkUnableToDecryptInfo, UtdHookManager,
27 },
28};
29use tracing::error;
3031use crate::{
32 error::ClientError, helpers::unwrap_or_clone_arc, room_list::RoomListService, TaskHandle,
33};
3435#[derive(uniffi::Enum)]
36pub enum SyncServiceState {
37 Idle,
38 Running,
39 Terminated,
40 Error,
41 Offline,
42}
4344impl From<MatrixSyncServiceState> for SyncServiceState {
45fn from(value: MatrixSyncServiceState) -> Self {
46match value {
47 MatrixSyncServiceState::Idle => Self::Idle,
48 MatrixSyncServiceState::Running => Self::Running,
49 MatrixSyncServiceState::Terminated => Self::Terminated,
50 MatrixSyncServiceState::Error => Self::Error,
51 MatrixSyncServiceState::Offline => Self::Offline,
52 }
53 }
54}
5556#[matrix_sdk_ffi_macros::export(callback_interface)]
57pub trait SyncServiceStateObserver: Send + Sync + Debug {
58fn on_update(&self, state: SyncServiceState);
59}
6061#[derive(uniffi::Object)]
62pub struct SyncService {
63pub(crate) inner: Arc<MatrixSyncService>,
64 utd_hook: Option<Arc<UtdHookManager>>,
65}
6667#[matrix_sdk_ffi_macros::export]
68impl SyncService {
69pub fn room_list_service(&self) -> Arc<RoomListService> {
70 Arc::new(RoomListService {
71 inner: self.inner.room_list_service(),
72 utd_hook: self.utd_hook.clone(),
73 })
74 }
7576pub async fn start(&self) {
77self.inner.start().await
78}
7980pub async fn stop(&self) {
81self.inner.stop().await
82}
8384pub fn state(&self, listener: Box<dyn SyncServiceStateObserver>) -> Arc<TaskHandle> {
85let state_stream = self.inner.state();
8687 Arc::new(TaskHandle::new(get_runtime_handle().spawn(async move {
88pin_mut!(state_stream);
8990while let Some(state) = state_stream.next().await {
91 listener.on_update(state.into());
92 }
93 })))
94 }
95}
9697#[derive(Clone, uniffi::Object)]
98pub struct SyncServiceBuilder {
99 client: Client,
100 builder: MatrixSyncServiceBuilder,
101102 utd_hook: Option<Arc<UtdHookManager>>,
103}
104105impl SyncServiceBuilder {
106pub(crate) fn new(client: Client) -> Arc<Self> {
107 Arc::new(Self {
108 client: client.clone(),
109 builder: MatrixSyncService::builder(client),
110 utd_hook: None,
111 })
112 }
113}
114115#[matrix_sdk_ffi_macros::export]
116impl SyncServiceBuilder {
117pub fn with_cross_process_lock(self: Arc<Self>) -> Arc<Self> {
118let this = unwrap_or_clone_arc(self);
119let builder = this.builder.with_cross_process_lock();
120 Arc::new(Self { client: this.client, builder, utd_hook: this.utd_hook })
121 }
122123/// Enable the "offline" mode for the [`SyncService`].
124pub fn with_offline_mode(self: Arc<Self>) -> Arc<Self> {
125let this = unwrap_or_clone_arc(self);
126let builder = this.builder.with_offline_mode();
127 Arc::new(Self { client: this.client, builder, utd_hook: this.utd_hook })
128 }
129130pub async fn with_utd_hook(
131self: Arc<Self>,
132 delegate: Box<dyn UnableToDecryptDelegate>,
133 ) -> Arc<Self> {
134// UTDs detected before this duration may be reclassified as "late decryption"
135 // events (or discarded, if they get decrypted fast enough).
136const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
137138let this = unwrap_or_clone_arc(self);
139140let mut utd_hook = UtdHookManager::new(Arc::new(UtdHook { delegate }), this.client.clone())
141 .with_max_delay(UTD_HOOK_GRACE_PERIOD);
142143if let Err(e) = utd_hook.reload_from_store().await {
144error!("Unable to reload UTD hook data from data store: {}", e);
145// Carry on with the setup anyway; we shouldn't fail setup just
146 // because the UTD hook failed to load its data.
147}
148149 Arc::new(Self {
150 client: this.client,
151 builder: this.builder,
152 utd_hook: Some(Arc::new(utd_hook)),
153 })
154 }
155156pub async fn finish(self: Arc<Self>) -> Result<Arc<SyncService>, ClientError> {
157let this = unwrap_or_clone_arc(self);
158Ok(Arc::new(SyncService {
159 inner: Arc::new(this.builder.build().await?),
160 utd_hook: this.utd_hook,
161 }))
162 }
163}
164165#[matrix_sdk_ffi_macros::export(callback_interface)]
166pub trait UnableToDecryptDelegate: Sync + Send {
167fn on_utd(&self, info: UnableToDecryptInfo);
168}
169170struct UtdHook {
171 delegate: Box<dyn UnableToDecryptDelegate>,
172}
173174impl std::fmt::Debug for UtdHook {
175fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 f.debug_struct("UtdHook").finish_non_exhaustive()
177 }
178}
179180impl UnableToDecryptHook for UtdHook {
181fn on_utd(&self, info: SdkUnableToDecryptInfo) {
182const IGNORE_UTD_PERIOD: Duration = Duration::from_secs(4);
183184// UTDs that have been decrypted in the `IGNORE_UTD_PERIOD` are just ignored and
185 // not considered UTDs.
186if let Some(duration) = &info.time_to_decrypt {
187if *duration < IGNORE_UTD_PERIOD {
188return;
189 }
190 }
191192// Report the UTD to the client.
193self.delegate.on_utd(info.into());
194 }
195}
196197#[derive(uniffi::Record)]
198pub struct UnableToDecryptInfo {
199/// The identifier of the event that couldn't get decrypted.
200event_id: String,
201202/// If the event could be decrypted late (that is, the event was encrypted
203 /// at first, but could be decrypted later on), then this indicates the
204 /// time it took to decrypt the event. If it is not set, this is
205 /// considered a definite UTD.
206 ///
207 /// If set, this is in milliseconds.
208pub time_to_decrypt_ms: Option<u64>,
209210/// What we know about what caused this UTD. E.g. was this event sent when
211 /// we were not a member of this room?
212pub cause: UtdCause,
213214/// The difference between the event creation time (`origin_server_ts`) and
215 /// the time our device was created. If negative, this event was sent
216 /// *before* our device was created.
217pub event_local_age_millis: i64,
218219/// Whether the user had verified their own identity at the point they
220 /// received the UTD event.
221pub user_trusts_own_identity: bool,
222223/// The homeserver of the user that sent the undecryptable event.
224pub sender_homeserver: String,
225226/// Our local user's own homeserver, or `None` if the client is not logged
227 /// in.
228pub own_homeserver: Option<String>,
229}
230231impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo {
232fn from(value: SdkUnableToDecryptInfo) -> Self {
233Self {
234 event_id: value.event_id.to_string(),
235 time_to_decrypt_ms: value.time_to_decrypt.map(|ttd| ttd.as_millis() as u64),
236 cause: value.cause,
237 event_local_age_millis: value.event_local_age_millis,
238 user_trusts_own_identity: value.user_trusts_own_identity,
239 sender_homeserver: value.sender_homeserver.to_string(),
240 own_homeserver: value.own_homeserver.map(String::from),
241 }
242 }
243}