1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
1415use matrix_sdk_common::deserialized_responses::TimelineEvent;
16use matrix_sdk_crypto::{DecryptionSettings, RoomEventDecryptionResult};
17use ruma::{events::AnySyncTimelineEvent, serde::Raw, RoomId};
1819use super::{e2ee::E2EE, verification, Context};
20use crate::{
21 latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
22Result, Room,
23};
2425/// Decrypt any [`Room::latest_encrypted_events`] for a particular set of
26/// [`Room`]s.
27///
28/// If we can decrypt them, change [`Room::latest_event`] to reflect what we
29/// found, and remove any older encrypted events from
30/// [`Room::latest_encrypted_events`].
31pub async fn decrypt_from_rooms(
32 context: &mut Context,
33 rooms: Vec<Room>,
34 e2ee: E2EE<'_>,
35) -> Result<()> {
36// All functions used by this one expect an `OlmMachine`. Return if there is
37 // none.
38if e2ee.olm_machine.is_none() {
39return Ok(());
40 }
4142for room in rooms {
43// Try to find a message we can decrypt and is suitable for using as the latest
44 // event. If we found one, set it as the latest and delete any older
45 // encrypted events
46if let Some((found, found_index)) = find_suitable_and_decrypt(context, &room, &e2ee).await {
47 room.on_latest_event_decrypted(
48 found,
49 found_index,
50&mut context.state_changes,
51&mut context.room_info_notable_updates,
52 );
53 }
54 }
5556Ok(())
57}
5859async fn find_suitable_and_decrypt(
60 context: &mut Context,
61 room: &Room,
62 e2ee: &E2EE<'_>,
63) -> Option<(Box<LatestEvent>, usize)> {
64let enc_events = room.latest_encrypted_events();
65let power_levels = room.power_levels().await.ok();
66let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
6768// Walk backwards through the encrypted events, looking for one we can decrypt
69for (i, event) in enc_events.iter().enumerate().rev() {
70// Size of the `decrypt_sync_room_event` future should not impact this
71 // async fn since it is likely that there aren't even any encrypted
72 // events when calling it.
73let decrypt_sync_room_event =
74 Box::pin(decrypt_sync_room_event(context, event, e2ee, room.room_id()));
7576if let Ok(decrypted) = decrypt_sync_room_event.await {
77// We found an event we can decrypt
78if let Ok(any_sync_event) = decrypted.raw().deserialize() {
79// We can deserialize it to find its type
80match is_suitable_for_latest_event(&any_sync_event, power_levels_info) {
81 PossibleLatestEvent::YesRoomMessage(_)
82 | PossibleLatestEvent::YesPoll(_)
83 | PossibleLatestEvent::YesCallInvite(_)
84 | PossibleLatestEvent::YesCallNotify(_)
85 | PossibleLatestEvent::YesSticker(_)
86 | PossibleLatestEvent::YesKnockedStateEvent(_) => {
87return Some((Box::new(LatestEvent::new(decrypted)), i));
88 }
89_ => (),
90 }
91 }
92 }
93 }
9495None
96}
9798/// Attempt to decrypt the given raw event into a [`TimelineEvent`].
99///
100/// In the case of a decryption error, returns a [`TimelineEvent`]
101/// representing the decryption error; in the case of problems with our
102/// application, returns `Err`.
103///
104/// # Panics
105///
106/// Panics if there is no [`OlmMachine`] in [`E2EE`].
107async fn decrypt_sync_room_event(
108 context: &mut Context,
109 event: &Raw<AnySyncTimelineEvent>,
110 e2ee: &E2EE<'_>,
111 room_id: &RoomId,
112) -> Result<TimelineEvent> {
113let decryption_settings =
114 DecryptionSettings { sender_device_trust_requirement: e2ee.decryption_trust_requirement };
115116let event = match e2ee
117 .olm_machine
118 .expect("An `OlmMachine` is expected")
119 .try_decrypt_room_event(event.cast_ref(), room_id, &decryption_settings)
120 .await?
121{
122 RoomEventDecryptionResult::Decrypted(decrypted) => {
123let event: TimelineEvent = decrypted.into();
124125if let Ok(sync_timeline_event) = event.raw().deserialize() {
126 verification::process_if_relevant(
127 context,
128&sync_timeline_event,
129 e2ee.clone(),
130 room_id,
131 )
132 .await?;
133 }
134135 event
136 }
137138 RoomEventDecryptionResult::UnableToDecrypt(utd_info) => {
139 TimelineEvent::new_utd_event(event.clone(), utd_info)
140 }
141 };
142143Ok(event)
144}
145146#[cfg(test)]
147mod tests {
148use matrix_sdk_test::{
149 async_test, event_factory::EventFactory, JoinedRoomBuilder, SyncResponseBuilder,
150 };
151use ruma::{event_id, events::room::member::MembershipState, room_id, user_id};
152153use super::{decrypt_from_rooms, Context, E2EE};
154use crate::{rooms::normal::RoomInfoNotableUpdateReasons, test_utils::logged_in_base_client};
155156#[async_test]
157async fn test_when_there_are_no_latest_encrypted_events_decrypting_them_does_nothing() {
158// Given a room
159let user_id = user_id!("@u:u.to");
160let room_id = room_id!("!r:u.to");
161162let client = logged_in_base_client(Some(user_id)).await;
163164let mut sync_builder = SyncResponseBuilder::new();
165166let response = sync_builder
167 .add_joined_room(
168 JoinedRoomBuilder::new(room_id).add_timeline_event(
169 EventFactory::new()
170 .member(user_id)
171 .display_name("Alice")
172 .membership(MembershipState::Join)
173 .event_id(event_id!("$1")),
174 ),
175 )
176 .build_sync_response();
177 client.receive_sync_response(response).await.unwrap();
178179let room = client.get_room(room_id).expect("Just-created room not found!");
180181// Sanity: it has no latest_encrypted_events or latest_event
182assert!(room.latest_encrypted_events().is_empty());
183assert!(room.latest_event().is_none());
184185// When I tell it to do some decryption
186let mut context = Context::default();
187188 decrypt_from_rooms(
189&mut context,
190vec![room.clone()],
191 E2EE::new(
192 client.olm_machine().await.as_ref(),
193 client.decryption_trust_requirement,
194 client.handle_verification_events,
195 ),
196 )
197 .await
198.unwrap();
199200// Then nothing changed
201assert!(room.latest_encrypted_events().is_empty());
202assert!(room.latest_event().is_none());
203assert!(context.state_changes.room_infos.is_empty());
204assert!(!context
205 .room_info_notable_updates
206 .get(room_id)
207 .copied()
208 .unwrap_or_default()
209 .contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
210 }
211}