Merge branch 'main' into bnjbvr/permalink-mvp

This commit is contained in:
Ivan Enderlin
2024-04-25 16:02:23 +02:00
16 changed files with 387 additions and 60 deletions

View File

@@ -4,6 +4,7 @@ use std::{
};
use matrix_sdk::{
encryption::BackupDownloadStrategy,
oidc::{
registrations::{ClientId, OidcRegistrations, OidcRegistrationsError},
types::{
@@ -621,12 +622,9 @@ impl AuthenticationService {
.passphrase(self.passphrase.clone())
.homeserver_url(homeserver_url)
.sliding_sync_proxy(sliding_sync_proxy)
.with_encryption_settings(matrix_sdk::encryption::EncryptionSettings {
auto_enable_cross_signing: true,
backup_download_strategy:
matrix_sdk::encryption::BackupDownloadStrategy::AfterDecryptionFailure,
auto_enable_backups: true,
})
.auto_enable_cross_signing(true)
.backup_download_strategy(BackupDownloadStrategy::AfterDecryptionFailure)
.auto_enable_backups(true)
.username(user_id.to_string());
if let Some(proxy) = &self.proxy {

View File

@@ -76,6 +76,7 @@ pub struct ClientBuilder {
cross_process_refresh_lock_id: Option<String>,
session_delegate: Option<Arc<dyn ClientSessionDelegate>>,
additional_root_certificates: Vec<Vec<u8>>,
encryption_settings: EncryptionSettings,
}
#[uniffi::export(async_runtime = "tokio")]
@@ -93,14 +94,16 @@ impl ClientBuilder {
proxy: None,
disable_ssl_verification: false,
disable_automatic_token_refresh: false,
inner: MatrixClient::builder().with_encryption_settings(EncryptionSettings {
auto_enable_cross_signing: false,
backup_download_strategy: BackupDownloadStrategy::AfterDecryptionFailure,
auto_enable_backups: false,
}),
inner: MatrixClient::builder(),
cross_process_refresh_lock_id: None,
session_delegate: None,
additional_root_certificates: Default::default(),
encryption_settings: EncryptionSettings {
auto_enable_cross_signing: false,
backup_download_strategy:
matrix_sdk::encryption::BackupDownloadStrategy::AfterDecryptionFailure,
auto_enable_backups: false,
},
})
}
@@ -203,21 +206,41 @@ impl ClientBuilder {
Arc::new(builder)
}
pub fn auto_enable_cross_signing(
self: Arc<Self>,
auto_enable_cross_signing: bool,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.encryption_settings.auto_enable_cross_signing = auto_enable_cross_signing;
Arc::new(builder)
}
/// Select a strategy to download room keys from the backup. By default
/// we download after a decryption failure.
///
/// Take a look at the [`BackupDownloadStrategy`] enum for more options.
pub fn backup_download_strategy(
self: Arc<Self>,
backup_download_strategy: BackupDownloadStrategy,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.encryption_settings.backup_download_strategy = backup_download_strategy;
Arc::new(builder)
}
/// Automatically create a backup version if no backup exists.
pub fn auto_enable_backups(self: Arc<Self>, auto_enable_backups: bool) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.encryption_settings.auto_enable_backups = auto_enable_backups;
Arc::new(builder)
}
pub async fn build(self: Arc<Self>) -> Result<Arc<Client>, ClientBuildError> {
Ok(Arc::new(self.build_inner().await?))
}
}
impl ClientBuilder {
pub(crate) fn with_encryption_settings(
self: Arc<Self>,
settings: EncryptionSettings,
) -> Arc<Self> {
let mut builder = unwrap_or_clone_arc(self);
builder.inner = builder.inner.with_encryption_settings(settings);
Arc::new(builder)
}
pub(crate) fn enable_cross_process_refresh_lock_inner(
self: Arc<Self>,
process_id: String,
@@ -316,6 +339,8 @@ impl ClientBuilder {
);
}
inner_builder = inner_builder.with_encryption_settings(builder.encryption_settings);
let sdk_client = inner_builder.build().await?;
// At this point, `sdk_client` might contain a `sliding_sync_proxy` that has

View File

@@ -15,7 +15,7 @@
use std::{fmt::Debug, sync::Arc, time::Duration};
use futures_util::pin_mut;
use matrix_sdk::Client;
use matrix_sdk::{crypto::types::events::UtdCause, Client};
use matrix_sdk_ui::{
sync_service::{
State as MatrixSyncServiceState, SyncService as MatrixSyncService,
@@ -187,6 +187,10 @@ pub struct UnableToDecryptInfo {
///
/// If set, this is in milliseconds.
pub time_to_decrypt_ms: Option<u64>,
/// What we know about what caused this UTD. E.g. was this event sent when
/// we were not a member of this room?
pub cause: UtdCause,
}
impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo {
@@ -194,6 +198,7 @@ impl From<SdkUnableToDecryptInfo> for UnableToDecryptInfo {
Self {
event_id: value.event_id.to_string(),
time_to_decrypt_ms: value.time_to_decrypt.map(|ttd| ttd.as_millis() as u64),
cause: value.cause,
}
}
}

View File

@@ -14,7 +14,7 @@
use std::{collections::HashMap, sync::Arc};
use matrix_sdk::room::power_levels::power_level_user_changes;
use matrix_sdk::{crypto::types::events::UtdCause, room::power_levels::power_level_user_changes};
use matrix_sdk_ui::timeline::{PollResult, TimelineDetails};
use tracing::warn;
@@ -214,6 +214,10 @@ pub enum EncryptedMessage {
MegolmV1AesSha2 {
/// The ID of the session used to encrypt the message.
session_id: String,
/// What we know about what caused this UTD. E.g. was this event sent
/// when we were not a member of this room?
cause: UtdCause,
},
Unknown,
}
@@ -227,9 +231,9 @@ impl EncryptedMessage {
let sender_key = sender_key.clone();
Self::OlmV1Curve25519AesSha2 { sender_key }
}
Message::MegolmV1AesSha2 { session_id, .. } => {
Message::MegolmV1AesSha2 { session_id, cause, .. } => {
let session_id = session_id.clone();
Self::MegolmV1AesSha2 { session_id }
Self::MegolmV1AesSha2 { session_id, cause: *cause }
}
Message::Unknown => Self::Unknown,
}

View File

@@ -186,7 +186,7 @@ impl Timeline {
/// Paginate forwards, when in focused mode.
///
/// Returns whether we hit the end of the timeline or not.
pub async fn paginate_forwards(&self, num_events: u16) -> Result<bool, ClientError> {
pub async fn focused_paginate_forwards(&self, num_events: u16) -> Result<bool, ClientError> {
Ok(self.inner.focused_paginate_forwards(num_events).await?)
}

View File

@@ -23,7 +23,7 @@ qrcode = ["matrix-sdk-crypto?/qrcode"]
automatic-room-key-forwarding = ["matrix-sdk-crypto?/automatic-room-key-forwarding"]
message-ids = ["matrix-sdk-crypto?/message-ids"]
experimental-sliding-sync = ["ruma/unstable-msc3575"]
uniffi = ["dep:uniffi"]
uniffi = ["dep:uniffi", "matrix-sdk-crypto?/uniffi"]
# helpers for testing features build upon this
testing = [

View File

@@ -27,9 +27,11 @@ pub mod room_key_request;
pub mod room_key_withheld;
pub mod secret_send;
mod to_device;
mod utd_cause;
use ruma::serde::Raw;
pub use to_device::{ToDeviceCustomEvent, ToDeviceEvent, ToDeviceEvents};
pub use utd_cause::UtdCause;
/// A trait for event contents to define their event type.
pub trait EventType {

View File

@@ -0,0 +1,153 @@
// Copyright 2024 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
use serde::Deserialize;
/// Our best guess at the reason why an event can't be decrypted.
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum UtdCause {
/// We don't have an explanation for why this UTD happened - it is probably
/// a bug, or a network split between the two homeservers.
#[default]
Unknown = 0,
/// This event was sent when we were not a member of the room (or invited),
/// so it is impossible to decrypt (without MSC3061).
Membership = 1,
//
// TODO: Other causes for UTDs. For example, this message is device-historical, information
// extracted from the WithheldCode in the MissingRoomKey object, or various types of Olm
// session problems.
//
// Note: This needs to be a simple enum so we can export it via FFI, so if more information
// needs to be provided, it should be through a separate type.
}
/// MSC4115 membership info in the unsigned area.
#[derive(Deserialize)]
struct UnsignedWithMembership {
#[serde(alias = "io.element.msc4115.membership")]
membership: Membership,
}
/// MSC4115 contents of the membership property
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
enum Membership {
Leave,
Invite,
Join,
}
impl UtdCause {
/// Decide the cause of this UTD, based on the evidence we have.
pub fn determine(raw_event: Option<&Raw<AnySyncTimelineEvent>>) -> Self {
// TODO: in future, use more information to give a richer answer. E.g.
// is this event device-historical? Was the Olm communication disrupted?
// Did the sender refuse to send the key because we're not verified?
// Look in the unsigned area for a `membership` field.
if let Some(raw_event) = raw_event {
if let Ok(Some(unsigned)) = raw_event.get_field::<UnsignedWithMembership>("unsigned") {
if let Membership::Leave = unsigned.membership {
// We were not a member - this is the cause of the UTD
return UtdCause::Membership;
}
}
}
// We can't find an explanation for this UTD
UtdCause::Unknown
}
}
#[cfg(test)]
mod tests {
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
use serde_json::{json, value::to_raw_value};
use crate::types::events::UtdCause;
#[test]
fn a_missing_raw_event_means_we_guess_unknown() {
// When we don't provide any JSON to check for membership, then we guess the UTD
// is unknown.
assert_eq!(UtdCause::determine(None), UtdCause::Unknown);
}
#[test]
fn if_there_is_no_membership_info_we_guess_unknown() {
// If our JSON contains no membership info, then we guess the UTD is unknown.
assert_eq!(UtdCause::determine(Some(&raw_event(json!({})))), UtdCause::Unknown);
}
#[test]
fn if_membership_info_cant_be_parsed_we_guess_unknown() {
// If our JSON contains a membership property but not the JSON we expected, then
// we guess the UTD is unknown.
assert_eq!(
UtdCause::determine(Some(&raw_event(json!({ "unsigned": { "membership": 3 } })))),
UtdCause::Unknown
);
}
#[test]
fn if_membership_is_invite_we_guess_unknown() {
// If membership=invite then we expected to be sent the keys so the cause of the
// UTD is unknown.
assert_eq!(
UtdCause::determine(Some(&raw_event(
json!({ "unsigned": { "membership": "invite" } }),
))),
UtdCause::Unknown
);
}
#[test]
fn if_membership_is_join_we_guess_unknown() {
// If membership=join then we expected to be sent the keys so the cause of the
// UTD is unknown.
assert_eq!(
UtdCause::determine(Some(&raw_event(json!({ "unsigned": { "membership": "join" } })))),
UtdCause::Unknown
);
}
#[test]
fn if_membership_is_leave_we_guess_membership() {
// If membership=leave then we have an explanation for why we can't decrypt,
// until we have MSC3061.
assert_eq!(
UtdCause::determine(Some(&raw_event(json!({ "unsigned": { "membership": "leave" } })))),
UtdCause::Membership
);
}
#[test]
fn if_unstable_prefix_membership_is_leave_we_guess_membership() {
// Before MSC4115 is merged, we support the unstable prefix too.
assert_eq!(
UtdCause::determine(Some(&raw_event(
json!({ "unsigned": { "io.element.msc4115.membership": "leave" } })
))),
UtdCause::Membership
);
}
fn raw_event(value: serde_json::Value) -> Raw<AnySyncTimelineEvent> {
Raw::from_json(to_raw_value(&value).unwrap())
}
}

View File

@@ -18,7 +18,7 @@ experimental-room-list-with-unified-invites = []
native-tls = ["matrix-sdk/native-tls"]
rustls-tls = ["matrix-sdk/rustls-tls"]
uniffi = ["dep:uniffi"]
uniffi = ["dep:uniffi", "matrix-sdk/uniffi", "matrix-sdk-base/uniffi"]
[dependencies]
as_variant = { workspace = true }

View File

@@ -17,7 +17,7 @@ use std::sync::Arc;
use as_variant::as_variant;
use eyeball_im::{ObservableVectorTransaction, ObservableVectorTransactionEntry};
use indexmap::{map::Entry, IndexMap};
use matrix_sdk::deserialized_responses::EncryptionInfo;
use matrix_sdk::{crypto::types::events::UtdCause, deserialized_responses::EncryptionInfo};
use ruma::{
events::{
poll::{
@@ -276,11 +276,15 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
/// Handle an event.
///
/// Returns the number of timeline updates that were made.
///
/// `raw_event` is only needed to determine the cause of any UTDs,
/// so if we know this is not a UTD it can be None.
#[instrument(skip_all, fields(txn_id, event_id, position))]
pub(super) fn handle_event(
mut self,
day_divider_adjuster: &mut DayDividerAdjuster,
event_kind: TimelineEventKind,
raw_event: Option<&Raw<AnySyncTimelineEvent>>,
) -> HandleEventResult {
let span = tracing::Span::current();
@@ -348,13 +352,14 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
}
AnyMessageLikeEventContent::RoomEncrypted(c) => {
// TODO: Handle replacements if the replaced event is also UTD
self.add(should_add, TimelineItemContent::unable_to_decrypt(c));
let cause = UtdCause::determine(raw_event);
self.add(true, TimelineItemContent::unable_to_decrypt(c, cause));
// Let the hook know that we ran into an unable-to-decrypt that is added to the
// timeline.
if let Some(hook) = self.meta.unable_to_decrypt_hook.as_ref() {
if let Flow::Remote { event_id, .. } = &self.ctx.flow {
hook.on_utd(event_id);
hook.on_utd(event_id, cause);
}
}
}

View File

@@ -16,6 +16,7 @@ use std::sync::Arc;
use as_variant::as_variant;
use imbl::Vector;
use matrix_sdk::crypto::types::events::UtdCause;
use matrix_sdk_base::latest_event::{is_suitable_for_latest_event, PossibleLatestEvent};
use ruma::{
events::{
@@ -248,8 +249,8 @@ impl TimelineItemContent {
}
}
pub(crate) fn unable_to_decrypt(content: RoomEncryptedEventContent) -> Self {
Self::UnableToDecrypt(content.into())
pub(crate) fn unable_to_decrypt(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
Self::UnableToDecrypt(EncryptedMessage::from_content(content, cause))
}
pub(crate) fn room_member(
@@ -356,21 +357,26 @@ pub enum EncryptedMessage {
/// The ID of the session used to encrypt the message.
session_id: String,
/// What we know about what caused this UTD. E.g. was this event sent
/// when we were not a member of this room?
cause: UtdCause,
},
/// No metadata because the event uses an unknown algorithm.
Unknown,
}
impl From<RoomEncryptedEventContent> for EncryptedMessage {
fn from(c: RoomEncryptedEventContent) -> Self {
match c.scheme {
impl EncryptedMessage {
fn from_content(content: RoomEncryptedEventContent, cause: UtdCause) -> Self {
match content.scheme {
EncryptedEventScheme::OlmV1Curve25519AesSha2(s) => {
Self::OlmV1Curve25519AesSha2 { sender_key: s.sender_key }
}
#[allow(deprecated)]
EncryptedEventScheme::MegolmV1AesSha2(s) => {
let MegolmV1AesSha2Content { sender_key, device_id, session_id, .. } = s;
Self::MegolmV1AesSha2 { sender_key, device_id, session_id }
Self::MegolmV1AesSha2 { sender_key, device_id, session_id, cause }
}
_ => Self::Unknown,
}

View File

@@ -875,6 +875,8 @@ impl<P: RoomDataProvider> TimelineInner<P> {
decryptor: impl Decryptor,
session_ids: Option<BTreeSet<String>>,
) {
use matrix_sdk::crypto::types::events::UtdCause;
use super::EncryptedMessage;
let mut state = self.state.clone().write_owned().await;
@@ -953,9 +955,11 @@ impl<P: RoomDataProvider> TimelineInner<P> {
"Successfully decrypted event that previously failed to decrypt"
);
let cause = UtdCause::determine(Some(original_json));
// Notify observers that we managed to eventually decrypt an event.
if let Some(hook) = unable_to_decrypt_hook {
hook.on_late_decrypt(&remote_event.event_id);
hook.on_late_decrypt(&remote_event.event_id, cause);
}
Some(event)

View File

@@ -191,7 +191,13 @@ impl TimelineInnerState {
let mut day_divider_adjuster = DayDividerAdjuster::default();
TimelineEventHandler::new(&mut txn, ctx).handle_event(&mut day_divider_adjuster, content);
TimelineEventHandler::new(&mut txn, ctx).handle_event(
&mut day_divider_adjuster,
content,
// Local events are never UTD, so no need to pass in a raw_event - this is only used to
// determine the type of UTD if there is one.
None,
);
txn.adjust_day_dividers(day_divider_adjuster);
@@ -564,14 +570,18 @@ impl TimelineInnerStateTransaction<'_> {
is_highlighted: event.push_actions.iter().any(Action::is_highlight),
flow: Flow::Remote {
event_id: event_id.clone(),
raw_event: raw,
raw_event: raw.clone(),
txn_id,
position,
should_add,
},
};
TimelineEventHandler::new(self, ctx).handle_event(day_divider_adjuster, event_kind)
TimelineEventHandler::new(self, ctx).handle_event(
day_divider_adjuster,
event_kind,
Some(&raw),
)
}
fn clear(&mut self) {

View File

@@ -23,16 +23,22 @@ use std::{
use assert_matches::assert_matches;
use assert_matches2::assert_let;
use eyeball_im::VectorDiff;
use matrix_sdk::crypto::{decrypt_room_key_export, OlmMachine};
use matrix_sdk::crypto::{decrypt_room_key_export, types::events::UtdCause, OlmMachine};
use matrix_sdk_test::{async_test, BOB};
use ruma::{
assign,
events::room::encrypted::{
EncryptedEventScheme, MegolmV1AesSha2ContentInit, Relation, Replacement,
RoomEncryptedEventContent,
events::{
room::encrypted::{
EncryptedEventScheme, MegolmV1AesSha2ContentInit, Relation, Replacement,
RoomEncryptedEventContent,
},
AnySyncTimelineEvent,
},
room_id, user_id,
room_id,
serde::Raw,
user_id,
};
use serde_json::{json, value::to_raw_value};
use stream_assert::assert_next_matches;
use super::TestTimeline;
@@ -455,3 +461,104 @@ async fn test_retry_message_decryption_highlighted() {
assert_eq!(message.body(), "A secret to everybody but Alice");
assert!(event.is_highlighted());
}
#[async_test]
async fn test_utd_cause_for_nonmember_event_is_found() {
// Given a timline
let timeline = TestTimeline::new();
let mut stream = timeline.subscribe().await;
// When we add an event with "membership: leave"
timeline.handle_live_event(raw_event_with_unsigned(json!({ "membership": "leave" }))).await;
// Then its UTD cause is membership
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let event = item.as_event().unwrap();
assert_let!(
TimelineItemContent::UnableToDecrypt(EncryptedMessage::MegolmV1AesSha2 { cause, .. }) =
event.content()
);
assert_eq!(*cause, UtdCause::Membership);
}
#[async_test]
async fn test_utd_cause_for_nonmember_event_is_found_unstable_prefix() {
// Given a timline
let timeline = TestTimeline::new();
let mut stream = timeline.subscribe().await;
// When we add an event with "io.element.msc4115.membership: leave"
timeline
.handle_live_event(raw_event_with_unsigned(
json!({ "io.element.msc4115.membership": "leave" }),
))
.await;
// Then its UTD cause is membership
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let event = item.as_event().unwrap();
assert_let!(
TimelineItemContent::UnableToDecrypt(EncryptedMessage::MegolmV1AesSha2 { cause, .. }) =
event.content()
);
assert_eq!(*cause, UtdCause::Membership);
}
#[async_test]
async fn test_utd_cause_for_member_event_is_unknown() {
// Given a timline
let timeline = TestTimeline::new();
let mut stream = timeline.subscribe().await;
// When we add an event with "membership: join"
timeline.handle_live_event(raw_event_with_unsigned(json!({ "membership": "join" }))).await;
// Then its UTD cause is membership
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let event = item.as_event().unwrap();
assert_let!(
TimelineItemContent::UnableToDecrypt(EncryptedMessage::MegolmV1AesSha2 { cause, .. }) =
event.content()
);
assert_eq!(*cause, UtdCause::Unknown);
}
#[async_test]
async fn test_utd_cause_for_missing_membership_is_unknown() {
// Given a timline
let timeline = TestTimeline::new();
let mut stream = timeline.subscribe().await;
// When we add an event with no membership in unsigned
timeline.handle_live_event(raw_event_with_unsigned(json!({}))).await;
// Then its UTD cause is membership
let item = assert_next_matches!(stream, VectorDiff::PushBack { value } => value);
let event = item.as_event().unwrap();
assert_let!(
TimelineItemContent::UnableToDecrypt(EncryptedMessage::MegolmV1AesSha2 { cause, .. }) =
event.content()
);
assert_eq!(*cause, UtdCause::Unknown);
}
fn raw_event_with_unsigned(unsigned: serde_json::Value) -> Raw<AnySyncTimelineEvent> {
Raw::from_json(
to_raw_value(&json!({
"event_id": "$myevent",
"sender": "@u:s",
"origin_server_ts": 3,
"type": "m.room.encrypted",
"content": {
"algorithm": "m.megolm.v1.aes-sha2",
"ciphertext": "NOT_REAL_CIPHERTEXT",
"sender_key": "SENDER_KEY",
"device_id": "DEVICE_ID",
"session_id": "SESSION_ID",
},
"unsigned": unsigned
}))
.unwrap(),
)
}

View File

@@ -24,6 +24,7 @@ use std::{
time::{Duration, Instant},
};
use matrix_sdk::crypto::types::events::UtdCause;
use ruma::{EventId, OwnedEventId};
use tokio::{spawn, task::JoinHandle, time::sleep};
@@ -50,6 +51,10 @@ pub struct UnableToDecryptInfo {
/// time it took to decrypt the event. If it is not set, this is
/// considered a definite UTD.
pub time_to_decrypt: Option<Duration>,
/// What we know about what caused this UTD. E.g. was this event sent when
/// we were not a member of this room?
pub cause: UtdCause,
}
type PendingUtdReports = Vec<(OwnedEventId, JoinHandle<()>)>;
@@ -111,7 +116,7 @@ impl UtdHookManager {
/// The function to call whenever a UTD is seen for the first time.
///
/// Pipe in any information that needs to be included in the final report.
pub(crate) fn on_utd(&self, event_id: &EventId) {
pub(crate) fn on_utd(&self, event_id: &EventId, cause: UtdCause) {
// Only let the parent hook know if the event wasn't already handled.
{
let mut known_utds = self.known_utds.lock().unwrap();
@@ -123,7 +128,8 @@ impl UtdHookManager {
known_utds.insert(event_id.to_owned(), Instant::now());
}
let info = UnableToDecryptInfo { event_id: event_id.to_owned(), time_to_decrypt: None };
let info =
UnableToDecryptInfo { event_id: event_id.to_owned(), time_to_decrypt: None, cause };
let Some(max_delay) = self.max_delay else {
// No delay: immediately report the event to the parent hook.
@@ -163,7 +169,7 @@ impl UtdHookManager {
///
/// Note: if this is called for an event that was never marked as a UTD
/// before, it has no effect.
pub(crate) fn on_late_decrypt(&self, event_id: &EventId) {
pub(crate) fn on_late_decrypt(&self, event_id: &EventId, cause: UtdCause) {
// Only let the parent hook know if the event was known to be a UTDs.
let Some(marked_utd_at) = self.known_utds.lock().unwrap().remove(event_id) else {
return;
@@ -172,6 +178,7 @@ impl UtdHookManager {
let info = UnableToDecryptInfo {
event_id: event_id.to_owned(),
time_to_decrypt: Some(marked_utd_at.elapsed()),
cause,
};
// Cancel and remove the task from the outstanding set immediately.
@@ -226,12 +233,12 @@ mod tests {
let wrapper = UtdHookManager::new(hook.clone());
// And I call the `on_utd` method multiple times, sometimes on the same event,
wrapper.on_utd(event_id!("$1"));
wrapper.on_utd(event_id!("$1"));
wrapper.on_utd(event_id!("$2"));
wrapper.on_utd(event_id!("$1"));
wrapper.on_utd(event_id!("$2"));
wrapper.on_utd(event_id!("$3"));
wrapper.on_utd(event_id!("$1"), UtdCause::Unknown);
wrapper.on_utd(event_id!("$1"), UtdCause::Unknown);
wrapper.on_utd(event_id!("$2"), UtdCause::Unknown);
wrapper.on_utd(event_id!("$1"), UtdCause::Unknown);
wrapper.on_utd(event_id!("$2"), UtdCause::Unknown);
wrapper.on_utd(event_id!("$3"), UtdCause::Unknown);
// Then the event ids have been deduplicated,
{
@@ -258,7 +265,7 @@ mod tests {
// And I call the `on_late_decrypt` method before the event had been marked as
// utd,
wrapper.on_late_decrypt(event_id!("$1"));
wrapper.on_late_decrypt(event_id!("$1"), UtdCause::Unknown);
// Then nothing is registered in the parent hook.
assert!(hook.utds.lock().unwrap().is_empty());
@@ -273,7 +280,7 @@ mod tests {
let wrapper = UtdHookManager::new(hook.clone());
// And I call the `on_utd` method for an event,
wrapper.on_utd(event_id!("$1"));
wrapper.on_utd(event_id!("$1"), UtdCause::Unknown);
// Then the UTD has been notified, but not as late-decrypted event.
{
@@ -284,7 +291,7 @@ mod tests {
}
// And when I call the `on_late_decrypt` method,
wrapper.on_late_decrypt(event_id!("$1"));
wrapper.on_late_decrypt(event_id!("$1"), UtdCause::Unknown);
// Then the event is now reported as a late-decryption too.
{
@@ -312,7 +319,7 @@ mod tests {
let wrapper = UtdHookManager::new(hook.clone()).with_max_delay(Duration::from_secs(2));
// And I call the `on_utd` method for an event,
wrapper.on_utd(event_id!("$1"));
wrapper.on_utd(event_id!("$1"), UtdCause::Unknown);
// Then the UTD is not being reported immediately.
assert!(hook.utds.lock().unwrap().is_empty());
@@ -348,7 +355,7 @@ mod tests {
let wrapper = UtdHookManager::new(hook.clone()).with_max_delay(Duration::from_secs(2));
// And I call the `on_utd` method for an event,
wrapper.on_utd(event_id!("$1"));
wrapper.on_utd(event_id!("$1"), UtdCause::Unknown);
// Then the UTD has not been notified quite yet.
assert!(hook.utds.lock().unwrap().is_empty());
@@ -357,7 +364,7 @@ mod tests {
// If I wait for 1 second, and mark the event as late-decrypted,
sleep(Duration::from_secs(1)).await;
wrapper.on_late_decrypt(event_id!("$1"));
wrapper.on_late_decrypt(event_id!("$1"), UtdCause::Unknown);
// Then it's being immediately reported as a late-decryption UTD.
{

View File

@@ -161,6 +161,7 @@ pub struct EncryptionSettings {
/// Settings for end-to-end encryption features.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
pub enum BackupDownloadStrategy {
/// Automatically download all room keys from the backup when the backup
/// recovery key has been received. The backup recovery key can be received