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