1use std::collections::BTreeSet;
16
17use as_variant::as_variant;
18use ruma::{
19 RoomId,
20 events::{AnySyncStateEvent, SyncStateEvent},
21 serde::Raw,
22};
23use tracing::error;
24
25use super::Context;
26#[cfg(feature = "experimental-encrypted-state-events")]
27use super::e2ee;
28use crate::{store::BaseStateStore, utils::RawStateEventWithKeys};
29
30pub mod sync {
32 use std::collections::BTreeSet;
33
34 use as_variant::as_variant;
35 use ruma::{
36 OwnedUserId, RoomId, UserId,
37 events::{
38 AnySyncStateEvent, AnySyncTimelineEvent, StateEventType, room::member::MembershipState,
39 },
40 };
41 use tracing::instrument;
42
43 use super::{super::profiles, Context, Raw};
44 #[cfg(feature = "experimental-encrypted-state-events")]
45 use crate::response_processors::e2ee;
46 use crate::{
47 RoomInfo, RoomInfoNotableUpdateReasons, RoomState,
48 store::{BaseStateStore, Result as StoreResult, ambiguity_map::AmbiguityCache},
49 sync::State,
50 utils::RawStateEventWithKeys,
51 };
52
53 impl State {
54 pub(crate) fn collect(
59 &self,
60 timeline: &[Raw<AnySyncTimelineEvent>],
61 ) -> Vec<RawStateEventWithKeys<AnySyncStateEvent>> {
62 match self {
63 Self::Before(events) => events
64 .iter()
65 .cloned()
66 .filter_map(RawStateEventWithKeys::try_from_raw_state_event)
67 .chain(
68 timeline
69 .iter()
70 .filter_map(RawStateEventWithKeys::try_from_raw_timeline_event),
71 )
72 .collect(),
73 Self::After(events) => events
74 .iter()
75 .cloned()
76 .filter_map(RawStateEventWithKeys::try_from_raw_state_event)
77 .collect(),
78 }
79 }
80 }
81
82 #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
92 pub async fn dispatch<U>(
93 context: &mut Context,
94 raw_events: Vec<RawStateEventWithKeys<AnySyncStateEvent>>,
95 room_info: &mut RoomInfo,
96 ambiguity_cache: &mut AmbiguityCache,
97 new_users: &mut U,
98 state_store: &BaseStateStore,
99 #[cfg(feature = "experimental-encrypted-state-events")] e2ee: &e2ee::E2EE<'_>,
100 ) -> StoreResult<()>
101 where
102 U: NewUsers,
103 {
104 for mut raw_event in raw_events {
105 match (&raw_event.event_type, raw_event.state_key.as_str()) {
106 (StateEventType::RoomMember, _) => {
107 room_info.handle_state_event(&mut raw_event);
108
109 dispatch_room_member(
110 context,
111 &room_info.room_id,
112 &mut raw_event,
113 ambiguity_cache,
114 new_users,
115 )
116 .await?;
117 }
118
119 (StateEventType::RoomCreate, "") => {
120 super::validate_create_event_predecessor(
121 context,
122 &room_info.room_id,
123 &mut raw_event,
124 state_store,
125 );
126
127 room_info.handle_state_event(&mut raw_event);
128 }
129
130 (StateEventType::RoomTombstone, "") => {
131 if super::is_tombstone_event_valid(
132 context,
133 &room_info.room_id,
134 &mut raw_event,
135 state_store,
136 ) {
137 room_info.handle_state_event(&mut raw_event);
138 } else {
139 continue;
142 }
143 }
144
145 #[cfg(feature = "experimental-encrypted-state-events")]
146 (StateEventType::RoomEncrypted, _) => {
147 let Some(mut raw_event) =
148 super::decrypt_state_event(&mut raw_event, &room_info.room_id, e2ee).await
149 else {
150 continue;
151 };
152
153 room_info.handle_state_event(&mut raw_event);
154 }
155
156 _ => {
157 room_info.handle_state_event(&mut raw_event);
158 }
159 }
160
161 context
162 .state_changes
163 .state
164 .entry(room_info.room_id.to_owned())
165 .or_default()
166 .entry(raw_event.event_type)
167 .or_default()
168 .insert(raw_event.state_key, raw_event.raw);
169 }
170
171 Ok(())
172 }
173
174 async fn dispatch_room_member<U>(
176 context: &mut Context,
177 room_id: &RoomId,
178 raw_event: &mut RawStateEventWithKeys<AnySyncStateEvent>,
179 ambiguity_cache: &mut AmbiguityCache,
180 new_users: &mut U,
181 ) -> StoreResult<()>
182 where
183 U: NewUsers,
184 {
185 let Some(event) = raw_event
186 .deserialize_as(|any_event| as_variant!(any_event, AnySyncStateEvent::RoomMember))
187 else {
188 return Ok(());
189 };
190
191 ambiguity_cache.handle_event(&context.state_changes, room_id, event).await?;
192
193 match event.membership() {
194 MembershipState::Join | MembershipState::Invite => {
195 new_users.insert(event.state_key());
196 }
197 _ => (),
198 }
199
200 profiles::upsert_or_delete(context, room_id, event);
201
202 Ok(())
203 }
204
205 pub(crate) trait NewUsers {
207 fn insert(&mut self, user_id: &UserId);
209 }
210
211 impl NewUsers for BTreeSet<OwnedUserId> {
212 fn insert(&mut self, user_id: &UserId) {
213 self.insert(user_id.to_owned());
214 }
215 }
216
217 impl NewUsers for () {
218 fn insert(&mut self, _user_id: &UserId) {}
219 }
220
221 pub fn own_membership_and_update_room_state(
225 context: &mut Context,
226 user_id: &UserId,
227 state_events: &mut [RawStateEventWithKeys<AnySyncStateEvent>],
228 room_info: &mut RoomInfo,
229 ) {
230 if let Some(member) = state_events.iter_mut().rev().find_map(|event| {
234 if event.event_type == StateEventType::RoomMember
236 && event.state_key.as_str() == user_id
237 && let Some(member) = event.deserialize_as(|any_event| {
238 as_variant!(any_event, AnySyncStateEvent::RoomMember)
239 })
240 {
241 Some(member)
242 } else {
243 None
244 }
245 }) {
246 let new_state: RoomState = member.membership().into();
247
248 if new_state != room_info.state() {
249 room_info.set_state(new_state);
250
251 context
253 .room_info_notable_updates
254 .entry(room_info.room_id.to_owned())
255 .or_default()
256 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
257 }
258 }
259 }
260}
261
262pub mod stripped {
264 use std::collections::BTreeMap;
265
266 use ruma::{
267 RoomId, UserId,
268 events::{AnyStrippedStateEvent, StateEventType},
269 push::Action,
270 };
271 use tracing::instrument;
272
273 use super::{
274 super::{notification, timeline},
275 Context, Raw,
276 };
277 use crate::{RawStateEventWithKeys, Result, Room, RoomInfo, RoomInfoNotableUpdateReasons};
278
279 pub fn collect(
282 raw_events: &[Raw<AnyStrippedStateEvent>],
283 ) -> Vec<RawStateEventWithKeys<AnyStrippedStateEvent>> {
284 raw_events
285 .iter()
286 .cloned()
287 .filter_map(RawStateEventWithKeys::try_from_raw_state_event)
288 .collect()
289 }
290
291 #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
308 pub(crate) async fn dispatch_invite_or_knock(
309 context: &mut Context,
310 raw_events: Vec<RawStateEventWithKeys<AnyStrippedStateEvent>>,
311 room: &Room,
312 room_info: &mut RoomInfo,
313 user_id: &UserId,
314 mut notification: notification::Notification<'_>,
315 ) -> Result<()> {
316 let mut state_events = BTreeMap::new();
317
318 own_membership(context, room.room_id(), user_id, &raw_events);
319
320 for mut raw_event in raw_events {
321 room_info.handle_stripped_state_event(&mut raw_event);
322
323 state_events
324 .entry(raw_event.event_type)
325 .or_insert_with(BTreeMap::new)
326 .insert(raw_event.state_key, raw_event.raw);
327 }
328
329 context
330 .state_changes
331 .stripped_state
332 .insert(room_info.room_id().to_owned(), state_events.clone());
333
334 if let Some(push_condition_room_ctx) =
337 timeline::get_push_room_context(context, room, room_info).await?
338 {
339 for event in state_events.values().flat_map(|map| map.values()) {
341 notification
342 .push_notification_from_event_if(
343 &push_condition_room_ctx,
344 event,
345 Action::should_notify,
346 )
347 .await;
348 }
349 }
350
351 Ok(())
352 }
353
354 pub fn own_membership(
357 context: &mut Context,
358 room_id: &RoomId,
359 user_id: &UserId,
360 raw_state_events: &[RawStateEventWithKeys<AnyStrippedStateEvent>],
361 ) {
362 if raw_state_events.iter().rev().any(|raw_event| {
366 raw_event.event_type == StateEventType::RoomMember
368 && raw_event.state_key == user_id.as_str()
369 }) {
370 context
372 .room_info_notable_updates
373 .entry(room_id.to_owned())
374 .or_default()
375 .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
376 }
377 }
378}
379
380pub fn validate_create_event_predecessor(
385 context: &mut Context,
386 room_id: &RoomId,
387 raw_event: &mut RawStateEventWithKeys<AnySyncStateEvent>,
388 state_store: &BaseStateStore,
389) {
390 let mut already_seen = BTreeSet::new();
391 already_seen.insert(room_id.to_owned());
392
393 let Some(event) =
394 raw_event.deserialize_as(|any_event| as_variant!(any_event, AnySyncStateEvent::RoomCreate))
395 else {
396 return;
397 };
398
399 let content = match event {
401 SyncStateEvent::Original(event) => &event.content,
402 SyncStateEvent::Redacted(event) => &event.content,
403 };
404
405 let Some(mut predecessor_room_id) =
406 content.predecessor.as_ref().map(|predecessor| predecessor.room_id.clone())
407 else {
408 return;
410 };
411
412 loop {
413 if already_seen.contains(&predecessor_room_id) {
417 let mut event = event.clone();
420
421 match &mut event {
422 SyncStateEvent::Original(event) => event.content.predecessor.take(),
423 SyncStateEvent::Redacted(event) => event.content.predecessor.take(),
424 };
425
426 raw_event.set_cached_event(event.into());
427
428 return;
429 }
430
431 already_seen.insert(predecessor_room_id.clone());
432
433 let Some(next_predecessor_room_id) = context
436 .state_changes
437 .room_infos
438 .get(&predecessor_room_id)
439 .and_then(|room_info| Some(room_info.create()?.predecessor.as_ref()?.room_id.clone()))
440 .or_else(|| {
441 state_store
442 .room(&predecessor_room_id)
443 .and_then(|room| Some(room.predecessor_room()?.room_id))
444 })
445 else {
446 break;
448 };
449
450 predecessor_room_id = next_predecessor_room_id;
451 }
452}
453
454pub fn is_tombstone_event_valid(
456 context: &mut Context,
457 room_id: &RoomId,
458 raw_event: &mut RawStateEventWithKeys<AnySyncStateEvent>,
459 state_store: &BaseStateStore,
460) -> bool {
461 let mut already_seen = BTreeSet::new();
462 already_seen.insert(room_id.to_owned());
463
464 let Some(tombstone) = raw_event
465 .deserialize_as(|any_event| as_variant!(any_event, AnySyncStateEvent::RoomTombstone))
466 .and_then(|event| Some(&event.as_original()?.content))
467 else {
468 return true;
470 };
471
472 let mut successor_room_id = tombstone.replacement_room.clone();
473
474 loop {
475 if already_seen.contains(AsRef::<RoomId>::as_ref(&successor_room_id)) {
478 error!(?room_id, ?tombstone, "`m.room.tombstone` event is invalid, it creates a loop");
480 return false;
481 }
482
483 already_seen.insert(successor_room_id.clone());
484
485 let Some(next_successor_room_id) = context
487 .state_changes
488 .room_infos
489 .get(&successor_room_id)
490 .and_then(|room_info| room_info.tombstone()?.replacement_room.clone())
491 .or_else(|| {
492 state_store
493 .room(&successor_room_id)
494 .and_then(|room| Some(room.successor_room()?.room_id))
495 })
496 else {
497 break;
499 };
500
501 successor_room_id = next_successor_room_id;
502 }
503
504 true
505}
506
507#[cfg(feature = "experimental-encrypted-state-events")]
512async fn decrypt_state_event(
513 raw_event: &mut RawStateEventWithKeys<AnySyncStateEvent>,
514 room_id: &RoomId,
515 e2ee: &e2ee::E2EE<'_>,
516) -> Option<RawStateEventWithKeys<AnySyncStateEvent>> {
517 use matrix_sdk_crypto::RoomEventDecryptionResult;
518 use ruma::OwnedEventId;
519 use tracing::{trace, warn};
520
521 let event_id = match raw_event.raw.get_field::<OwnedEventId>("event_id") {
522 Ok(Some(event_id)) => event_id,
523 Ok(None) => {
524 warn!("Couldn't deserialize encrypted state event's ID: missing `event_id` field");
525 return None;
526 }
527 Err(error) => {
528 warn!(?error, "Couldn't deserialize encrypted state event's ID");
529 return None;
530 }
531 };
532
533 trace!(?event_id, "Received encrypted state event, attempting decryption...");
534
535 let olm_machine = e2ee.olm_machine?;
536
537 let decrypted_event = olm_machine
538 .try_decrypt_room_event(
539 raw_event.raw.cast_ref_unchecked(),
540 room_id,
541 e2ee.decryption_settings,
542 )
543 .await
544 .expect("OlmMachine was not started");
545
546 let RoomEventDecryptionResult::Decrypted(decrypted_event) = decrypted_event else {
548 warn!(?event_id, "Failed to decrypt state event");
549 return None;
550 };
551
552 match RawStateEventWithKeys::try_from_raw_state_event(decrypted_event.event.cast_unchecked()) {
555 Some(event) => {
556 trace!(?event_id, "Decrypted state event successfully.");
557 Some(event)
558 }
559 None => {
560 warn!(?event_id, "Failed to decrypt state event: decrypted state event is invalid");
561 None
562 }
563 }
564}
565
566#[cfg(test)]
567mod tests {
568 use assert_matches2::assert_matches;
569 use matrix_sdk_test::{
570 DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, SyncResponseBuilder, TestResult, async_test,
571 event_factory::EventFactory,
572 };
573 use ruma::{RoomVersionId, event_id, room_id, user_id};
574
575 use crate::test_utils::logged_in_base_client;
576
577 #[async_test]
578 async fn test_not_possible_to_overwrite_m_room_create() -> TestResult {
579 let sender = user_id!("@mnt_io:matrix.org");
580 let event_factory = EventFactory::new().sender(sender);
581 let mut response_builder = SyncResponseBuilder::new();
582 let room_id_0 = room_id!("!r0");
583 let room_id_1 = room_id!("!r1");
584 let room_id_2 = room_id!("!r2");
585
586 let client = logged_in_base_client(None).await;
587
588 {
592 let response = response_builder
593 .add_joined_room(
594 JoinedRoomBuilder::new(room_id_0)
595 .add_timeline_event(
596 event_factory.create(sender, RoomVersionId::try_from("42")?),
597 )
598 .add_timeline_event(
599 event_factory.create(sender, RoomVersionId::try_from("43")?),
600 ),
601 )
602 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
603 event_factory.create(sender, RoomVersionId::try_from("44")?),
604 ))
605 .add_joined_room(JoinedRoomBuilder::new(room_id_2))
606 .build_sync_response();
607
608 assert!(client.receive_sync_response(response).await.is_ok());
609
610 assert_eq!(
613 client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
614 "42"
615 );
616 assert_eq!(
618 client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
619 "44"
620 );
621 assert!(client.get_room(room_id_2).unwrap().create_content().is_none());
623 }
624
625 {
629 let response = response_builder
630 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
631 event_factory.create(sender, RoomVersionId::try_from("45")?),
632 ))
633 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
634 event_factory.create(sender, RoomVersionId::try_from("46")?),
635 ))
636 .add_joined_room(JoinedRoomBuilder::new(room_id_2).add_timeline_event(
637 event_factory.create(sender, RoomVersionId::try_from("47")?),
638 ))
639 .build_sync_response();
640
641 assert!(client.receive_sync_response(response).await.is_ok());
642
643 assert_eq!(
646 client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
647 "42"
648 );
649 assert_eq!(
652 client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
653 "44"
654 );
655 assert_eq!(
657 client.get_room(room_id_2).unwrap().create_content().unwrap().room_version.as_str(),
658 "47"
659 );
660 }
661
662 Ok(())
663 }
664
665 #[async_test]
666 async fn test_check_room_upgrades_no_newly_tombstoned_rooms() {
667 let client = logged_in_base_client(None).await;
668
669 {
671 let response = SyncResponseBuilder::new()
672 .add_joined_room(JoinedRoomBuilder::new(room_id!("!r0")))
673 .build_sync_response();
674
675 assert!(client.receive_sync_response(response).await.is_ok());
676 }
677 }
678
679 #[async_test]
680 async fn test_check_room_upgrades_no_error() -> TestResult {
681 let sender = user_id!("@mnt_io:matrix.org");
682 let event_factory = EventFactory::new().sender(sender);
683 let mut response_builder = SyncResponseBuilder::new();
684 let room_id_0 = room_id!("!r0");
685 let room_id_1 = room_id!("!r1");
686 let room_id_2 = room_id!("!r2");
687
688 let client = logged_in_base_client(None).await;
689
690 {
692 let response = response_builder
693 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
694 event_factory.create(sender, RoomVersionId::try_from("41")?),
696 ))
697 .build_sync_response();
698
699 assert!(client.receive_sync_response(response).await.is_ok());
700
701 let room_0 = client.get_room(room_id_0).unwrap();
702
703 assert!(room_0.predecessor_room().is_none());
704 assert!(room_0.successor_room().is_none());
705 }
706
707 {
709 let tombstone_event_id = event_id!("$ev0");
710 let response = response_builder
711 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
712 event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
714 ))
715 .add_joined_room(
716 JoinedRoomBuilder::new(room_id_1).add_timeline_event(
717 event_factory
719 .create(sender, RoomVersionId::try_from("42")?)
720 .predecessor(room_id_0),
721 ),
722 )
723 .build_sync_response();
724
725 assert!(client.receive_sync_response(response).await.is_ok());
726
727 let room_0 = client.get_room(room_id_0).unwrap();
728
729 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
730 assert_eq!(
731 room_0.successor_room().expect("room 0 must have a successor").room_id,
732 room_id_1,
733 "room 0 does not have the expected successor",
734 );
735
736 let room_1 = client.get_room(room_id_1).unwrap();
737
738 assert_eq!(
739 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
740 room_id_0,
741 "room 1 does not have the expected predecessor",
742 );
743 assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
744 }
745
746 {
748 let tombstone_event_id = event_id!("$ev1");
749 let response = response_builder
750 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
751 event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
753 ))
754 .add_joined_room(
755 JoinedRoomBuilder::new(room_id_2).add_timeline_event(
756 event_factory
758 .create(sender, RoomVersionId::try_from("43")?)
759 .predecessor(room_id_1),
760 ),
761 )
762 .build_sync_response();
763
764 assert!(client.receive_sync_response(response).await.is_ok());
765
766 let room_1 = client.get_room(room_id_1).unwrap();
767
768 assert_eq!(
769 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
770 room_id_0,
771 "room 1 does not have the expected predecessor",
772 );
773 assert_eq!(
774 room_1.successor_room().expect("room 1 must have a successor").room_id,
775 room_id_2,
776 "room 1 does not have the expected successor",
777 );
778
779 let room_2 = client.get_room(room_id_2).unwrap();
780
781 assert_eq!(
782 room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
783 room_id_1,
784 "room 2 does not have the expected predecessor",
785 );
786 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
787 }
788
789 Ok(())
790 }
791
792 #[async_test]
793 async fn test_check_room_upgrades_no_loop_within_misordered_rooms() -> TestResult {
794 let sender = user_id!("@mnt_io:matrix.org");
795 let event_factory = EventFactory::new().sender(sender);
796 let mut response_builder = SyncResponseBuilder::new();
797 let room_id_0 = room_id!("!r1");
800 let room_id_1 = room_id!("!r0");
801 let room_id_2 = room_id!("!r2");
802
803 let client = logged_in_base_client(None).await;
804
805 {
808 let response = response_builder
809 .add_joined_room(
811 JoinedRoomBuilder::new(room_id_0)
812 .add_timeline_event(
813 event_factory.create(sender, RoomVersionId::try_from("41")?),
815 )
816 .add_timeline_event(
817 event_factory
819 .room_tombstone("hello", room_id_1)
820 .event_id(event_id!("$ev0")),
821 ),
822 )
823 .add_joined_room(
825 JoinedRoomBuilder::new(room_id_1)
826 .add_timeline_event(
827 event_factory
829 .create(sender, RoomVersionId::try_from("42")?)
830 .predecessor(room_id_0),
831 )
832 .add_timeline_event(
833 event_factory
835 .room_tombstone("hello", room_id_2)
836 .event_id(event_id!("$ev1")),
837 ),
838 )
839 .add_joined_room(
841 JoinedRoomBuilder::new(room_id_2).add_timeline_event(
842 event_factory
844 .create(sender, RoomVersionId::try_from("43")?)
845 .predecessor(room_id_1),
846 ),
847 )
848 .build_sync_response();
849
850 {
852 let mut rooms = response.rooms.join.keys();
853
854 assert_eq!(rooms.next().unwrap(), room_id_1);
856 assert_eq!(rooms.next().unwrap(), room_id_0);
857 assert_eq!(rooms.next().unwrap(), room_id_2);
858 assert!(rooms.next().is_none());
859 }
860
861 assert!(client.receive_sync_response(response).await.is_ok());
863
864 let room_0 = client.get_room(room_id_0).unwrap();
865
866 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
867 assert_eq!(
868 room_0.successor_room().expect("room 0 must have a successor").room_id,
869 room_id_1,
870 "room 0 does not have the expected successor",
871 );
872
873 let room_1 = client.get_room(room_id_1).unwrap();
874
875 assert_eq!(
876 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
877 room_id_0,
878 "room 1 does not have the expected predecessor",
879 );
880 assert_eq!(
881 room_1.successor_room().expect("room 1 must have a successor").room_id,
882 room_id_2,
883 "room 1 does not have the expected successor",
884 );
885
886 let room_2 = client.get_room(room_id_2).unwrap();
887
888 assert_eq!(
889 room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
890 room_id_1,
891 "room 2 does not have the expected predecessor",
892 );
893 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
894 }
895
896 Ok(())
897 }
898
899 #[async_test]
900 async fn test_check_room_upgrades_shortest_invalid_successor() -> TestResult {
901 let sender = user_id!("@mnt_io:matrix.org");
902 let event_factory = EventFactory::new().sender(sender);
903 let mut response_builder = SyncResponseBuilder::new();
904 let room_id_0 = room_id!("!r0");
905
906 let client = logged_in_base_client(None).await;
907
908 {
910 let tombstone_event_id = event_id!("$ev0");
911 let response = response_builder
912 .add_joined_room(
913 JoinedRoomBuilder::new(room_id_0).add_timeline_event(
916 event_factory
917 .room_tombstone("hello", room_id_0)
918 .event_id(tombstone_event_id),
919 ),
920 )
921 .build_sync_response();
922
923 assert!(client.receive_sync_response(response).await.is_ok());
925
926 let room_0 = client.get_room(room_id_0).unwrap();
928
929 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
930 assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
931 }
932
933 Ok(())
934 }
935
936 #[async_test]
937 async fn test_check_room_upgrades_invalid_successor() -> TestResult {
938 let sender = user_id!("@mnt_io:matrix.org");
939 let event_factory = EventFactory::new().sender(sender);
940 let mut response_builder = SyncResponseBuilder::new();
941 let room_id_0 = room_id!("!r0");
942 let room_id_1 = room_id!("!r1");
943 let room_id_2 = room_id!("!r2");
944
945 let client = logged_in_base_client(None).await;
946
947 {
949 let tombstone_event_id = event_id!("$ev0");
950 let response = response_builder
951 .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
952 event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
954 ))
955 .add_joined_room(
956 JoinedRoomBuilder::new(room_id_1).add_timeline_event(
957 event_factory
959 .create(sender, RoomVersionId::try_from("42")?)
960 .predecessor(room_id_0),
961 ),
962 )
963 .build_sync_response();
964
965 assert!(client.receive_sync_response(response).await.is_ok());
966
967 let room_0 = client.get_room(room_id_0).unwrap();
968
969 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
970 assert_eq!(
971 room_0.successor_room().expect("room 0 must have a successor").room_id,
972 room_id_1,
973 "room 0 does not have the expected successor",
974 );
975
976 let room_1 = client.get_room(room_id_1).unwrap();
977
978 assert_eq!(
979 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
980 room_id_0,
981 "room 1 does not have the expected predecessor",
982 );
983 assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
984 }
985
986 {
988 let tombstone_event_id = event_id!("$ev1");
989 let response = response_builder
990 .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
991 event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
993 ))
994 .add_joined_room(
995 JoinedRoomBuilder::new(room_id_2)
996 .add_timeline_event(
997 event_factory
999 .create(sender, RoomVersionId::try_from("43")?)
1000 .predecessor(room_id_1),
1001 )
1002 .add_timeline_event(
1003 event_factory
1005 .room_tombstone("hehe", room_id_0)
1006 .event_id(event_id!("$ev_foo")),
1007 ),
1008 )
1009 .build_sync_response();
1010
1011 assert!(client.receive_sync_response(response).await.is_ok());
1013
1014 let room_0 = client.get_room(room_id_0).unwrap();
1016
1017 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
1018 assert_eq!(
1019 room_0.successor_room().expect("room 0 must have a successor").room_id,
1020 room_id_1,
1021 "room 0 does not have the expected successor",
1022 );
1023
1024 let room_1 = client.get_room(room_id_1).unwrap();
1025
1026 assert_eq!(
1027 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
1028 room_id_0,
1029 "room 1 does not have the expected predecessor",
1030 );
1031 assert_eq!(
1032 room_1.successor_room().expect("room 1 must have a successor").room_id,
1033 room_id_2,
1034 "room 1 does not have the expected successor",
1035 );
1036
1037 let room_2 = client.get_room(room_id_2).unwrap();
1038
1039 assert_eq!(
1040 room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
1041 room_id_1,
1042 "room 2 does not have the expected predecessor",
1043 );
1044 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
1046 }
1047
1048 Ok(())
1049 }
1050
1051 #[async_test]
1052 async fn test_check_room_upgrades_shortest_invalid_predecessor() -> TestResult {
1053 let sender = user_id!("@mnt_io:matrix.org");
1054 let event_factory = EventFactory::new().sender(sender);
1055 let mut response_builder = SyncResponseBuilder::new();
1056 let room_id_0 = room_id!("!r0");
1057
1058 let client = logged_in_base_client(None).await;
1059
1060 {
1062 let tombstone_event_id = event_id!("$ev0");
1063 let response = response_builder
1064 .add_joined_room(
1065 JoinedRoomBuilder::new(room_id_0).add_timeline_event(
1068 event_factory
1069 .create(sender, RoomVersionId::try_from("42")?)
1070 .predecessor(room_id_0)
1071 .event_id(tombstone_event_id),
1072 ),
1073 )
1074 .build_sync_response();
1075
1076 assert!(client.receive_sync_response(response).await.is_ok());
1078
1079 let room_0 = client.get_room(room_id_0).unwrap();
1081
1082 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
1083 assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
1084 assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
1085 }
1086
1087 Ok(())
1088 }
1089
1090 #[async_test]
1091 async fn test_check_room_upgrades_shortest_loop() -> TestResult {
1092 let sender = user_id!("@mnt_io:matrix.org");
1093 let event_factory = EventFactory::new().sender(sender);
1094 let mut response_builder = SyncResponseBuilder::new();
1095 let room_id_0 = room_id!("!r0");
1096
1097 let client = logged_in_base_client(None).await;
1098
1099 {
1101 let tombstone_event_id = event_id!("$ev0");
1102 let response = response_builder
1103 .add_joined_room(
1104 JoinedRoomBuilder::new(room_id_0)
1105 .add_timeline_event(
1106 event_factory
1108 .room_tombstone("hello", room_id_0)
1109 .event_id(tombstone_event_id),
1110 )
1111 .add_timeline_event(
1112 event_factory
1114 .create(sender, RoomVersionId::try_from("42")?)
1115 .predecessor(room_id_0),
1116 ),
1117 )
1118 .build_sync_response();
1119
1120 assert!(client.receive_sync_response(response).await.is_ok());
1122
1123 let room_0 = client.get_room(room_id_0).unwrap();
1125
1126 assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
1127 assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
1128 assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
1129 }
1130
1131 Ok(())
1132 }
1133
1134 #[async_test]
1135 async fn test_check_room_upgrades_loop() -> TestResult {
1136 let sender = user_id!("@mnt_io:matrix.org");
1137 let event_factory = EventFactory::new().sender(sender);
1138 let mut response_builder = SyncResponseBuilder::new();
1139 let room_id_0 = room_id!("!r0");
1140 let room_id_1 = room_id!("!r1");
1141 let room_id_2 = room_id!("!r2");
1142
1143 let client = logged_in_base_client(None).await;
1144
1145 {
1151 let response = response_builder
1152 .add_joined_room(
1153 JoinedRoomBuilder::new(room_id_0)
1154 .add_timeline_event(
1155 event_factory
1157 .create(sender, RoomVersionId::try_from("42")?)
1158 .predecessor(room_id_2),
1159 )
1160 .add_timeline_event(
1161 event_factory
1163 .room_tombstone("hello", room_id_1)
1164 .event_id(event_id!("$ev0")),
1165 ),
1166 )
1167 .add_joined_room(
1168 JoinedRoomBuilder::new(room_id_1)
1169 .add_timeline_event(
1170 event_factory
1172 .create(sender, RoomVersionId::try_from("43")?)
1173 .predecessor(room_id_0),
1174 )
1175 .add_timeline_event(
1176 event_factory
1178 .room_tombstone("hello", room_id_2)
1179 .event_id(event_id!("$ev1")),
1180 ),
1181 )
1182 .add_joined_room(
1183 JoinedRoomBuilder::new(room_id_2)
1184 .add_timeline_event(
1185 event_factory
1187 .create(sender, RoomVersionId::try_from("44")?)
1188 .predecessor(room_id_1),
1189 )
1190 .add_timeline_event(
1191 event_factory
1193 .room_tombstone("hello", room_id_0)
1194 .event_id(event_id!("$ev2")),
1195 ),
1196 )
1197 .build_sync_response();
1198
1199 assert!(client.receive_sync_response(response).await.is_ok());
1201
1202 let room_0 = client.get_room(room_id_0).unwrap();
1205
1206 assert_eq!(
1207 room_0.predecessor_room().expect("room 0 must have a predecessor").room_id,
1208 room_id_2,
1209 "room 0 does not have the expected predecessor"
1210 );
1211 assert_eq!(
1212 room_0.successor_room().expect("room 0 must have a successor").room_id,
1213 room_id_1,
1214 "room 0 does not have the expected successor",
1215 );
1216
1217 let room_1 = client.get_room(room_id_1).unwrap();
1218
1219 assert_eq!(
1220 room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
1221 room_id_0,
1222 "room 1 does not have the expected predecessor",
1223 );
1224 assert_eq!(
1225 room_1.successor_room().expect("room 1 must have a successor").room_id,
1226 room_id_2,
1227 "room 1 does not have the expected successor",
1228 );
1229
1230 let room_2 = client.get_room(room_id_2).unwrap();
1231
1232 assert!(room_2.predecessor_room().is_none(), "room 2 must not have a predecessor");
1234 assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
1235 assert_matches!(room_2.create_content(), Some(_), "room 2 must have a create content");
1236 }
1237
1238 Ok(())
1239 }
1240
1241 #[async_test]
1242 async fn test_state_events_after_sync() -> TestResult {
1243 let user_id = user_id!("@u:u.to");
1245
1246 let client = logged_in_base_client(Some(user_id)).await;
1247 let mut sync_builder = SyncResponseBuilder::new();
1248
1249 let event_factory = EventFactory::new().sender(user_id);
1250
1251 let room_name = event_factory
1252 .room_topic("this is the test topic in the timeline")
1253 .event_id(event_id!("$2"))
1254 .into_raw_sync();
1255
1256 let response = sync_builder
1257 .add_joined_room(
1258 JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID)
1259 .add_timeline_event(room_name)
1260 .add_state_event(event_factory.create(user_id, RoomVersionId::V1))
1261 .add_state_event(event_factory.default_power_levels()),
1262 )
1263 .build_sync_response();
1264 client.receive_sync_response(response).await?;
1265
1266 let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Just-created room not found!");
1267
1268 assert!(room.power_levels().await.is_ok());
1270
1271 assert_eq!(room.topic().unwrap(), "this is the test topic in the timeline");
1273
1274 Ok(())
1275 }
1276}