matrix_sdk_base/response_processors/
state_events.rs

1// Copyright 2025 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::BTreeSet;
16
17use ruma::{
18    RoomId,
19    events::{
20        AnySyncStateEvent, SyncStateEvent,
21        room::{create::RoomCreateEventContent, tombstone::RoomTombstoneEventContent},
22    },
23    serde::Raw,
24};
25use serde::Deserialize;
26use tracing::warn;
27
28use super::Context;
29use crate::store::BaseStateStore;
30
31/// Collect [`AnySyncStateEvent`].
32pub mod sync {
33    use std::{collections::BTreeSet, iter};
34
35    use ruma::{
36        OwnedUserId, RoomId, UserId,
37        events::{
38            AnySyncTimelineEvent, SyncStateEvent,
39            room::member::{MembershipState, RoomMemberEventContent},
40        },
41    };
42    use tracing::{error, instrument};
43
44    use super::{super::profiles, AnySyncStateEvent, Context, Raw};
45    #[cfg(feature = "experimental-encrypted-state-events")]
46    use crate::response_processors::e2ee;
47    use crate::{
48        RoomInfo,
49        store::{BaseStateStore, Result as StoreResult, ambiguity_map::AmbiguityCache},
50        sync::State,
51    };
52
53    impl State {
54        /// Collect all the state changes to update the local state, from this
55        /// [`State`] and from the given timeline, if necessary.
56        ///
57        /// The events that fail to deserialize are logged and filtered out.
58        pub(crate) fn collect(
59            &self,
60            timeline: &[Raw<AnySyncTimelineEvent>],
61        ) -> (Vec<Raw<AnySyncStateEvent>>, Vec<AnySyncStateEvent>) {
62            match self {
63                Self::Before(events) => {
64                    super::collect(events.iter().chain(timeline.iter().filter_map(|raw_event| {
65                        // Only state events have a `state_key` field.
66                        match raw_event.get_field::<&str>("state_key") {
67                            Ok(Some(_)) => Some(raw_event.cast_ref_unchecked()),
68                            _ => None,
69                        }
70                    })))
71                }
72                Self::After(events) => super::collect(events),
73            }
74        }
75    }
76
77    /// Dispatch the sync state events.
78    ///
79    /// `raw_events` and `events` must be generated from [`collect`].
80    /// Events must be exactly the same list of events that are in
81    /// `raw_events`, but deserialised. We demand them here to avoid
82    /// deserialising multiple times.
83    ///
84    /// The `new_users` mutable reference allows to collect the new users for
85    /// this room.
86    #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
87    pub async fn dispatch<U>(
88        context: &mut Context,
89        (raw_events, events): (&[Raw<AnySyncStateEvent>], &[AnySyncStateEvent]),
90        room_info: &mut RoomInfo,
91        ambiguity_cache: &mut AmbiguityCache,
92        new_users: &mut U,
93        state_store: &BaseStateStore,
94        #[cfg(feature = "experimental-encrypted-state-events")] e2ee: e2ee::E2EE<'_>,
95    ) -> StoreResult<()>
96    where
97        U: NewUsers,
98    {
99        for (raw_event, event) in iter::zip(raw_events, events) {
100            match event {
101                AnySyncStateEvent::RoomMember(member) => {
102                    room_info.handle_state_event(event);
103
104                    dispatch_room_member(
105                        context,
106                        &room_info.room_id,
107                        member,
108                        ambiguity_cache,
109                        new_users,
110                    )
111                    .await?;
112                }
113
114                AnySyncStateEvent::RoomCreate(create) => {
115                    let edited_create = super::validate_create_event_predecessor(
116                        context,
117                        room_info.room_id(),
118                        create,
119                        state_store,
120                    );
121
122                    room_info.handle_state_event(
123                        edited_create.map(Into::into).as_ref().unwrap_or(event),
124                    );
125                }
126
127                AnySyncStateEvent::RoomTombstone(tombstone) => {
128                    if super::is_tombstone_event_valid(
129                        context,
130                        room_info.room_id(),
131                        tombstone,
132                        state_store,
133                    ) {
134                        room_info.handle_state_event(event);
135                    } else {
136                        error!(
137                            room_id = ?room_info.room_id(),
138                            ?tombstone,
139                            "`m.room.tombstone` event is invalid, it creates a loop"
140                        );
141
142                        // Do not add the event to `room_info`.
143                        // Do not add the event to `context.state_changes.state`.
144                        continue;
145                    }
146                }
147
148                #[cfg(feature = "experimental-encrypted-state-events")]
149                AnySyncStateEvent::RoomEncrypted(SyncStateEvent::Original(outer)) => {
150                    use matrix_sdk_crypto::RoomEventDecryptionResult;
151                    use tracing::{trace, warn};
152
153                    trace!(event_id = ?outer.event_id, "Received encrypted state event, attempting decryption...");
154
155                    let Some(olm_machine) = e2ee.olm_machine else {
156                        continue;
157                    };
158
159                    let decrypted_event = olm_machine
160                        .try_decrypt_room_event(
161                            raw_event.cast_ref_unchecked(),
162                            &room_info.room_id,
163                            e2ee.decryption_settings,
164                        )
165                        .await
166                        .expect("OlmMachine was not started");
167
168                    // Skip state events that failed to decrypt.
169                    let RoomEventDecryptionResult::Decrypted(decrypted_event) = decrypted_event
170                    else {
171                        warn!(event_id = ?outer.event_id, "Failed to decrypt state event");
172                        continue;
173                    };
174
175                    // Cast to `AnySyncTimelineEvent`, safe since this is a supertype of
176                    // `AnyTimelineEvent`.
177                    let deserialized_event = match decrypted_event
178                        .event
179                        .deserialize_as::<AnySyncTimelineEvent>()
180                    {
181                        Ok(event) => event,
182                        Err(err) => {
183                            warn!(event_id = ?outer.event_id, "Failed to decrypt state event: {err}");
184                            continue;
185                        }
186                    };
187
188                    // Ensure decrypted event is actually a state event.
189                    let AnySyncTimelineEvent::State(event) = deserialized_event else {
190                        continue;
191                    };
192
193                    trace!(event_id = ?outer.event_id, "Decrypted state event successfully.");
194                    room_info.handle_state_event(&event);
195                }
196
197                _ => {
198                    room_info.handle_state_event(event);
199                }
200            }
201
202            context
203                .state_changes
204                .state
205                .entry(room_info.room_id.to_owned())
206                .or_default()
207                .entry(event.event_type())
208                .or_default()
209                .insert(event.state_key().to_owned(), raw_event.clone());
210        }
211
212        Ok(())
213    }
214
215    /// Dispatch a [`RoomMemberEventContent`] state event.
216    async fn dispatch_room_member<U>(
217        context: &mut Context,
218        room_id: &RoomId,
219        event: &SyncStateEvent<RoomMemberEventContent>,
220        ambiguity_cache: &mut AmbiguityCache,
221        new_users: &mut U,
222    ) -> StoreResult<()>
223    where
224        U: NewUsers,
225    {
226        ambiguity_cache.handle_event(&context.state_changes, room_id, event).await?;
227
228        match event.membership() {
229            MembershipState::Join | MembershipState::Invite => {
230                new_users.insert(event.state_key());
231            }
232            _ => (),
233        }
234
235        profiles::upsert_or_delete(context, room_id, event);
236
237        Ok(())
238    }
239
240    /// A trait to collect new users in [`dispatch`].
241    pub(crate) trait NewUsers {
242        /// Insert a new user in the collection of new users.
243        fn insert(&mut self, user_id: &UserId);
244    }
245
246    impl NewUsers for BTreeSet<OwnedUserId> {
247        fn insert(&mut self, user_id: &UserId) {
248            self.insert(user_id.to_owned());
249        }
250    }
251
252    impl NewUsers for () {
253        fn insert(&mut self, _user_id: &UserId) {}
254    }
255}
256
257/// Collect [`AnyStrippedStateEvent`].
258pub mod stripped {
259    use std::{collections::BTreeMap, iter};
260
261    use ruma::{events::AnyStrippedStateEvent, push::Action};
262    use tracing::instrument;
263
264    use super::{
265        super::{notification, timeline},
266        Context, Raw,
267    };
268    use crate::{Result, Room, RoomInfo};
269
270    /// Collect [`Raw<AnyStrippedStateEvent>`] to [`AnyStrippedStateEvent`].
271    pub fn collect(
272        raw_events: &[Raw<AnyStrippedStateEvent>],
273    ) -> (Vec<Raw<AnyStrippedStateEvent>>, Vec<AnyStrippedStateEvent>) {
274        super::collect(raw_events)
275    }
276
277    /// Dispatch the stripped state events.
278    ///
279    /// `raw_events` and `events` must be generated from [`collect`].
280    /// Events must be exactly the same list of events that are in
281    /// `raw_events`, but deserialised. We demand them here to avoid
282    /// deserialising multiple times.
283    ///
284    /// Dispatch the stripped state events in `invite_state` or `knock_state`,
285    /// modifying the room's info and posting notifications as needed.
286    ///
287    /// * `raw_events` and `events` - The contents of `invite_state` in the form
288    ///   of list of pairs of raw stripped state events with their deserialized
289    ///   counterpart.
290    /// * `room` - The [`Room`] to modify.
291    /// * `room_info` - The current room's info.
292    /// * `notifications` - Notifications to post for the current room.
293    #[instrument(skip_all, fields(room_id = ?room_info.room_id))]
294    pub(crate) async fn dispatch_invite_or_knock(
295        context: &mut Context,
296        (raw_events, events): (&[Raw<AnyStrippedStateEvent>], &[AnyStrippedStateEvent]),
297        room: &Room,
298        room_info: &mut RoomInfo,
299        mut notification: notification::Notification<'_>,
300    ) -> Result<()> {
301        let mut state_events = BTreeMap::new();
302
303        for (raw_event, event) in iter::zip(raw_events, events) {
304            room_info.handle_stripped_state_event(event);
305            state_events
306                .entry(event.event_type())
307                .or_insert_with(BTreeMap::new)
308                .insert(event.state_key().to_owned(), raw_event.clone());
309        }
310
311        context
312            .state_changes
313            .stripped_state
314            .insert(room_info.room_id().to_owned(), state_events.clone());
315
316        // We need to check for notifications after we have handled all state
317        // events, to make sure we have the full push context.
318        if let Some(push_condition_room_ctx) =
319            timeline::get_push_room_context(context, room, room_info).await?
320        {
321            // Check every event again for notification.
322            for event in state_events.values().flat_map(|map| map.values()) {
323                notification
324                    .push_notification_from_event_if(
325                        &push_condition_room_ctx,
326                        event,
327                        Action::should_notify,
328                    )
329                    .await;
330            }
331        }
332
333        Ok(())
334    }
335}
336
337fn collect<'a, I, T>(raw_events: I) -> (Vec<Raw<T>>, Vec<T>)
338where
339    I: IntoIterator<Item = &'a Raw<T>>,
340    T: Deserialize<'a> + 'a,
341{
342    raw_events
343        .into_iter()
344        .filter_map(|raw_event| match raw_event.deserialize() {
345            Ok(event) => Some((raw_event.clone(), event)),
346            Err(e) => {
347                warn!("Couldn't deserialize stripped state event: {e}");
348                None
349            }
350        })
351        .unzip()
352}
353
354/// Check if the `predecessor` in `m.room.create` isn't creating a loop of
355/// rooms.
356///
357/// If it is, we return a clone of the event with the predecessor removed.
358pub fn validate_create_event_predecessor(
359    context: &mut Context,
360    room_id: &RoomId,
361    event: &SyncStateEvent<RoomCreateEventContent>,
362    state_store: &BaseStateStore,
363) -> Option<SyncStateEvent<RoomCreateEventContent>> {
364    let mut already_seen = BTreeSet::new();
365    already_seen.insert(room_id.to_owned());
366
367    // Redacted and non-redacted create events use the same content type.
368    let content = match event {
369        SyncStateEvent::Original(event) => &event.content,
370        SyncStateEvent::Redacted(event) => &event.content,
371    };
372
373    let Some(mut predecessor_room_id) =
374        content.predecessor.as_ref().map(|predecessor| predecessor.room_id.clone())
375    else {
376        // No predecessor = no problem here.
377        return None;
378    };
379
380    loop {
381        // We must check immediately if the `predecessor_room_id` is in `already_seen`
382        // in case of a room is created and marks itself as its predecessor in a single
383        // sync.
384        if already_seen.contains(&predecessor_room_id) {
385            // Ahhh, there is a loop with `m.room.create` events!
386            // We remove the predecessor so that we don't process it later.
387            let mut event = event.clone();
388
389            match &mut event {
390                SyncStateEvent::Original(event) => event.content.predecessor.take(),
391                SyncStateEvent::Redacted(event) => event.content.predecessor.take(),
392            };
393
394            return Some(event);
395        }
396
397        already_seen.insert(predecessor_room_id.clone());
398
399        // Where is the predecessor room? Check in `room_infos` and then in
400        // `state_store`.
401        let Some(next_predecessor_room_id) = context
402            .state_changes
403            .room_infos
404            .get(&predecessor_room_id)
405            .and_then(|room_info| Some(room_info.create()?.predecessor.as_ref()?.room_id.clone()))
406            .or_else(|| {
407                state_store
408                    .room(&predecessor_room_id)
409                    .and_then(|room| Some(room.predecessor_room()?.room_id))
410            })
411        else {
412            // No more predecessor found. Everything seems alright. No loop.
413            break;
414        };
415
416        predecessor_room_id = next_predecessor_room_id;
417    }
418
419    None
420}
421
422/// Check if `m.room.tombstone` isn't creating a loop of rooms.
423pub fn is_tombstone_event_valid(
424    context: &mut Context,
425    room_id: &RoomId,
426    event: &SyncStateEvent<RoomTombstoneEventContent>,
427    state_store: &BaseStateStore,
428) -> bool {
429    let mut already_seen = BTreeSet::new();
430    already_seen.insert(room_id.to_owned());
431
432    let Some(mut successor_room_id) =
433        event.as_original().map(|event| event.content.replacement_room.clone())
434    else {
435        // `true` means no problem. No successor = no problem here.
436        return true;
437    };
438
439    loop {
440        // We must check immediately if the `successor_room_id` is in `already_seen` in
441        // case of a room is created and tombstones itself in a single sync.
442        if already_seen.contains(AsRef::<RoomId>::as_ref(&successor_room_id)) {
443            // Ahhh, there is a loop with `m.room.tombstone` events!
444            return false;
445        }
446
447        already_seen.insert(successor_room_id.clone());
448
449        // Where is the successor room? Check in `room_infos` and then in `state_store`.
450        let Some(next_successor_room_id) = context
451            .state_changes
452            .room_infos
453            .get(&successor_room_id)
454            .and_then(|room_info| Some(room_info.tombstone()?.replacement_room.clone()))
455            .or_else(|| {
456                state_store
457                    .room(&successor_room_id)
458                    .and_then(|room| Some(room.successor_room()?.room_id))
459            })
460        else {
461            // No more successor found. Everything seems alright. No loop.
462            break;
463        };
464
465        successor_room_id = next_successor_room_id;
466    }
467
468    true
469}
470
471#[cfg(test)]
472mod tests {
473    use assert_matches2::assert_matches;
474    use matrix_sdk_test::{
475        DEFAULT_TEST_ROOM_ID, JoinedRoomBuilder, StateTestEvent, SyncResponseBuilder, TestResult,
476        async_test, event_factory::EventFactory,
477    };
478    use ruma::{RoomVersionId, event_id, room_id, user_id};
479
480    use crate::test_utils::logged_in_base_client;
481
482    #[async_test]
483    async fn test_not_possible_to_overwrite_m_room_create() -> TestResult {
484        let sender = user_id!("@mnt_io:matrix.org");
485        let event_factory = EventFactory::new().sender(sender);
486        let mut response_builder = SyncResponseBuilder::new();
487        let room_id_0 = room_id!("!r0");
488        let room_id_1 = room_id!("!r1");
489        let room_id_2 = room_id!("!r2");
490
491        let client = logged_in_base_client(None).await;
492
493        // Create room 0 with 2 `m.room.create` events.
494        // Create room 1 with 1 `m.room.create` event.
495        // Create room 2 with 0 `m.room.create` event.
496        {
497            let response = response_builder
498                .add_joined_room(
499                    JoinedRoomBuilder::new(room_id_0)
500                        .add_timeline_event(
501                            event_factory.create(sender, RoomVersionId::try_from("42")?),
502                        )
503                        .add_timeline_event(
504                            event_factory.create(sender, RoomVersionId::try_from("43")?),
505                        ),
506                )
507                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
508                    event_factory.create(sender, RoomVersionId::try_from("44")?),
509                ))
510                .add_joined_room(JoinedRoomBuilder::new(room_id_2))
511                .build_sync_response();
512
513            assert!(client.receive_sync_response(response).await.is_ok());
514
515            // Room 0
516            // the second `m.room.create` has been ignored!
517            assert_eq!(
518                client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
519                "42"
520            );
521            // Room 1
522            assert_eq!(
523                client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
524                "44"
525            );
526            // Room 2
527            assert!(client.get_room(room_id_2).unwrap().create_content().is_none());
528        }
529
530        // Room 0 receives a new `m.room.create` event.
531        // Room 1 receives a new `m.room.create` event.
532        // Room 2 receives its first `m.room.create` event.
533        {
534            let response = response_builder
535                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
536                    event_factory.create(sender, RoomVersionId::try_from("45")?),
537                ))
538                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
539                    event_factory.create(sender, RoomVersionId::try_from("46")?),
540                ))
541                .add_joined_room(JoinedRoomBuilder::new(room_id_2).add_timeline_event(
542                    event_factory.create(sender, RoomVersionId::try_from("47")?),
543                ))
544                .build_sync_response();
545
546            assert!(client.receive_sync_response(response).await.is_ok());
547
548            // Room 0
549            // the third `m.room.create` has been ignored!
550            assert_eq!(
551                client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
552                "42"
553            );
554            // Room 1
555            // the second `m.room.create` has been ignored!
556            assert_eq!(
557                client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
558                "44"
559            );
560            // Room 2
561            assert_eq!(
562                client.get_room(room_id_2).unwrap().create_content().unwrap().room_version.as_str(),
563                "47"
564            );
565        }
566
567        Ok(())
568    }
569
570    #[async_test]
571    async fn test_check_room_upgrades_no_newly_tombstoned_rooms() {
572        let client = logged_in_base_client(None).await;
573
574        // Create a new room, no tombstone, no anything.
575        {
576            let response = SyncResponseBuilder::new()
577                .add_joined_room(JoinedRoomBuilder::new(room_id!("!r0")))
578                .build_sync_response();
579
580            assert!(client.receive_sync_response(response).await.is_ok());
581        }
582    }
583
584    #[async_test]
585    async fn test_check_room_upgrades_no_error() -> TestResult {
586        let sender = user_id!("@mnt_io:matrix.org");
587        let event_factory = EventFactory::new().sender(sender);
588        let mut response_builder = SyncResponseBuilder::new();
589        let room_id_0 = room_id!("!r0");
590        let room_id_1 = room_id!("!r1");
591        let room_id_2 = room_id!("!r2");
592
593        let client = logged_in_base_client(None).await;
594
595        // Room 0.
596        {
597            let response = response_builder
598                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
599                    // Room 0 has no predecessor.
600                    event_factory.create(sender, RoomVersionId::try_from("41")?),
601                ))
602                .build_sync_response();
603
604            assert!(client.receive_sync_response(response).await.is_ok());
605
606            let room_0 = client.get_room(room_id_0).unwrap();
607
608            assert!(room_0.predecessor_room().is_none());
609            assert!(room_0.successor_room().is_none());
610        }
611
612        // Room 0 and room 1.
613        {
614            let tombstone_event_id = event_id!("$ev0");
615            let response = response_builder
616                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
617                    // Successor of room 0 is room 1.
618                    event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
619                ))
620                .add_joined_room(
621                    JoinedRoomBuilder::new(room_id_1).add_timeline_event(
622                        // Predecessor of room 1 is room 0.
623                        event_factory
624                            .create(sender, RoomVersionId::try_from("42")?)
625                            .predecessor(room_id_0),
626                    ),
627                )
628                .build_sync_response();
629
630            assert!(client.receive_sync_response(response).await.is_ok());
631
632            let room_0 = client.get_room(room_id_0).unwrap();
633
634            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
635            assert_eq!(
636                room_0.successor_room().expect("room 0 must have a successor").room_id,
637                room_id_1,
638                "room 0 does not have the expected successor",
639            );
640
641            let room_1 = client.get_room(room_id_1).unwrap();
642
643            assert_eq!(
644                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
645                room_id_0,
646                "room 1 does not have the expected predecessor",
647            );
648            assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
649        }
650
651        // Room 1 and room 2.
652        {
653            let tombstone_event_id = event_id!("$ev1");
654            let response = response_builder
655                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
656                    // Successor of room 1 is room 2.
657                    event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
658                ))
659                .add_joined_room(
660                    JoinedRoomBuilder::new(room_id_2).add_timeline_event(
661                        // Predecessor of room 2 is room 1.
662                        event_factory
663                            .create(sender, RoomVersionId::try_from("43")?)
664                            .predecessor(room_id_1),
665                    ),
666                )
667                .build_sync_response();
668
669            assert!(client.receive_sync_response(response).await.is_ok());
670
671            let room_1 = client.get_room(room_id_1).unwrap();
672
673            assert_eq!(
674                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
675                room_id_0,
676                "room 1 does not have the expected predecessor",
677            );
678            assert_eq!(
679                room_1.successor_room().expect("room 1 must have a successor").room_id,
680                room_id_2,
681                "room 1 does not have the expected successor",
682            );
683
684            let room_2 = client.get_room(room_id_2).unwrap();
685
686            assert_eq!(
687                room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
688                room_id_1,
689                "room 2 does not have the expected predecessor",
690            );
691            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
692        }
693
694        Ok(())
695    }
696
697    #[async_test]
698    async fn test_check_room_upgrades_no_loop_within_misordered_rooms() -> TestResult {
699        let sender = user_id!("@mnt_io:matrix.org");
700        let event_factory = EventFactory::new().sender(sender);
701        let mut response_builder = SyncResponseBuilder::new();
702        // The room IDs are important because `SyncResponseBuilder` stores them in a
703        // `HashMap`, so they are going to be “shuffled”.
704        let room_id_0 = room_id!("!r1");
705        let room_id_1 = room_id!("!r0");
706        let room_id_2 = room_id!("!r2");
707
708        let client = logged_in_base_client(None).await;
709
710        // Create all rooms in a misordered way to see if `check_tombstone` will
711        // re-order them appropriately.
712        {
713            let response = response_builder
714                // Room 0
715                .add_joined_room(
716                    JoinedRoomBuilder::new(room_id_0)
717                        .add_timeline_event(
718                            // No predecessor for room 0.
719                            event_factory.create(sender, RoomVersionId::try_from("41")?),
720                        )
721                        .add_timeline_event(
722                            // Successor of room 0 is room 1.
723                            event_factory
724                                .room_tombstone("hello", room_id_1)
725                                .event_id(event_id!("$ev0")),
726                        ),
727                )
728                // Room 1
729                .add_joined_room(
730                    JoinedRoomBuilder::new(room_id_1)
731                        .add_timeline_event(
732                            // Predecessor of room 1 is room 0.
733                            event_factory
734                                .create(sender, RoomVersionId::try_from("42")?)
735                                .predecessor(room_id_0),
736                        )
737                        .add_timeline_event(
738                            // Successor of room 1 is room 2.
739                            event_factory
740                                .room_tombstone("hello", room_id_2)
741                                .event_id(event_id!("$ev1")),
742                        ),
743                )
744                // Room 2
745                .add_joined_room(
746                    JoinedRoomBuilder::new(room_id_2).add_timeline_event(
747                        // Predecessor of room 2 is room 1.
748                        event_factory
749                            .create(sender, RoomVersionId::try_from("43")?)
750                            .predecessor(room_id_1),
751                    ),
752                )
753                .build_sync_response();
754
755            // At this point, we can check that `response` contains misordered room updates.
756            {
757                let mut rooms = response.rooms.join.keys();
758
759                // Room 1 is before room 0!
760                assert_eq!(rooms.next().unwrap(), room_id_1);
761                assert_eq!(rooms.next().unwrap(), room_id_0);
762                assert_eq!(rooms.next().unwrap(), room_id_2);
763                assert!(rooms.next().is_none());
764            }
765
766            // But the algorithm to detect invalid states works nicely.
767            assert!(client.receive_sync_response(response).await.is_ok());
768
769            let room_0 = client.get_room(room_id_0).unwrap();
770
771            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
772            assert_eq!(
773                room_0.successor_room().expect("room 0 must have a successor").room_id,
774                room_id_1,
775                "room 0 does not have the expected successor",
776            );
777
778            let room_1 = client.get_room(room_id_1).unwrap();
779
780            assert_eq!(
781                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
782                room_id_0,
783                "room 1 does not have the expected predecessor",
784            );
785            assert_eq!(
786                room_1.successor_room().expect("room 1 must have a successor").room_id,
787                room_id_2,
788                "room 1 does not have the expected successor",
789            );
790
791            let room_2 = client.get_room(room_id_2).unwrap();
792
793            assert_eq!(
794                room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
795                room_id_1,
796                "room 2 does not have the expected predecessor",
797            );
798            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor");
799        }
800
801        Ok(())
802    }
803
804    #[async_test]
805    async fn test_check_room_upgrades_shortest_invalid_successor() -> TestResult {
806        let sender = user_id!("@mnt_io:matrix.org");
807        let event_factory = EventFactory::new().sender(sender);
808        let mut response_builder = SyncResponseBuilder::new();
809        let room_id_0 = room_id!("!r0");
810
811        let client = logged_in_base_client(None).await;
812
813        // Room 0.
814        {
815            let tombstone_event_id = event_id!("$ev0");
816            let response = response_builder
817                .add_joined_room(
818                    // Successor of room 0 is room 0.
819                    // No predecessor.
820                    JoinedRoomBuilder::new(room_id_0).add_timeline_event(
821                        event_factory
822                            .room_tombstone("hello", room_id_0)
823                            .event_id(tombstone_event_id),
824                    ),
825                )
826                .build_sync_response();
827
828            // The sync doesn't fail but…
829            assert!(client.receive_sync_response(response).await.is_ok());
830
831            // … the state event has not been saved.
832            let room_0 = client.get_room(room_id_0).unwrap();
833
834            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
835            assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
836        }
837
838        Ok(())
839    }
840
841    #[async_test]
842    async fn test_check_room_upgrades_invalid_successor() -> TestResult {
843        let sender = user_id!("@mnt_io:matrix.org");
844        let event_factory = EventFactory::new().sender(sender);
845        let mut response_builder = SyncResponseBuilder::new();
846        let room_id_0 = room_id!("!r0");
847        let room_id_1 = room_id!("!r1");
848        let room_id_2 = room_id!("!r2");
849
850        let client = logged_in_base_client(None).await;
851
852        // Room 0 and room 1.
853        {
854            let tombstone_event_id = event_id!("$ev0");
855            let response = response_builder
856                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
857                    // Successor of room 0 is room 1.
858                    event_factory.room_tombstone("hello", room_id_1).event_id(tombstone_event_id),
859                ))
860                .add_joined_room(
861                    JoinedRoomBuilder::new(room_id_1).add_timeline_event(
862                        // Predecessor of room 1 is room 0.
863                        event_factory
864                            .create(sender, RoomVersionId::try_from("42")?)
865                            .predecessor(room_id_0),
866                    ),
867                )
868                .build_sync_response();
869
870            assert!(client.receive_sync_response(response).await.is_ok());
871
872            let room_0 = client.get_room(room_id_0).unwrap();
873
874            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
875            assert_eq!(
876                room_0.successor_room().expect("room 0 must have a successor").room_id,
877                room_id_1,
878                "room 0 does not have the expected successor",
879            );
880
881            let room_1 = client.get_room(room_id_1).unwrap();
882
883            assert_eq!(
884                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
885                room_id_0,
886                "room 1 does not have the expected predecessor",
887            );
888            assert!(room_1.successor_room().is_none(), "room 1 must not have a successor");
889        }
890
891        // Room 1, room 2 and room 0.
892        {
893            let tombstone_event_id = event_id!("$ev1");
894            let response = response_builder
895                .add_joined_room(JoinedRoomBuilder::new(room_id_1).add_timeline_event(
896                    // Successor of room 1 is room 2.
897                    event_factory.room_tombstone("hello", room_id_2).event_id(tombstone_event_id),
898                ))
899                .add_joined_room(
900                    JoinedRoomBuilder::new(room_id_2)
901                        .add_timeline_event(
902                            // Predecessor of room 2 is room 1.
903                            event_factory
904                                .create(sender, RoomVersionId::try_from("43")?)
905                                .predecessor(room_id_1),
906                        )
907                        .add_timeline_event(
908                            // Successor of room 2 is room 0.
909                            event_factory
910                                .room_tombstone("hehe", room_id_0)
911                                .event_id(event_id!("$ev_foo")),
912                        ),
913                )
914                .build_sync_response();
915
916            // The sync doesn't fail but…
917            assert!(client.receive_sync_response(response).await.is_ok());
918
919            // … the state event for `room_id_2` has not been saved.
920            let room_0 = client.get_room(room_id_0).unwrap();
921
922            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
923            assert_eq!(
924                room_0.successor_room().expect("room 0 must have a successor").room_id,
925                room_id_1,
926                "room 0 does not have the expected successor",
927            );
928
929            let room_1 = client.get_room(room_id_1).unwrap();
930
931            assert_eq!(
932                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
933                room_id_0,
934                "room 1 does not have the expected predecessor",
935            );
936            assert_eq!(
937                room_1.successor_room().expect("room 1 must have a successor").room_id,
938                room_id_2,
939                "room 1 does not have the expected successor",
940            );
941
942            let room_2 = client.get_room(room_id_2).unwrap();
943
944            assert_eq!(
945                room_2.predecessor_room().expect("room 2 must have a predecessor").room_id,
946                room_id_1,
947                "room 2 does not have the expected predecessor",
948            );
949            // this state event is missing because it creates a loop
950            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
951        }
952
953        Ok(())
954    }
955
956    #[async_test]
957    async fn test_check_room_upgrades_shortest_invalid_predecessor() -> TestResult {
958        let sender = user_id!("@mnt_io:matrix.org");
959        let event_factory = EventFactory::new().sender(sender);
960        let mut response_builder = SyncResponseBuilder::new();
961        let room_id_0 = room_id!("!r0");
962
963        let client = logged_in_base_client(None).await;
964
965        // Room 0.
966        {
967            let tombstone_event_id = event_id!("$ev0");
968            let response = response_builder
969                .add_joined_room(
970                    // Predecessor of room 0 is room 0.
971                    // No successor.
972                    JoinedRoomBuilder::new(room_id_0).add_timeline_event(
973                        event_factory
974                            .create(sender, RoomVersionId::try_from("42")?)
975                            .predecessor(room_id_0)
976                            .event_id(tombstone_event_id),
977                    ),
978                )
979                .build_sync_response();
980
981            // The sync doesn't fail but…
982            assert!(client.receive_sync_response(response).await.is_ok());
983
984            // … the predecessor has not been saved.
985            let room_0 = client.get_room(room_id_0).unwrap();
986
987            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
988            assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
989            assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
990        }
991
992        Ok(())
993    }
994
995    #[async_test]
996    async fn test_check_room_upgrades_shortest_loop() -> TestResult {
997        let sender = user_id!("@mnt_io:matrix.org");
998        let event_factory = EventFactory::new().sender(sender);
999        let mut response_builder = SyncResponseBuilder::new();
1000        let room_id_0 = room_id!("!r0");
1001
1002        let client = logged_in_base_client(None).await;
1003
1004        // Room 0.
1005        {
1006            let tombstone_event_id = event_id!("$ev0");
1007            let response = response_builder
1008                .add_joined_room(
1009                    JoinedRoomBuilder::new(room_id_0)
1010                        .add_timeline_event(
1011                            // Successor of room 0 is room 0
1012                            event_factory
1013                                .room_tombstone("hello", room_id_0)
1014                                .event_id(tombstone_event_id),
1015                        )
1016                        .add_timeline_event(
1017                            // Predecessor of room 0 is room 0
1018                            event_factory
1019                                .create(sender, RoomVersionId::try_from("42")?)
1020                                .predecessor(room_id_0),
1021                        ),
1022                )
1023                .build_sync_response();
1024
1025            // The sync doesn't fail but…
1026            assert!(client.receive_sync_response(response).await.is_ok());
1027
1028            // … the tombstone event and the predecessor have not been saved.
1029            let room_0 = client.get_room(room_id_0).unwrap();
1030
1031            assert!(room_0.predecessor_room().is_none(), "room 0 must not have a predecessor");
1032            assert!(room_0.successor_room().is_none(), "room 0 must not have a successor");
1033            assert_matches!(room_0.create_content(), Some(_), "room 0 must have a create content");
1034        }
1035
1036        Ok(())
1037    }
1038
1039    #[async_test]
1040    async fn test_check_room_upgrades_loop() -> TestResult {
1041        let sender = user_id!("@mnt_io:matrix.org");
1042        let event_factory = EventFactory::new().sender(sender);
1043        let mut response_builder = SyncResponseBuilder::new();
1044        let room_id_0 = room_id!("!r0");
1045        let room_id_1 = room_id!("!r1");
1046        let room_id_2 = room_id!("!r2");
1047
1048        let client = logged_in_base_client(None).await;
1049
1050        // Room 0, room 1 and room 2.
1051        //
1052        // Doing that in one sync, it's the only way to create such loop (otherwise it
1053        // implies overwriting the `m.room.create` event, or not setting it first, then
1054        // setting it later… anyway, it works in one sync)
1055        {
1056            let response = response_builder
1057                .add_joined_room(
1058                    JoinedRoomBuilder::new(room_id_0)
1059                        .add_timeline_event(
1060                            // Predecessor of room 0 is room 2
1061                            event_factory
1062                                .create(sender, RoomVersionId::try_from("42")?)
1063                                .predecessor(room_id_2),
1064                        )
1065                        .add_timeline_event(
1066                            // Successor of room 0 is room 1
1067                            event_factory
1068                                .room_tombstone("hello", room_id_1)
1069                                .event_id(event_id!("$ev0")),
1070                        ),
1071                )
1072                .add_joined_room(
1073                    JoinedRoomBuilder::new(room_id_1)
1074                        .add_timeline_event(
1075                            // Predecessor of room 1 is room 0
1076                            event_factory
1077                                .create(sender, RoomVersionId::try_from("43")?)
1078                                .predecessor(room_id_0),
1079                        )
1080                        .add_timeline_event(
1081                            // Successor of room 1 is room 2
1082                            event_factory
1083                                .room_tombstone("hello", room_id_2)
1084                                .event_id(event_id!("$ev1")),
1085                        ),
1086                )
1087                .add_joined_room(
1088                    JoinedRoomBuilder::new(room_id_2)
1089                        .add_timeline_event(
1090                            // Predecessor of room 2 is room 1
1091                            event_factory
1092                                .create(sender, RoomVersionId::try_from("44")?)
1093                                .predecessor(room_id_1),
1094                        )
1095                        .add_timeline_event(
1096                            // Successor of room 2 is room 0
1097                            event_factory
1098                                .room_tombstone("hello", room_id_0)
1099                                .event_id(event_id!("$ev2")),
1100                        ),
1101                )
1102                .build_sync_response();
1103
1104            // The sync doesn't fail but…
1105            assert!(client.receive_sync_response(response).await.is_ok());
1106
1107            // … the state event for room 2 -> room 0 has not been saved, but room 0 <- room
1108            // 2 has been saved.
1109            let room_0 = client.get_room(room_id_0).unwrap();
1110
1111            assert_eq!(
1112                room_0.predecessor_room().expect("room 0 must have a predecessor").room_id,
1113                room_id_2,
1114                "room 0 does not have the expected predecessor"
1115            );
1116            assert_eq!(
1117                room_0.successor_room().expect("room 0 must have a successor").room_id,
1118                room_id_1,
1119                "room 0 does not have the expected successor",
1120            );
1121
1122            let room_1 = client.get_room(room_id_1).unwrap();
1123
1124            assert_eq!(
1125                room_1.predecessor_room().expect("room 1 must have a predecessor").room_id,
1126                room_id_0,
1127                "room 1 does not have the expected predecessor",
1128            );
1129            assert_eq!(
1130                room_1.successor_room().expect("room 1 must have a successor").room_id,
1131                room_id_2,
1132                "room 1 does not have the expected successor",
1133            );
1134
1135            let room_2 = client.get_room(room_id_2).unwrap();
1136
1137            // this state event is missing because it creates a loop
1138            assert!(room_2.predecessor_room().is_none(), "room 2 must not have a predecessor");
1139            assert!(room_2.successor_room().is_none(), "room 2 must not have a successor",);
1140            assert_matches!(room_2.create_content(), Some(_), "room 2 must have a create content");
1141        }
1142
1143        Ok(())
1144    }
1145
1146    #[async_test]
1147    async fn test_state_events_after_sync() -> TestResult {
1148        // Given a room
1149        let user_id = user_id!("@u:u.to");
1150
1151        let client = logged_in_base_client(Some(user_id)).await;
1152        let mut sync_builder = SyncResponseBuilder::new();
1153
1154        let room_name = EventFactory::new()
1155            .sender(user_id)
1156            .room_topic("this is the test topic in the timeline")
1157            .event_id(event_id!("$2"))
1158            .into_raw_sync();
1159
1160        let response = sync_builder
1161            .add_joined_room(
1162                JoinedRoomBuilder::new(&DEFAULT_TEST_ROOM_ID)
1163                    .add_timeline_event(room_name)
1164                    .add_state_event(StateTestEvent::Create)
1165                    .add_state_event(StateTestEvent::PowerLevels),
1166            )
1167            .build_sync_response();
1168        client.receive_sync_response(response).await?;
1169
1170        let room = client.get_room(&DEFAULT_TEST_ROOM_ID).expect("Just-created room not found!");
1171
1172        // ensure that we have the power levels
1173        assert!(room.power_levels().await.is_ok());
1174
1175        // ensure that we have the topic
1176        assert_eq!(room.topic().unwrap(), "this is the test topic in the timeline");
1177
1178        Ok(())
1179    }
1180}