matrix_sdk_ffi/
sync_service.rs

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.
14
15use std::{fmt::Debug, sync::Arc, time::Duration};
16
17use futures_util::pin_mut;
18use matrix_sdk::{crypto::types::events::UtdCause, Client};
19use matrix_sdk_ui::{
20    sync_service::{
21        State as MatrixSyncServiceState, SyncService as MatrixSyncService,
22        SyncServiceBuilder as MatrixSyncServiceBuilder,
23    },
24    unable_to_decrypt_hook::{
25        UnableToDecryptHook, UnableToDecryptInfo as SdkUnableToDecryptInfo, UtdHookManager,
26    },
27};
28use tracing::error;
29
30use crate::{
31    error::ClientError, helpers::unwrap_or_clone_arc, room_list::RoomListService, TaskHandle,
32    RUNTIME,
33};
34
35#[derive(uniffi::Enum)]
36pub enum SyncServiceState {
37    Idle,
38    Running,
39    Terminated,
40    Error,
41    Offline,
42}
43
44impl From<MatrixSyncServiceState> for SyncServiceState {
45    fn from(value: MatrixSyncServiceState) -> Self {
46        match 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}
55
56#[matrix_sdk_ffi_macros::export(callback_interface)]
57pub trait SyncServiceStateObserver: Send + Sync + Debug {
58    fn on_update(&self, state: SyncServiceState);
59}
60
61#[derive(uniffi::Object)]
62pub struct SyncService {
63    pub(crate) inner: Arc<MatrixSyncService>,
64    utd_hook: Option<Arc<UtdHookManager>>,
65}
66
67#[matrix_sdk_ffi_macros::export]
68impl SyncService {
69    pub 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    }
75
76    pub async fn start(&self) {
77        self.inner.start().await
78    }
79
80    pub async fn stop(&self) {
81        self.inner.stop().await
82    }
83
84    pub fn state(&self, listener: Box<dyn SyncServiceStateObserver>) -> Arc<TaskHandle> {
85        let state_stream = self.inner.state();
86
87        Arc::new(TaskHandle::new(RUNTIME.spawn(async move {
88            pin_mut!(state_stream);
89
90            while let Some(state) = state_stream.next().await {
91                listener.on_update(state.into());
92            }
93        })))
94    }
95}
96
97#[derive(Clone, uniffi::Object)]
98pub struct SyncServiceBuilder {
99    client: Client,
100    builder: MatrixSyncServiceBuilder,
101
102    utd_hook: Option<Arc<UtdHookManager>>,
103}
104
105impl SyncServiceBuilder {
106    pub(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}
114
115#[matrix_sdk_ffi_macros::export]
116impl SyncServiceBuilder {
117    pub fn with_cross_process_lock(self: Arc<Self>) -> Arc<Self> {
118        let this = unwrap_or_clone_arc(self);
119        let builder = this.builder.with_cross_process_lock();
120        Arc::new(Self { client: this.client, builder, utd_hook: this.utd_hook })
121    }
122
123    /// Enable the "offline" mode for the [`SyncService`].
124    pub fn with_offline_mode(self: Arc<Self>) -> Arc<Self> {
125        let this = unwrap_or_clone_arc(self);
126        let builder = this.builder.with_offline_mode();
127        Arc::new(Self { client: this.client, builder, utd_hook: this.utd_hook })
128    }
129
130    pub async fn with_utd_hook(
131        self: 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).
136        const UTD_HOOK_GRACE_PERIOD: Duration = Duration::from_secs(60);
137
138        let this = unwrap_or_clone_arc(self);
139
140        let mut utd_hook = UtdHookManager::new(Arc::new(UtdHook { delegate }), this.client.clone())
141            .with_max_delay(UTD_HOOK_GRACE_PERIOD);
142
143        if let Err(e) = utd_hook.reload_from_store().await {
144            error!("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        }
148
149        Arc::new(Self {
150            client: this.client,
151            builder: this.builder,
152            utd_hook: Some(Arc::new(utd_hook)),
153        })
154    }
155
156    pub async fn finish(self: Arc<Self>) -> Result<Arc<SyncService>, ClientError> {
157        let this = unwrap_or_clone_arc(self);
158        Ok(Arc::new(SyncService {
159            inner: Arc::new(this.builder.build().await?),
160            utd_hook: this.utd_hook,
161        }))
162    }
163}
164
165#[matrix_sdk_ffi_macros::export(callback_interface)]
166pub trait UnableToDecryptDelegate: Sync + Send {
167    fn on_utd(&self, info: UnableToDecryptInfo);
168}
169
170struct UtdHook {
171    delegate: Box<dyn UnableToDecryptDelegate>,
172}
173
174impl std::fmt::Debug for UtdHook {
175    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176        f.debug_struct("UtdHook").finish_non_exhaustive()
177    }
178}
179
180impl UnableToDecryptHook for UtdHook {
181    fn on_utd(&self, info: SdkUnableToDecryptInfo) {
182        const IGNORE_UTD_PERIOD: Duration = Duration::from_secs(4);
183
184        // UTDs that have been decrypted in the `IGNORE_UTD_PERIOD` are just ignored and
185        // not considered UTDs.
186        if let Some(duration) = &info.time_to_decrypt {
187            if *duration < IGNORE_UTD_PERIOD {
188                return;
189            }
190        }
191
192        // Report the UTD to the client.
193        self.delegate.on_utd(info.into());
194    }
195}
196
197#[derive(uniffi::Record)]
198pub struct UnableToDecryptInfo {
199    /// The identifier of the event that couldn't get decrypted.
200    event_id: String,
201
202    /// 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.
208    pub time_to_decrypt_ms: Option<u64>,
209
210    /// What we know about what caused this UTD. E.g. was this event sent when
211    /// we were not a member of this room?
212    pub cause: UtdCause,
213
214    /// 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.
217    pub event_local_age_millis: i64,
218
219    /// Whether the user had verified their own identity at the point they
220    /// received the UTD event.
221    pub user_trusts_own_identity: bool,
222
223    /// The homeserver of the user that sent the undecryptable event.
224    pub sender_homeserver: String,
225
226    /// Our local user's own homeserver, or `None` if the client is not logged
227    /// in.
228    pub own_homeserver: Option<String>,
229}
230
231impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo {
232    fn from(value: SdkUnableToDecryptInfo) -> Self {
233        Self {
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}