diff --git a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs index d9828624f87..642c08c0362 100644 --- a/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/event_cache/store/integration_tests.rs @@ -51,6 +51,7 @@ pub fn make_test_event(room_id: &RoomId, content: &str) -> TimelineEvent { sender_claimed_keys: Default::default(), }, verification_state: VerificationState::Verified, + session_id: "mysessionid9".to_owned(), }; let event = EventFactory::new() diff --git a/crates/matrix-sdk-common/src/deserialized_responses.rs b/crates/matrix-sdk-common/src/deserialized_responses.rs index 0a25b539374..7d60fbcb40b 100644 --- a/crates/matrix-sdk-common/src/deserialized_responses.rs +++ b/crates/matrix-sdk-common/src/deserialized_responses.rs @@ -302,6 +302,9 @@ pub struct EncryptionInfo { /// Callers that persist this should mark the state as dirty when a device /// change is received down the sync. pub verification_state: VerificationState, + /// The Megolm session ID that was used to encrypt this event. + #[serde(default)] + pub session_id: String, } /// Represents a matrix room event that has been returned from `/sync`, @@ -540,6 +543,18 @@ impl TimelineEventKind { TimelineEventKind::PlainText { event } => event, } } + + /// The Megolm session ID that was used to send this event, if it was + /// encrypted. + pub fn session_id(&self) -> Option { + match self { + TimelineEventKind::Decrypted(decrypted_room_event) => { + Some(decrypted_room_event.encryption_info.session_id.clone()) + } + TimelineEventKind::UnableToDecrypt { utd_info, .. } => utd_info.session_id.clone(), + TimelineEventKind::PlainText { .. } => None, + } + } } #[cfg(not(tarpaulin_include))] @@ -1042,6 +1057,7 @@ mod tests { sender_claimed_keys: Default::default(), }, verification_state: VerificationState::Verified, + session_id: "xyz".to_owned(), }, unsigned_encryption_info: Some(BTreeMap::from([( UnsignedEventLocation::RelationsReplace, @@ -1080,6 +1096,7 @@ mod tests { } }, "verification_state": "Verified", + "session_id": "xyz", }, "unsigned_encryption_info": { "RelationsReplace": {"UnableToDecrypt": { @@ -1128,6 +1145,7 @@ mod tests { event.encryption_info().unwrap().algorithm_info, AlgorithmInfo::MegolmV1AesSha2 { .. } ); + assert_eq!(event.encryption_info().unwrap().session_id, ""); // Test that the previous format, with an undecryptable unsigned event, can also // be deserialized. @@ -1354,6 +1372,7 @@ mod tests { sender_claimed_keys: Default::default(), }, verification_state: VerificationState::Verified, + session_id: "mysessionid76".to_owned(), }; with_settings!({sort_maps =>true}, { @@ -1383,6 +1402,7 @@ mod tests { ]), }, verification_state: VerificationState::Verified, + session_id: "mysessionid112".to_owned(), }, unsigned_encryption_info: Some(BTreeMap::from([( UnsignedEventLocation::RelationsThreadLatestEvent, diff --git a/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_encryption_info.snap b/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_encryption_info.snap index 96e5c2b2765..07e7395f3ce 100644 --- a/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_encryption_info.snap +++ b/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_encryption_info.snap @@ -11,5 +11,6 @@ expression: info "sender_claimed_keys": {} } }, - "verification_state": "Verified" + "verification_state": "Verified", + "session_id": "mysessionid76" } diff --git a/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_sync_timeline_event.snap b/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_sync_timeline_event.snap index f40882141c1..1acd21cd203 100644 --- a/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_sync_timeline_event.snap +++ b/crates/matrix-sdk-common/src/snapshots/matrix_sdk_common__deserialized_responses__tests__snapshot_test_sync_timeline_event.snap @@ -17,6 +17,7 @@ expression: "serde_json::to_value(&room_event).unwrap()" }, "sender": "@sender:example.com", "sender_device": "ABCDEFGHIJ", + "session_id": "mysessionid112", "verification_state": "Verified" }, "event": { diff --git a/crates/matrix-sdk-crypto/src/machine/mod.rs b/crates/matrix-sdk-crypto/src/machine/mod.rs index a497057d280..14a23632836 100644 --- a/crates/matrix-sdk-crypto/src/machine/mod.rs +++ b/crates/matrix-sdk-crypto/src/machine/mod.rs @@ -1678,6 +1678,7 @@ impl OlmMachine { .collect(), }, verification_state, + session_id: session.session_id().to_owned(), }) } diff --git a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs index 32c70f29047..13eea553e0e 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/mod.rs @@ -19,6 +19,7 @@ use eyeball_im::VectorDiff; use eyeball_im_util::vector::VectorObserverExt; use futures_core::Stream; use imbl::Vector; +use itertools::{Either, Itertools}; #[cfg(test)] use matrix_sdk::{crypto::OlmMachine, SendOutsideWasm}; use matrix_sdk::{ @@ -80,7 +81,7 @@ use crate::{ date_dividers::DateDividerAdjuster, event_item::EventTimelineItemKind, pinned_events_loader::{PinnedEventsLoader, PinnedEventsLoaderError}, - TimelineEventFilterFn, + EncryptedMessage, TimelineEventFilterFn, }, unable_to_decrypt_hook::UtdHookManager, }; @@ -1062,10 +1063,10 @@ impl TimelineController

{ decryptor: impl Decryptor, session_ids: Option>, ) { - use super::EncryptedMessage; - let mut state = self.state.clone().write_owned().await; + // We should retry an event if its session is included in the list, or the list + // is None. let should_retry = move |session_id: &str| { if let Some(session_ids) = &session_ids { session_ids.contains(session_id) @@ -1074,28 +1075,53 @@ impl TimelineController

{ } }; - let retry_indices: Vec<_> = state - .items - .iter() - .enumerate() - .filter_map(|(idx, item)| match item.as_event()?.content().as_unable_to_decrypt()? { - EncryptedMessage::MegolmV1AesSha2 { session_id, .. } - if should_retry(session_id) => - { - Some(idx) - } - EncryptedMessage::MegolmV1AesSha2 { .. } - | EncryptedMessage::OlmV1Curve25519AesSha2 { .. } - | EncryptedMessage::Unknown => None, - }) - .collect(); + // Find which events need retrying + let (retry_decryption_indices, retry_info_indices) = + event_indices_to_retry_decryption(&state, &should_retry); + + // Retry fetching encryption info for events that are already decrypted + let room_data_provider = self.room_data_provider.clone(); + matrix_sdk::executor::spawn(async move { + state.retry_event_encryption_info(retry_info_indices, &room_data_provider).await; + }); + // Retry decrypting UTDs + self.retry_event_decryption_by_index( + &self.state, + retry_decryption_indices, + should_retry, + decryptor, + ) + .await; + } + + /// Retry decryption of the supplied events, which are expected to be UTDs. + /// + /// # Arguments + /// + /// * `state` is the [`TimelineState`] state of the timeline. + /// + /// * `retry_indices` contains the indices of events to try within the + /// `state.items`. + /// + /// * `should_retry` checks that a session is included in the list of + /// updated sessions. + /// + /// * `decryptor` does the actual work of decrypting events. + async fn retry_event_decryption_by_index( + &self, + state: &Arc>, + retry_indices: Vec, + should_retry: impl Fn(&str) -> bool + Send + Sync + 'static, + decryptor: impl Decryptor, + ) { if retry_indices.is_empty() { return; } debug!("Retrying decryption"); + let mut state = state.clone().write_owned().await; let settings = self.settings.clone(); let room_data_provider = self.room_data_provider.clone(); let push_rules_context = room_data_provider.push_rules_and_context().await; @@ -1433,6 +1459,42 @@ impl TimelineController

{ } } +/// Decide which events should be retried, either for re-decryption, or, if they +/// are already decrypted, for re-checking their encryption info. +/// +/// Returns a tuple `(retry_decryption_indices, retry_info_indices)` where +/// `retry_decryption_indices` is a list of UTDs to try decrypting, and +/// retry_info_indices is a list of already-decrypted events whose encryption +/// info we can re-fetch. +fn event_indices_to_retry_decryption( + state: &tokio::sync::OwnedRwLockWriteGuard, + should_retry: impl Fn(&str) -> bool, +) -> (Vec, Vec) { + state + .items + .iter() + .enumerate() + .filter_map(|(idx, item)| { + if let Some(event) = item.as_event() { + if let TimelineItemContent::UnableToDecrypt(encrypted) = event.content() { + if let Some(session_id) = encrypted.session_id() { + if should_retry(session_id) { + return Some(Either::Left(idx)); + } + } + } else if let Some(remote_event) = event.as_remote() { + if let Some(encryption_info) = &remote_event.encryption_info { + if should_retry(&encryption_info.session_id) { + return Some(Either::Right(idx)); + } + } + } + } + None + }) + .partition_map(|either| either) +} + impl TimelineController { pub(super) fn room(&self) -> &Room { &self.room_data_provider diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state.rs b/crates/matrix-sdk-ui/src/timeline/controller/state.rs index 60eeeff7ee4..1c4ba26cb85 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state.rs @@ -46,7 +46,10 @@ use super::{ DateDividerMode, TimelineFocusKind, TimelineMetadata, TimelineSettings, TimelineStateTransaction, }; -use crate::unable_to_decrypt_hook::UtdHookManager; +use crate::{ + timeline::{event_item::EventTimelineItemKind, TimelineItemKind}, + unable_to_decrypt_hook::UtdHookManager, +}; #[derive(Debug)] pub(in crate::timeline) struct TimelineState { @@ -225,6 +228,46 @@ impl TimelineState { txn.commit(); } + /// Try to fetch [`EncryptionInfo`] for the events with the supplied + /// indices, and update them where we succeed. + pub(super) async fn retry_event_encryption_info( + &mut self, + retry_indices: Vec, + room_data_provider: &P, + ) { + let mut new_info = Vec::new(); + for idx in retry_indices { + let item = self.items.get(idx); + if let Some(item) = item { + if let Some(event) = item.as_event() { + let sender = event.sender.clone(); + if let Some(remote) = event.as_remote() { + if let Some(encryption_info) = &remote.encryption_info { + let mut new_remote = remote.clone(); + + new_remote.encryption_info = room_data_provider + .get_encryption_info(&encryption_info.session_id, &sender) + .await; + + let new_event = + event.with_kind(EventTimelineItemKind::Remote(new_remote)); + + let new_item = item.with_kind(TimelineItemKind::Event(new_event)); + + new_info.push((idx, new_item)); + } + } + } + } + } + + let mut txn = self.transaction(); + for (idx, item) in new_info { + txn.replace(idx, item); + } + txn.commit(); + } + #[cfg(test)] pub(super) fn handle_read_receipts( &mut self, diff --git a/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs b/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs index feb95b281c5..2104ed125ba 100644 --- a/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs +++ b/crates/matrix-sdk-ui/src/timeline/controller/state_transaction.rs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + use eyeball_im::VectorDiff; use itertools::Itertools as _; use matrix_sdk::deserialized_responses::TimelineEvent; @@ -32,7 +34,7 @@ use super::{ ObservableItems, ObservableItemsTransaction, TimelineFocusKind, TimelineMetadata, TimelineSettings, }; -use crate::events::SyncTimelineEventWithoutContent; +use crate::{events::SyncTimelineEventWithoutContent, timeline::TimelineItem}; pub(in crate::timeline) struct TimelineStateTransaction<'a> { /// A vector transaction over the items themselves. Holds temporary state @@ -441,6 +443,15 @@ impl<'a> TimelineStateTransaction<'a> { } } + /// Replace the timeline item at the supplied index with the supplied item. + pub(super) fn replace( + &mut self, + timeline_item_index: usize, + timeline_item: Arc, + ) -> Arc { + self.items.replace(timeline_item_index, timeline_item) + } + pub(super) fn clear(&mut self) { let has_local_echoes = self.items.iter().any(|item| item.is_local_echo()); diff --git a/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs b/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs index 40d39d080be..700981a7c19 100644 --- a/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/event_item/content/mod.rs @@ -551,6 +551,16 @@ impl EncryptedMessage { _ => Self::Unknown, } } + + /// Return the Megolm session ID used to encrypt this message, if it is + /// supplied in the encryption scheme. + pub(crate) fn session_id(&self) -> Option<&str> { + match self { + EncryptedMessage::OlmV1Curve25519AesSha2 { .. } => None, + EncryptedMessage::MegolmV1AesSha2 { session_id, .. } => Some(session_id), + EncryptedMessage::Unknown => None, + } + } } /// An `m.sticker` event. diff --git a/crates/matrix-sdk-ui/src/timeline/tests/edit.rs b/crates/matrix-sdk-ui/src/timeline/tests/edit.rs index 6b17911f1a1..c23c6addbb5 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/edit.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/edit.rs @@ -176,6 +176,7 @@ async fn test_edit_updates_encryption_info() { sender_claimed_keys: BTreeMap::new(), }, verification_state: VerificationState::Verified, + session_id: "mysessionid6333".to_owned(), }; let original_event: TimelineEvent = DecryptedRoomEvent { diff --git a/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs b/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs index 6dd1345967c..f6b6030605a 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/encryption.rs @@ -28,6 +28,9 @@ use eyeball_im::VectorDiff; use matrix_sdk::{ assert_next_matches_with_timeout, crypto::{decrypt_room_key_export, types::events::UtdCause, OlmMachine}, + deserialized_responses::{ + AlgorithmInfo, DecryptedRoomEvent, EncryptionInfo, VerificationLevel, VerificationState, + }, test_utils::test_client_builder, }; use matrix_sdk_base::deserialized_responses::{TimelineEvent, UnableToDecryptReason}; @@ -38,18 +41,19 @@ use ruma::{ EncryptedEventScheme, MegolmV1AesSha2ContentInit, Relation, Replacement, RoomEncryptedEventContent, }, - room_id, + owned_device_id, room_id, serde::Raw, user_id, }; use serde_json::{json, value::to_raw_value}; -use stream_assert::assert_next_matches; +use stream_assert::{assert_next_matches, assert_pending}; use tokio::time::sleep; use super::TestTimeline; use crate::{ timeline::{ - tests::TestTimelineBuilder, EncryptedMessage, TimelineDetails, TimelineItemContent, + tests::{TestRoomDataProvider, TestTimelineBuilder}, + EncryptedMessage, TimelineDetails, TimelineItemContent, }, unable_to_decrypt_hook::{UnableToDecryptHook, UnableToDecryptInfo, UtdHookManager}, }; @@ -550,6 +554,94 @@ async fn test_retry_message_decryption_highlighted() { assert!(event.is_highlighted()); } +#[async_test] +async fn test_retry_fetching_encryption_info() { + const SESSION_ID: &str = "C25PoE+4MlNidQD0YU5ibZqHawV0zZ/up7R8vYJBYTY"; + let sender = user_id!("@sender:s.co"); + let room_id = room_id!("!room:s.co"); + + // Given when I ask the room for new encryption info for any session, it will + // say "verified" + let verified_encryption_info = make_encryption_info(SESSION_ID, VerificationState::Verified); + let provider = TestRoomDataProvider::default().with_encryption_info(verified_encryption_info); + let timeline = TestTimelineBuilder::new().provider(provider).build(); + let f = &timeline.factory; + let mut stream = timeline.subscribe().await; + + // But right now the timeline contains 2 events whose info says "unverified" + // One is linked to SESSION_ID, the other is linked to some other session. + let timeline_event_this_session: TimelineEvent = DecryptedRoomEvent { + event: f.text_msg("foo").sender(sender).room(room_id).into_raw(), + encryption_info: make_encryption_info( + SESSION_ID, + VerificationState::Unverified(VerificationLevel::UnsignedDevice), + ), + unsigned_encryption_info: None, + } + .into(); + let timeline_event_other_session: TimelineEvent = DecryptedRoomEvent { + event: f.text_msg("foo").sender(sender).room(room_id).into_raw(), + encryption_info: make_encryption_info( + "other_session_id", + VerificationState::Unverified(VerificationLevel::UnsignedDevice), + ), + unsigned_encryption_info: None, + } + .into(); + timeline.handle_live_event(timeline_event_this_session).await; + timeline.handle_live_event(timeline_event_other_session).await; + + // Sanity: the events come through as unverified + assert_eq!(timeline.controller.items().await.len(), 3); + let item_1 = assert_next_matches!(stream, VectorDiff::PushBack { value } => value); + let fetched_encryption_info_1 = + item_1.as_event().unwrap().as_remote().unwrap().encryption_info.as_ref().unwrap(); + assert_matches!(fetched_encryption_info_1.verification_state, VerificationState::Unverified(_)); + // (Plus a date divider is emitted - not sure why it's in the middle...) + let date_divider = assert_next_matches!(stream, VectorDiff::PushFront { value } => value); + assert!(date_divider.is_date_divider()); + let item_2 = assert_next_matches!(stream, VectorDiff::PushBack { value } => value); + let fetched_encryption_info_2 = + item_2.as_event().unwrap().as_remote().unwrap().encryption_info.as_ref().unwrap(); + assert_matches!(fetched_encryption_info_2.verification_state, VerificationState::Unverified(_)); + + // When we retry the session with ID SESSION_ID + let own_user_id = user_id!("@me:s.co"); + let olm_machine = OlmMachine::new(own_user_id, "SomeDeviceId".into()).await; + timeline + .controller + .retry_event_decryption_test( + room_id, + olm_machine, + Some(iter::once(SESSION_ID.to_owned()).collect()), + ) + .await; + + // Then the event in that session has been updated to be verified + assert_eq!(timeline.controller.items().await.len(), 3); + let item = assert_next_matches!(stream, VectorDiff::Set { index: 1, value } => value); + let event = item.as_event().unwrap(); + let fetched_encryption_info = event.as_remote().unwrap().encryption_info.as_ref().unwrap(); + assert_matches!(fetched_encryption_info.verification_state, VerificationState::Verified); + + // But the other one is unchanged because it was for a different session - no + // other updates are waiting + assert_pending!(stream); +} + +fn make_encryption_info(session_id: &str, verification_state: VerificationState) -> EncryptionInfo { + EncryptionInfo { + sender: BOB.to_owned(), + sender_device: Some(owned_device_id!("BOBDEVICE")), + algorithm_info: AlgorithmInfo::MegolmV1AesSha2 { + curve25519_key: Default::default(), + sender_claimed_keys: Default::default(), + }, + verification_state, + session_id: session_id.to_owned(), + } +} + #[async_test] async fn test_utd_cause_for_nonmember_event_is_found() { // Given a timline diff --git a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs index bd918ba63d1..010277b4454 100644 --- a/crates/matrix-sdk-ui/src/timeline/tests/mod.rs +++ b/crates/matrix-sdk-ui/src/timeline/tests/mod.rs @@ -28,7 +28,7 @@ use imbl::vector; use indexmap::IndexMap; use matrix_sdk::{ config::RequestConfig, - deserialized_responses::TimelineEvent, + deserialized_responses::{EncryptionInfo, TimelineEvent}, event_cache::paginator::{PaginableRoom, PaginatorError}, room::{EventWithContextResponse, Messages, MessagesOptions}, send_queue::RoomSendQueueUpdate, @@ -267,6 +267,9 @@ struct TestRoomDataProvider { /// Events redacted with that room data providier. pub redacted: Arc>>, + + /// Returned from get_encryption_info method + pub encryption_info: Option, } impl TestRoomDataProvider { @@ -278,6 +281,14 @@ impl TestRoomDataProvider { self.fully_read_marker = Some(event_id); self } + + pub(crate) fn with_encryption_info( + mut self, + encryption_info: EncryptionInfo, + ) -> TestRoomDataProvider { + self.encryption_info = Some(encryption_info); + self + } } impl PaginableRoom for TestRoomDataProvider { @@ -414,4 +425,12 @@ impl RoomDataProvider for TestRoomDataProvider { let info = RoomInfo::new(*DEFAULT_TEST_ROOM_ID, RoomState::Joined); SharedObservable::new(info).subscribe() } + + async fn get_encryption_info( + &self, + _session_id: &str, + _sender: &UserId, + ) -> Option { + self.encryption_info.clone() + } } diff --git a/crates/matrix-sdk-ui/src/timeline/traits.rs b/crates/matrix-sdk-ui/src/timeline/traits.rs index 55cae54db5f..725d0d7108f 100644 --- a/crates/matrix-sdk-ui/src/timeline/traits.rs +++ b/crates/matrix-sdk-ui/src/timeline/traits.rs @@ -19,8 +19,10 @@ use indexmap::IndexMap; #[cfg(test)] use matrix_sdk::crypto::{DecryptionSettings, RoomEventDecryptionResult, TrustRequirement}; use matrix_sdk::{ - crypto::types::events::CryptoContextInfo, deserialized_responses::TimelineEvent, - event_cache::paginator::PaginableRoom, AsyncTraitDeps, Result, Room, SendOutsideWasm, + crypto::types::events::CryptoContextInfo, + deserialized_responses::{EncryptionInfo, TimelineEvent}, + event_cache::paginator::PaginableRoom, + AsyncTraitDeps, Result, Room, SendOutsideWasm, }; use matrix_sdk_base::{latest_event::LatestEvent, RoomInfo}; use ruma::{ @@ -121,6 +123,14 @@ pub(super) trait RoomDataProvider: ) -> impl Future> + SendOutsideWasm + 'a; fn room_info(&self) -> Subscriber; + + /// Return the encryption info for the Megolm session with the supplied + /// session ID. + fn get_encryption_info( + &self, + session_id: &str, + sender: &UserId, + ) -> impl Future> + SendOutsideWasm; } impl RoomDataProvider for Room { @@ -273,6 +283,14 @@ impl RoomDataProvider for Room { fn room_info(&self) -> Subscriber { self.subscribe_info() } + + async fn get_encryption_info( + &self, + session_id: &str, + sender: &UserId, + ) -> Option { + self.get_encryption_info(session_id, sender).await + } } // Internal helper to make most of retry_event_decryption independent of a room diff --git a/crates/matrix-sdk/src/room/mod.rs b/crates/matrix-sdk/src/room/mod.rs index 43488c0cc6d..72a89043380 100644 --- a/crates/matrix-sdk/src/room/mod.rs +++ b/crates/matrix-sdk/src/room/mod.rs @@ -32,10 +32,13 @@ use futures_util::{ use http::StatusCode; #[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))] pub use identity_status_changes::IdentityStatusChanges; -#[cfg(feature = "e2e-encryption")] -use matrix_sdk_base::crypto::{DecryptionSettings, RoomEventDecryptionResult}; #[cfg(all(feature = "e2e-encryption", not(target_arch = "wasm32")))] use matrix_sdk_base::crypto::{IdentityStatusChange, RoomIdentityProvider, UserIdentity}; +#[cfg(feature = "e2e-encryption")] +use matrix_sdk_base::{ + crypto::{DecryptionSettings, RoomEventDecryptionResult}, + deserialized_responses::EncryptionInfo, +}; use matrix_sdk_base::{ deserialized_responses::{ RawAnySyncOrStrippedState, RawSyncOrStrippedState, SyncOrStrippedState, @@ -1308,6 +1311,23 @@ impl Room { Ok(event) } + /// Fetches the [`EncryptionInfo`] for the supplied session_id. + /// + /// This may be used when we receive an update for a session, and we want to + /// reflect the changes in messages we have received that were encrypted + /// with that session, e.g. to remove a warning shield because a device is + /// now verified. + #[cfg(feature = "e2e-encryption")] + pub async fn get_encryption_info( + &self, + session_id: &str, + sender: &UserId, + ) -> Option { + let machine = self.client.olm_machine().await; + let machine = machine.as_ref()?; + machine.get_session_encryption_info(self.room_id(), session_id, sender).await.ok() + } + /// Forces the currently active room key, which is used to encrypt messages, /// to be rotated. ///