Skip to content

Commit

Permalink
Merge pull request #3337 from matrix-org/andybalaam/utd-type-info
Browse files Browse the repository at this point in the history
crypto: UtdCause enum in reporting hooks and encryption event
  • Loading branch information
andybalaam committed Apr 25, 2024
2 parents 2c7afc2 + 89abb75 commit 4156170
Show file tree
Hide file tree
Showing 12 changed files with 342 additions and 39 deletions.
7 changes: 6 additions & 1 deletion bindings/matrix-sdk-ffi/src/sync_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -187,13 +187,18 @@ 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 {
fn from(value: SdkUnableToDecryptInfo) -> Self {
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,
}
}
}
10 changes: 7 additions & 3 deletions bindings/matrix-sdk-ffi/src/timeline/content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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,
}
Expand All @@ -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,
}
Expand Down
2 changes: 1 addition & 1 deletion crates/matrix-sdk-base/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
2 changes: 2 additions & 0 deletions crates/matrix-sdk-crypto/src/types/events/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
153 changes: 153 additions & 0 deletions crates/matrix-sdk-crypto/src/types/events/utd_cause.rs
Original file line number Diff line number Diff line change
@@ -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())
}
}
2 changes: 1 addition & 1 deletion crates/matrix-sdk-ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
11 changes: 8 additions & 3 deletions crates/matrix-sdk-ui/src/timeline/event_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -269,11 +269,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();

Expand Down Expand Up @@ -315,13 +319,14 @@ impl<'a, 'o> TimelineEventHandler<'a, 'o> {
}
AnyMessageLikeEventContent::RoomEncrypted(c) => {
// TODO: Handle replacements if the replaced event is also UTD
self.add(true, 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);
}
}
}
Expand Down
18 changes: 12 additions & 6 deletions crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
}
Expand Down
6 changes: 5 additions & 1 deletion crates/matrix-sdk-ui/src/timeline/inner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,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;
Expand Down Expand Up @@ -805,9 +807,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)
Expand Down
16 changes: 13 additions & 3 deletions crates/matrix-sdk-ui/src/timeline/inner/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,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);

Expand Down Expand Up @@ -551,14 +557,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) {
Expand Down
Loading

0 comments on commit 4156170

Please sign in to comment.