1#[cfg(feature = "e2e-encryption")]
16use std::{collections::BTreeMap, num::NonZeroUsize};
17
18#[cfg(feature = "e2e-encryption")]
19use ruma::{OwnedRoomId, events::AnySyncTimelineEvent, serde::Raw};
20
21use super::Room;
22#[cfg(feature = "e2e-encryption")]
23use super::RoomInfoNotableUpdateReasons;
24use crate::latest_event::{LatestEvent, LatestEventValue};
25
26impl Room {
27 #[cfg(feature = "e2e-encryption")]
29 pub(super) const MAX_ENCRYPTED_EVENTS: NonZeroUsize = NonZeroUsize::new(10).unwrap();
30
31 pub fn latest_event(&self) -> Option<LatestEvent> {
34 self.inner.read().latest_event.as_deref().cloned()
35 }
36
37 pub fn new_latest_event(&self) -> LatestEventValue {
39 self.inner.read().new_latest_event.clone()
40 }
41
42 #[cfg(feature = "e2e-encryption")]
47 pub(crate) fn latest_encrypted_events(&self) -> Vec<Raw<AnySyncTimelineEvent>> {
48 self.latest_encrypted_events.read().unwrap().iter().cloned().collect()
49 }
50
51 #[cfg(feature = "e2e-encryption")]
62 pub(crate) fn on_latest_event_decrypted(
63 &self,
64 latest_event: Box<LatestEvent>,
65 index: usize,
66 changes: &mut crate::StateChanges,
67 room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
68 ) {
69 self.latest_encrypted_events.write().unwrap().drain(0..=index);
70
71 let room_info = changes
72 .room_infos
73 .entry(self.room_id().to_owned())
74 .or_insert_with(|| self.clone_info());
75
76 room_info.latest_event = Some(latest_event);
77
78 room_info_notable_updates
79 .entry(self.room_id().to_owned())
80 .or_default()
81 .insert(RoomInfoNotableUpdateReasons::LATEST_EVENT);
82 }
83}
84
85#[cfg(all(test, feature = "e2e-encryption"))]
86mod tests_with_e2e_encryption {
87 use std::sync::Arc;
88
89 use assert_matches::assert_matches;
90 use matrix_sdk_common::deserialized_responses::TimelineEvent;
91 use matrix_sdk_test::async_test;
92 use ruma::{room_id, serde::Raw, user_id};
93 use serde_json::json;
94
95 use crate::{
96 BaseClient, Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomState,
97 SessionMeta, StateChanges,
98 client::ThreadingSupport,
99 latest_event::LatestEvent,
100 response_processors as processors,
101 store::{MemoryStore, RoomLoadSettings, StoreConfig},
102 };
103
104 fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
105 let store = Arc::new(MemoryStore::new());
106 let user_id = user_id!("@me:example.org");
107 let room_id = room_id!("!test:localhost");
108 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
109
110 (store.clone(), Room::new(user_id, store, room_id, room_type, sender))
111 }
112
113 #[async_test]
114 async fn test_setting_the_latest_event_doesnt_cause_a_room_info_notable_update() {
115 let client = BaseClient::new(
117 StoreConfig::new("cross-process-store-locks-holder-name".to_owned()),
118 ThreadingSupport::Disabled,
119 );
120
121 client
122 .activate(
123 SessionMeta {
124 user_id: user_id!("@alice:example.org").into(),
125 device_id: ruma::device_id!("AYEAYEAYE").into(),
126 },
127 RoomLoadSettings::default(),
128 None,
129 )
130 .await
131 .unwrap();
132
133 let room_id = room_id!("!test:localhost");
134 let room = client.get_or_create_room(room_id, RoomState::Joined);
135
136 add_encrypted_event(&room, "$A");
138 assert!(room.latest_event().is_none());
140
141 let mut room_info_notable_update = client.room_info_notable_update_receiver();
143
144 let event = make_latest_event("$A");
146
147 let mut context = processors::Context::default();
148 room.on_latest_event_decrypted(
149 event.clone(),
150 0,
151 &mut context.state_changes,
152 &mut context.room_info_notable_updates,
153 );
154
155 assert!(context.room_info_notable_updates.contains_key(room_id));
156
157 assert!(room_info_notable_update.is_empty());
159
160 processors::changes::save_and_apply(
162 context,
163 &client.state_store,
164 &client.ignore_user_list_changes,
165 None,
166 )
167 .await
168 .unwrap();
169
170 assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
171
172 assert_matches!(
174 room_info_notable_update.recv().await,
175 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
176 assert_eq!(received_room_id, room_id);
177 assert!(reasons.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
178 }
179 );
180 }
181
182 #[async_test]
183 async fn test_when_we_provide_a_newly_decrypted_event_it_replaces_latest_event() {
184 use std::collections::BTreeMap;
185
186 let (_store, room) = make_room_test_helper(RoomState::Joined);
188 add_encrypted_event(&room, "$A");
189 assert!(room.latest_event().is_none());
191
192 let event = make_latest_event("$A");
194 let mut changes = StateChanges::default();
195 let mut room_info_notable_updates = BTreeMap::new();
196 room.on_latest_event_decrypted(
197 event.clone(),
198 0,
199 &mut changes,
200 &mut room_info_notable_updates,
201 );
202 room.set_room_info(
203 changes.room_infos.get(room.room_id()).cloned().unwrap(),
204 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
205 );
206
207 assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
209 }
210
211 #[cfg(feature = "e2e-encryption")]
212 #[async_test]
213 async fn test_when_a_newly_decrypted_event_appears_we_delete_all_older_encrypted_events() {
214 use std::collections::BTreeMap;
217 let (_store, room) = make_room_test_helper(RoomState::Joined);
218 room.inner.update(|info| info.latest_event = Some(make_latest_event("$A")));
219 add_encrypted_event(&room, "$0");
220 add_encrypted_event(&room, "$1");
221 add_encrypted_event(&room, "$2");
222 add_encrypted_event(&room, "$3");
223
224 let new_event = make_latest_event("$1");
226 let new_event_index = 1;
227 let mut changes = StateChanges::default();
228 let mut room_info_notable_updates = BTreeMap::new();
229 room.on_latest_event_decrypted(
230 new_event.clone(),
231 new_event_index,
232 &mut changes,
233 &mut room_info_notable_updates,
234 );
235 room.set_room_info(
236 changes.room_infos.get(room.room_id()).cloned().unwrap(),
237 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
238 );
239
240 let enc_evs = room.latest_encrypted_events();
242 assert_eq!(enc_evs.len(), 2);
243 assert_eq!(enc_evs[0].get_field::<&str>("event_id").unwrap().unwrap(), "$2");
244 assert_eq!(enc_evs[1].get_field::<&str>("event_id").unwrap().unwrap(), "$3");
245
246 assert_eq!(room.latest_event().unwrap().event_id(), new_event.event_id());
248 }
249
250 #[async_test]
251 async fn test_replacing_the_newest_event_leaves_none_left() {
252 use std::collections::BTreeMap;
253
254 let (_store, room) = make_room_test_helper(RoomState::Joined);
256 add_encrypted_event(&room, "$0");
257 add_encrypted_event(&room, "$1");
258 add_encrypted_event(&room, "$2");
259 add_encrypted_event(&room, "$3");
260
261 let new_event = make_latest_event("$3");
263 let new_event_index = 3;
264 let mut changes = StateChanges::default();
265 let mut room_info_notable_updates = BTreeMap::new();
266 room.on_latest_event_decrypted(
267 new_event,
268 new_event_index,
269 &mut changes,
270 &mut room_info_notable_updates,
271 );
272 room.set_room_info(
273 changes.room_infos.get(room.room_id()).cloned().unwrap(),
274 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
275 );
276
277 let enc_evs = room.latest_encrypted_events();
279 assert_eq!(enc_evs.len(), 0);
280 }
281
282 fn add_encrypted_event(room: &Room, event_id: &str) {
283 room.latest_encrypted_events
284 .write()
285 .unwrap()
286 .push(Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap());
287 }
288
289 fn make_latest_event(event_id: &str) -> Box<LatestEvent> {
290 Box::new(LatestEvent::new(TimelineEvent::from_plaintext(
291 Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(),
292 )))
293 }
294}