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