#[cfg(feature = "e2e-encryption")]
use std::{collections::BTreeMap, num::NonZeroUsize};
use ruma::MilliSecondsSinceUnixEpoch;
#[cfg(feature = "e2e-encryption")]
use ruma::{OwnedRoomId, events::AnySyncTimelineEvent, serde::Raw};
use super::Room;
#[cfg(feature = "e2e-encryption")]
use super::RoomInfoNotableUpdateReasons;
use crate::latest_event::{LatestEvent, LatestEventValue};
impl Room {
#[cfg(feature = "e2e-encryption")]
pub(super) const MAX_ENCRYPTED_EVENTS: NonZeroUsize = NonZeroUsize::new(10).unwrap();
pub fn latest_event(&self) -> Option<LatestEvent> {
self.info.read().latest_event.as_deref().cloned()
}
pub fn new_latest_event(&self) -> LatestEventValue {
self.info.read().new_latest_event.clone()
}
pub fn new_latest_event_timestamp(&self) -> Option<MilliSecondsSinceUnixEpoch> {
self.info.read().new_latest_event.timestamp()
}
pub fn new_latest_event_is_local(&self) -> bool {
self.info.read().new_latest_event.is_local()
}
#[cfg(feature = "e2e-encryption")]
pub(crate) fn latest_encrypted_events(&self) -> Vec<Raw<AnySyncTimelineEvent>> {
self.latest_encrypted_events.read().unwrap().iter().cloned().collect()
}
#[cfg(feature = "e2e-encryption")]
pub(crate) fn on_latest_event_decrypted(
&self,
latest_event: Box<LatestEvent>,
index: usize,
changes: &mut crate::StateChanges,
room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
) {
self.latest_encrypted_events.write().unwrap().drain(0..=index);
let room_info = changes
.room_infos
.entry(self.room_id().to_owned())
.or_insert_with(|| self.clone_info());
room_info.latest_event = Some(latest_event);
room_info_notable_updates
.entry(self.room_id().to_owned())
.or_default()
.insert(RoomInfoNotableUpdateReasons::LATEST_EVENT);
}
}
#[cfg(all(test, feature = "e2e-encryption"))]
mod tests_with_e2e_encryption {
use std::sync::Arc;
use assert_matches::assert_matches;
use matrix_sdk_common::deserialized_responses::TimelineEvent;
use matrix_sdk_test::async_test;
use ruma::{room_id, serde::Raw, user_id};
use serde_json::json;
use crate::{
BaseClient, Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomState,
SessionMeta, StateChanges,
client::ThreadingSupport,
latest_event::LatestEvent,
response_processors as processors,
store::{MemoryStore, RoomLoadSettings, StoreConfig},
};
fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
let store = Arc::new(MemoryStore::new());
let user_id = user_id!("@me:example.org");
let room_id = room_id!("!test:localhost");
let (sender, _receiver) = tokio::sync::broadcast::channel(1);
(store.clone(), Room::new(user_id, store, room_id, room_type, sender))
}
#[async_test]
async fn test_setting_the_latest_event_doesnt_cause_a_room_info_notable_update() {
let client = BaseClient::new(
StoreConfig::new("cross-process-store-locks-holder-name".to_owned()),
ThreadingSupport::Disabled,
);
client
.activate(
SessionMeta {
user_id: user_id!("@alice:example.org").into(),
device_id: ruma::device_id!("AYEAYEAYE").into(),
},
RoomLoadSettings::default(),
None,
)
.await
.unwrap();
let room_id = room_id!("!test:localhost");
let room = client.get_or_create_room(room_id, RoomState::Joined);
add_encrypted_event(&room, "$A");
assert!(room.latest_event().is_none());
let mut room_info_notable_update = client.room_info_notable_update_receiver();
let event = make_latest_event("$A");
let mut context = processors::Context::default();
room.on_latest_event_decrypted(
event.clone(),
0,
&mut context.state_changes,
&mut context.room_info_notable_updates,
);
assert!(context.room_info_notable_updates.contains_key(room_id));
assert!(room_info_notable_update.is_empty());
processors::changes::save_and_apply(
context,
&client.state_store,
&client.ignore_user_list_changes,
None,
)
.await
.unwrap();
assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
assert_matches!(
room_info_notable_update.recv().await,
Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
assert_eq!(received_room_id, room_id);
assert!(reasons.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
}
);
}
#[async_test]
async fn test_when_we_provide_a_newly_decrypted_event_it_replaces_latest_event() {
use std::collections::BTreeMap;
let (_store, room) = make_room_test_helper(RoomState::Joined);
add_encrypted_event(&room, "$A");
assert!(room.latest_event().is_none());
let event = make_latest_event("$A");
let mut changes = StateChanges::default();
let mut room_info_notable_updates = BTreeMap::new();
room.on_latest_event_decrypted(
event.clone(),
0,
&mut changes,
&mut room_info_notable_updates,
);
room.set_room_info(
changes.room_infos.get(room.room_id()).cloned().unwrap(),
room_info_notable_updates.get(room.room_id()).copied().unwrap(),
);
assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
}
#[cfg(feature = "e2e-encryption")]
#[async_test]
async fn test_when_a_newly_decrypted_event_appears_we_delete_all_older_encrypted_events() {
use std::collections::BTreeMap;
let (_store, room) = make_room_test_helper(RoomState::Joined);
room.info.update(|info| info.latest_event = Some(make_latest_event("$A")));
add_encrypted_event(&room, "$0");
add_encrypted_event(&room, "$1");
add_encrypted_event(&room, "$2");
add_encrypted_event(&room, "$3");
let new_event = make_latest_event("$1");
let new_event_index = 1;
let mut changes = StateChanges::default();
let mut room_info_notable_updates = BTreeMap::new();
room.on_latest_event_decrypted(
new_event.clone(),
new_event_index,
&mut changes,
&mut room_info_notable_updates,
);
room.set_room_info(
changes.room_infos.get(room.room_id()).cloned().unwrap(),
room_info_notable_updates.get(room.room_id()).copied().unwrap(),
);
let enc_evs = room.latest_encrypted_events();
assert_eq!(enc_evs.len(), 2);
assert_eq!(enc_evs[0].get_field::<&str>("event_id").unwrap().unwrap(), "$2");
assert_eq!(enc_evs[1].get_field::<&str>("event_id").unwrap().unwrap(), "$3");
assert_eq!(room.latest_event().unwrap().event_id(), new_event.event_id());
}
#[async_test]
async fn test_replacing_the_newest_event_leaves_none_left() {
use std::collections::BTreeMap;
let (_store, room) = make_room_test_helper(RoomState::Joined);
add_encrypted_event(&room, "$0");
add_encrypted_event(&room, "$1");
add_encrypted_event(&room, "$2");
add_encrypted_event(&room, "$3");
let new_event = make_latest_event("$3");
let new_event_index = 3;
let mut changes = StateChanges::default();
let mut room_info_notable_updates = BTreeMap::new();
room.on_latest_event_decrypted(
new_event,
new_event_index,
&mut changes,
&mut room_info_notable_updates,
);
room.set_room_info(
changes.room_infos.get(room.room_id()).cloned().unwrap(),
room_info_notable_updates.get(room.room_id()).copied().unwrap(),
);
let enc_evs = room.latest_encrypted_events();
assert_eq!(enc_evs.len(), 0);
}
fn add_encrypted_event(room: &Room, event_id: &str) {
room.latest_encrypted_events
.write()
.unwrap()
.push(Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap());
}
fn make_latest_event(event_id: &str) -> Box<LatestEvent> {
Box::new(LatestEvent::new(TimelineEvent::from_plaintext(
Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(),
)))
}
}