mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-08 16:04:13 -04:00
Merge branch 'main' into bnjbvr/permalink-mvp
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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?)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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 {
|
||||
|
||||
153
crates/matrix-sdk-crypto/src/types/events/utd_cause.rs
Normal file
153
crates/matrix-sdk-crypto/src/types/events/utd_cause.rs
Normal 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())
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user