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 latest_event::LatestEvent,
92 response_processors as processors,
93 store::{MemoryStore, RoomLoadSettings, StoreConfig},
94 BaseClient, Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomState,
95 SessionMeta, StateChanges,
96 };
97
98 fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
99 let store = Arc::new(MemoryStore::new());
100 let user_id = user_id!("@me:example.org");
101 let room_id = room_id!("!test:localhost");
102 let (sender, _receiver) = tokio::sync::broadcast::channel(1);
103
104 (store.clone(), Room::new(user_id, store, room_id, room_type, sender))
105 }
106
107 #[async_test]
108 async fn test_setting_the_latest_event_doesnt_cause_a_room_info_notable_update() {
109 let client =
111 BaseClient::new(StoreConfig::new("cross-process-store-locks-holder-name".to_owned()));
112
113 client
114 .activate(
115 SessionMeta {
116 user_id: user_id!("@alice:example.org").into(),
117 device_id: ruma::device_id!("AYEAYEAYE").into(),
118 },
119 RoomLoadSettings::default(),
120 None,
121 )
122 .await
123 .unwrap();
124
125 let room_id = room_id!("!test:localhost");
126 let room = client.get_or_create_room(room_id, RoomState::Joined);
127
128 add_encrypted_event(&room, "$A");
130 assert!(room.latest_event().is_none());
132
133 let mut room_info_notable_update = client.room_info_notable_update_receiver();
135
136 let event = make_latest_event("$A");
138
139 let mut context = processors::Context::default();
140 room.on_latest_event_decrypted(
141 event.clone(),
142 0,
143 &mut context.state_changes,
144 &mut context.room_info_notable_updates,
145 );
146
147 assert!(context.room_info_notable_updates.contains_key(room_id));
148
149 assert!(room_info_notable_update.is_empty());
151
152 processors::changes::save_and_apply(
154 context,
155 &client.state_store,
156 &client.ignore_user_list_changes,
157 None,
158 )
159 .await
160 .unwrap();
161
162 assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
163
164 assert_matches!(
166 room_info_notable_update.recv().await,
167 Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
168 assert_eq!(received_room_id, room_id);
169 assert!(reasons.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
170 }
171 );
172 }
173
174 #[async_test]
175 async fn test_when_we_provide_a_newly_decrypted_event_it_replaces_latest_event() {
176 use std::collections::BTreeMap;
177
178 let (_store, room) = make_room_test_helper(RoomState::Joined);
180 add_encrypted_event(&room, "$A");
181 assert!(room.latest_event().is_none());
183
184 let event = make_latest_event("$A");
186 let mut changes = StateChanges::default();
187 let mut room_info_notable_updates = BTreeMap::new();
188 room.on_latest_event_decrypted(
189 event.clone(),
190 0,
191 &mut changes,
192 &mut room_info_notable_updates,
193 );
194 room.set_room_info(
195 changes.room_infos.get(room.room_id()).cloned().unwrap(),
196 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
197 );
198
199 assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
201 }
202
203 #[cfg(feature = "e2e-encryption")]
204 #[async_test]
205 async fn test_when_a_newly_decrypted_event_appears_we_delete_all_older_encrypted_events() {
206 use std::collections::BTreeMap;
209 let (_store, room) = make_room_test_helper(RoomState::Joined);
210 room.inner.update(|info| info.latest_event = Some(make_latest_event("$A")));
211 add_encrypted_event(&room, "$0");
212 add_encrypted_event(&room, "$1");
213 add_encrypted_event(&room, "$2");
214 add_encrypted_event(&room, "$3");
215
216 let new_event = make_latest_event("$1");
218 let new_event_index = 1;
219 let mut changes = StateChanges::default();
220 let mut room_info_notable_updates = BTreeMap::new();
221 room.on_latest_event_decrypted(
222 new_event.clone(),
223 new_event_index,
224 &mut changes,
225 &mut room_info_notable_updates,
226 );
227 room.set_room_info(
228 changes.room_infos.get(room.room_id()).cloned().unwrap(),
229 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
230 );
231
232 let enc_evs = room.latest_encrypted_events();
234 assert_eq!(enc_evs.len(), 2);
235 assert_eq!(enc_evs[0].get_field::<&str>("event_id").unwrap().unwrap(), "$2");
236 assert_eq!(enc_evs[1].get_field::<&str>("event_id").unwrap().unwrap(), "$3");
237
238 assert_eq!(room.latest_event().unwrap().event_id(), new_event.event_id());
240 }
241
242 #[async_test]
243 async fn test_replacing_the_newest_event_leaves_none_left() {
244 use std::collections::BTreeMap;
245
246 let (_store, room) = make_room_test_helper(RoomState::Joined);
248 add_encrypted_event(&room, "$0");
249 add_encrypted_event(&room, "$1");
250 add_encrypted_event(&room, "$2");
251 add_encrypted_event(&room, "$3");
252
253 let new_event = make_latest_event("$3");
255 let new_event_index = 3;
256 let mut changes = StateChanges::default();
257 let mut room_info_notable_updates = BTreeMap::new();
258 room.on_latest_event_decrypted(
259 new_event,
260 new_event_index,
261 &mut changes,
262 &mut room_info_notable_updates,
263 );
264 room.set_room_info(
265 changes.room_infos.get(room.room_id()).cloned().unwrap(),
266 room_info_notable_updates.get(room.room_id()).copied().unwrap(),
267 );
268
269 let enc_evs = room.latest_encrypted_events();
271 assert_eq!(enc_evs.len(), 0);
272 }
273
274 fn add_encrypted_event(room: &Room, event_id: &str) {
275 room.latest_encrypted_events
276 .write()
277 .unwrap()
278 .push(Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap());
279 }
280
281 fn make_latest_event(event_id: &str) -> Box<LatestEvent> {
282 Box::new(LatestEvent::new(TimelineEvent::new(
283 Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(),
284 )))
285 }
286}