use matrix_sdk_common::deserialized_responses::TimelineEvent;
use matrix_sdk_crypto::RoomEventDecryptionResult;
use ruma::{RoomId, events::AnySyncTimelineEvent, serde::Raw};
use super::{Context, e2ee::E2EE, verification};
use crate::{
Result, Room,
latest_event::{LatestEvent, PossibleLatestEvent, is_suitable_for_latest_event},
};
pub async fn decrypt_from_rooms(
context: &mut Context,
rooms: Vec<Room>,
e2ee: E2EE<'_>,
) -> Result<()> {
if e2ee.olm_machine.is_none() {
return Ok(());
}
for room in rooms {
if let Some((found, found_index)) = find_suitable_and_decrypt(&room, &e2ee).await {
room.on_latest_event_decrypted(
found,
found_index,
&mut context.state_changes,
&mut context.room_info_notable_updates,
);
}
}
Ok(())
}
async fn find_suitable_and_decrypt(
room: &Room,
e2ee: &E2EE<'_>,
) -> Option<(Box<LatestEvent>, usize)> {
let enc_events = room.latest_encrypted_events();
let power_levels = room.power_levels().await.ok();
let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
for (i, event) in enc_events.iter().enumerate().rev() {
let decrypt_sync_room_event =
Box::pin(decrypt_sync_room_event(event, e2ee, room.room_id()));
if let Ok(decrypted) = decrypt_sync_room_event.await {
if let Ok(any_sync_event) = decrypted.raw().deserialize() {
match is_suitable_for_latest_event(&any_sync_event, power_levels_info) {
PossibleLatestEvent::YesRoomMessage(_)
| PossibleLatestEvent::YesPoll(_)
| PossibleLatestEvent::YesCallInvite(_)
| PossibleLatestEvent::YesRtcNotification(_)
| PossibleLatestEvent::YesSticker(_)
| PossibleLatestEvent::YesKnockedStateEvent(_) => {
return Some((Box::new(LatestEvent::new(decrypted)), i));
}
_ => (),
}
}
}
}
None
}
async fn decrypt_sync_room_event(
event: &Raw<AnySyncTimelineEvent>,
e2ee: &E2EE<'_>,
room_id: &RoomId,
) -> Result<TimelineEvent> {
let event = match e2ee
.olm_machine
.expect("An `OlmMachine` is expected")
.try_decrypt_room_event(event.cast_ref_unchecked(), room_id, e2ee.decryption_settings)
.await?
{
RoomEventDecryptionResult::Decrypted(decrypted) => {
let event = TimelineEvent::from_decrypted(decrypted, None);
if let Ok(sync_timeline_event) = event.raw().deserialize() {
verification::process_if_relevant(&sync_timeline_event, e2ee.clone(), room_id)
.await?;
}
event
}
RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
TimelineEvent::from_utd(event.clone(), utd_info)
}
};
Ok(event)
}
#[cfg(test)]
mod tests {
use matrix_sdk_test::{
JoinedRoomBuilder, SyncResponseBuilder, async_test, event_factory::EventFactory,
};
use ruma::{event_id, events::room::member::MembershipState, room_id, user_id};
use super::{Context, E2EE, decrypt_from_rooms};
use crate::{room::RoomInfoNotableUpdateReasons, test_utils::logged_in_base_client};
#[async_test]
async fn test_when_there_are_no_latest_encrypted_events_decrypting_them_does_nothing() {
let user_id = user_id!("@u:u.to");
let room_id = room_id!("!r:u.to");
let client = logged_in_base_client(Some(user_id)).await;
let mut sync_builder = SyncResponseBuilder::new();
let response = sync_builder
.add_joined_room(
JoinedRoomBuilder::new(room_id).add_timeline_event(
EventFactory::new()
.member(user_id)
.display_name("Alice")
.membership(MembershipState::Join)
.event_id(event_id!("$1")),
),
)
.build_sync_response();
client.receive_sync_response(response).await.unwrap();
let room = client.get_room(room_id).expect("Just-created room not found!");
assert!(room.latest_encrypted_events().is_empty());
assert!(room.latest_event().is_none());
let mut context = Context::default();
decrypt_from_rooms(
&mut context,
vec![room.clone()],
E2EE::new(
client.olm_machine().await.as_ref(),
&client.decryption_settings,
client.handle_verification_events,
),
)
.await
.unwrap();
assert!(room.latest_encrypted_events().is_empty());
assert!(room.latest_event().is_none());
assert!(context.state_changes.room_infos.is_empty());
assert!(
!context
.room_info_notable_updates
.get(room_id)
.copied()
.unwrap_or_default()
.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT)
);
}
}