| 1 | // Copyright 2025 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 the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | use std::{fmt::Debug, sync::Arc, time::Duration}; |
| 16 | |
| 17 | use matrix_sdk_base::crypto::types::events::UtdCause; |
| 18 | use matrix_sdk_common::{SendOutsideWasm, SyncOutsideWasm}; |
| 19 | use matrix_sdk_ui::unable_to_decrypt_hook::{ |
| 20 | UnableToDecryptHook, UnableToDecryptInfo as SdkUnableToDecryptInfo, |
| 21 | }; |
| 22 | |
| 23 | #[matrix_sdk_ffi_macros::export(callback_interface)] |
| 24 | pub trait UnableToDecryptDelegate: SyncOutsideWasm + SendOutsideWasm { |
| 25 | fn on_utd(&self, info: UnableToDecryptInfo); |
| 26 | } |
| 27 | |
| 28 | pub struct UtdHook { |
| 29 | pub delegate: Arc<dyn UnableToDecryptDelegate>, |
| 30 | } |
| 31 | |
| 32 | impl std::fmt::Debug for UtdHook { |
| 33 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 34 | f.debug_struct("UtdHook").finish_non_exhaustive() |
| 35 | } |
| 36 | } |
| 37 | |
| 38 | impl UnableToDecryptHook for UtdHook { |
| 39 | fn on_utd(&self, info: SdkUnableToDecryptInfo) { |
| 40 | const IGNORE_UTD_PERIOD: Duration = Duration::from_secs(4); |
| 41 | |
| 42 | // UTDs that have been decrypted in the `IGNORE_UTD_PERIOD` are just ignored and |
| 43 | // not considered UTDs. |
| 44 | if let Some(duration) = &info.time_to_decrypt |
| 45 | && *duration < IGNORE_UTD_PERIOD |
| 46 | { |
| 47 | return; |
| 48 | } |
| 49 | |
| 50 | // Report the UTD to the client. |
| 51 | self.delegate.on_utd(info.into()); |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | #[derive(uniffi::Record)] |
| 56 | pub struct UnableToDecryptInfo { |
| 57 | /// The identifier of the event that couldn't get decrypted. |
| 58 | event_id: String, |
| 59 | |
| 60 | /// If the event could be decrypted late (that is, the event was encrypted |
| 61 | /// at first, but could be decrypted later on), then this indicates the |
| 62 | /// time it took to decrypt the event. If it is not set, this is |
| 63 | /// considered a definite UTD. |
| 64 | /// |
| 65 | /// If set, this is in milliseconds. |
| 66 | pub time_to_decrypt_ms: Option<u64>, |
| 67 | |
| 68 | /// What we know about what caused this UTD. E.g. was this event sent when |
| 69 | /// we were not a member of this room? |
| 70 | pub cause: UtdCause, |
| 71 | |
| 72 | /// The difference between the event creation time (`origin_server_ts`) and |
| 73 | /// the time our device was created. If negative, this event was sent |
| 74 | /// *before* our device was created. |
| 75 | pub event_local_age_millis: i64, |
| 76 | |
| 77 | /// Whether the user had verified their own identity at the point they |
| 78 | /// received the UTD event. |
| 79 | pub user_trusts_own_identity: bool, |
| 80 | |
| 81 | /// The homeserver of the user that sent the undecryptable event. |
| 82 | pub sender_homeserver: String, |
| 83 | |
| 84 | /// Our local user's own homeserver, or `None` if the client is not logged |
| 85 | /// in. |
| 86 | pub own_homeserver: Option<String>, |
| 87 | } |
| 88 | |
| 89 | impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo { |
| 90 | fn from(value: SdkUnableToDecryptInfo) -> Self { |
| 91 | Self { |
| 92 | event_id: value.event_id.to_string(), |
| 93 | time_to_decrypt_ms: value.time_to_decrypt.map(|ttd| ttd.as_millis() as u64), |
| 94 | cause: value.cause, |
| 95 | event_local_age_millis: value.event_local_age_millis, |
| 96 | user_trusts_own_identity: value.user_trusts_own_identity, |
| 97 | sender_homeserver: value.sender_homeserver.to_string(), |
| 98 | own_homeserver: value.own_homeserver.map(String::from), |
| 99 | } |
| 100 | } |
| 101 | } |