Skip to main content

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 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
30/// Collect [`AnySyncStateEvent`].
31pub 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        /// 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<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    /// Dispatch the sync state events.
83    ///
84    /// `raw_events` and `events` must be generated from [`collect`].
85    /// Events must be exactly the same list of events that are in
86    /// `raw_events`, but deserialised. We demand them here to avoid
87    /// deserialising multiple times.
88    ///
89    /// The `new_users` mutable reference allows to collect the new users for
90    /// this room.
91    #[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                        // Do not add the event to `room_info`.
140                        // Do not add the event to `context.state_changes.state`.
141                        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    /// Dispatch a [`RoomMemberEventContent`] state event.
175    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    /// A trait to collect new users in [`dispatch`].
206    pub(crate) trait NewUsers {
207        /// Insert a new user in the collection of new users.
208        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    /// Find any `m.room.member` events that refer to the current user, and emit
222    /// a membership update accordingly, plus update the state in `room_info` to
223    /// reflect the "membership" property.
224    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        // Start from the last event; the first membership event we see in that order is
231        // the last in the regular order, so that's the only one we need to
232        // consider.
233        if let Some(member) = state_events.iter_mut().rev().find_map(|event| {
234            // Find the event that updates the current user's membership.
235            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                // Update an existing notable update entry or create a new one
252                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
262/// Collect [`AnyStrippedStateEvent`].
263pub 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    /// Collect [`Raw<AnyStrippedStateEvent>`] to
280    /// [`RawStateEventWithKeys<AnyStrippedStateEvent>`].
281    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    /// Dispatch the stripped state events.
292    ///
293    /// `raw_events` and `events` must be generated from [`collect`].
294    /// Events must be exactly the same list of events that are in
295    /// `raw_events`, but deserialised. We demand them here to avoid
296    /// deserialising multiple times.
297    ///
298    /// Dispatch the stripped state events in `invite_state` or `knock_state`,
299    /// modifying the room's info and posting notifications as needed.
300    ///
301    /// * `raw_events` and `events` - The contents of `invite_state` in the form
302    ///   of list of pairs of raw stripped state events with their deserialized
303    ///   counterpart.
304    /// * `room` - The [`Room`] to modify.
305    /// * `room_info` - The current room's info.
306    /// * `notifications` - Notifications to post for the current room.
307    #[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        // We need to check for notifications after we have handled all state
335        // events, to make sure we have the full push context.
336        if let Some(push_condition_room_ctx) =
337            timeline::get_push_room_context(context, room, room_info).await?
338        {
339            // Check every event again for notification.
340            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    /// Find any `m.room.member` events that refer to the current user, and emit
355    /// a membership update accordingly.
356    pub fn own_membership(
357        context: &mut Context,
358        room_id: &RoomId,
359        user_id: &UserId,
360        raw_state_events: &[RawStateEventWithKeys<AnyStrippedStateEvent>],
361    ) {
362        // Start from the last event; the first membership event we see in that order is
363        // the last in the regular order, so that's the only one we need to
364        // consider.
365        if raw_state_events.iter().rev().any(|raw_event| {
366            // Find the event that updates the current user's membership.
367            raw_event.event_type == StateEventType::RoomMember
368                && raw_event.state_key == user_id.as_str()
369        }) {
370            // Update an existing notable update entry or create a new one
371            context
372                .room_info_notable_updates
373                .entry(room_id.to_owned())
374                .or_default()
375                .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
376        }
377    }
378}
379
380/// Check if the `predecessor` in `m.room.create` isn't creating a loop of
381/// rooms.
382///
383/// If it is, we edit the cached event in `raw_event` to remove the predecessor.
384pub 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    // Redacted and non-redacted create events use the same content type.
400    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        // No predecessor = no problem here.
409        return;
410    };
411
412    loop {
413        // We must check immediately if the `predecessor_room_id` is in `already_seen`
414        // in case of a room is created and marks itself as its predecessor in a single
415        // sync.
416        if already_seen.contains(&predecessor_room_id) {
417            // Ahhh, there is a loop with `m.room.create` events!
418            // We remove the predecessor so that we don't process it later.
419            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        // Where is the predecessor room? Check in `room_infos` and then in
434        // `state_store`.
435        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            // No more predecessor found. Everything seems alright. No loop.
447            break;
448        };
449
450        predecessor_room_id = next_predecessor_room_id;
451    }
452}
453
454/// Check if `m.room.tombstone` isn't creating a loop of rooms.
455pub 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        // `true` means no problem. No successor = no problem here.
469        return true;
470    };
471
472    let mut successor_room_id = tombstone.replacement_room.clone();
473
474    loop {
475        // We must check immediately if the `successor_room_id` is in `already_seen` in
476        // case of a room is created and tombstones itself in a single sync.
477        if already_seen.contains(AsRef::<RoomId>::as_ref(&successor_room_id)) {
478            // Ahhh, there is a loop with `m.room.tombstone` events!
479            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        // Where is the successor room? Check in `room_infos` and then in `state_store`.
486        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            // No more successor found. Everything seems alright. No loop.
498            break;
499        };
500
501        successor_room_id = next_successor_room_id;
502    }
503
504    true
505}
506
507/// Attempt to decrypt the given state event.
508///
509/// Returns `Some(_)` if the state event was successfully decrypted and
510/// its keys were deserialized.
511#[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    // Skip state events that failed to decrypt.
547    let RoomEventDecryptionResult::Decrypted(decrypted_event) = decrypted_event else {
548        warn!(?event_id, "Failed to decrypt state event");
549        return None;
550    };
551
552    // Cast to `AnySync*Event`, safe since this is a supertype of
553    // `AnyTimelineEvent`.
554    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        // Create room 0 with 2 `m.room.create` events.
589        // Create room 1 with 1 `m.room.create` event.
590        // Create room 2 with 0 `m.room.create` event.
591        {
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            // Room 0
611            // the second `m.room.create` has been ignored!
612            assert_eq!(
613                client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
614                "42"
615            );
616            // Room 1
617            assert_eq!(
618                client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
619                "44"
620            );
621            // Room 2
622            assert!(client.get_room(room_id_2).unwrap().create_content().is_none());
623        }
624
625        // Room 0 receives a new `m.room.create` event.
626        // Room 1 receives a new `m.room.create` event.
627        // Room 2 receives its first `m.room.create` event.
628        {
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            // Room 0
644            // the third `m.room.create` has been ignored!
645            assert_eq!(
646                client.get_room(room_id_0).unwrap().create_content().unwrap().room_version.as_str(),
647                "42"
648            );
649            // Room 1
650            // the second `m.room.create` has been ignored!
651            assert_eq!(
652                client.get_room(room_id_1).unwrap().create_content().unwrap().room_version.as_str(),
653                "44"
654            );
655            // Room 2
656            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        // Create a new room, no tombstone, no anything.
670        {
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        // Room 0.
691        {
692            let response = response_builder
693                .add_joined_room(JoinedRoomBuilder::new(room_id_0).add_timeline_event(
694                    // Room 0 has no predecessor.
695                    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        // Room 0 and room 1.
708        {
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                    // Successor of room 0 is room 1.
713                    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                        // Predecessor of room 1 is room 0.
718                        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        // Room 1 and room 2.
747        {
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                    // Successor of room 1 is room 2.
752                    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                        // Predecessor of room 2 is room 1.
757                        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        // The room IDs are important because `SyncResponseBuilder` stores them in a
798        // `HashMap`, so they are going to be “shuffled”.
799        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        // Create all rooms in a misordered way to see if `check_tombstone` will
806        // re-order them appropriately.
807        {
808            let response = response_builder
809                // Room 0
810                .add_joined_room(
811                    JoinedRoomBuilder::new(room_id_0)
812                        .add_timeline_event(
813                            // No predecessor for room 0.
814                            event_factory.create(sender, RoomVersionId::try_from("41")?),
815                        )
816                        .add_timeline_event(
817                            // Successor of room 0 is room 1.
818                            event_factory
819                                .room_tombstone("hello", room_id_1)
820                                .event_id(event_id!("$ev0")),
821                        ),
822                )
823                // Room 1
824                .add_joined_room(
825                    JoinedRoomBuilder::new(room_id_1)
826                        .add_timeline_event(
827                            // Predecessor of room 1 is room 0.
828                            event_factory
829                                .create(sender, RoomVersionId::try_from("42")?)
830                                .predecessor(room_id_0),
831                        )
832                        .add_timeline_event(
833                            // Successor of room 1 is room 2.
834                            event_factory
835                                .room_tombstone("hello", room_id_2)
836                                .event_id(event_id!("$ev1")),
837                        ),
838                )
839                // Room 2
840                .add_joined_room(
841                    JoinedRoomBuilder::new(room_id_2).add_timeline_event(
842                        // Predecessor of room 2 is room 1.
843                        event_factory
844                            .create(sender, RoomVersionId::try_from("43")?)
845                            .predecessor(room_id_1),
846                    ),
847                )
848                .build_sync_response();
849
850            // At this point, we can check that `response` contains misordered room updates.
851            {
852                let mut rooms = response.rooms.join.keys();
853
854                // Room 1 is before room 0!
855                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            // But the algorithm to detect invalid states works nicely.
862            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        // Room 0.
909        {
910            let tombstone_event_id = event_id!("$ev0");
911            let response = response_builder
912                .add_joined_room(
913                    // Successor of room 0 is room 0.
914                    // No predecessor.
915                    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            // The sync doesn't fail but…
924            assert!(client.receive_sync_response(response).await.is_ok());
925
926            // … the state event has not been saved.
927            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        // Room 0 and room 1.
948        {
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                    // Successor of room 0 is room 1.
953                    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                        // Predecessor of room 1 is room 0.
958                        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        // Room 1, room 2 and room 0.
987        {
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                    // Successor of room 1 is room 2.
992                    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                            // Predecessor of room 2 is room 1.
998                            event_factory
999                                .create(sender, RoomVersionId::try_from("43")?)
1000                                .predecessor(room_id_1),
1001                        )
1002                        .add_timeline_event(
1003                            // Successor of room 2 is room 0.
1004                            event_factory
1005                                .room_tombstone("hehe", room_id_0)
1006                                .event_id(event_id!("$ev_foo")),
1007                        ),
1008                )
1009                .build_sync_response();
1010
1011            // The sync doesn't fail but…
1012            assert!(client.receive_sync_response(response).await.is_ok());
1013
1014            // … the state event for `room_id_2` has not been saved.
1015            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            // this state event is missing because it creates a loop
1045            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        // Room 0.
1061        {
1062            let tombstone_event_id = event_id!("$ev0");
1063            let response = response_builder
1064                .add_joined_room(
1065                    // Predecessor of room 0 is room 0.
1066                    // No successor.
1067                    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            // The sync doesn't fail but…
1077            assert!(client.receive_sync_response(response).await.is_ok());
1078
1079            // … the predecessor has not been saved.
1080            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        // Room 0.
1100        {
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                            // Successor of room 0 is room 0
1107                            event_factory
1108                                .room_tombstone("hello", room_id_0)
1109                                .event_id(tombstone_event_id),
1110                        )
1111                        .add_timeline_event(
1112                            // Predecessor of room 0 is room 0
1113                            event_factory
1114                                .create(sender, RoomVersionId::try_from("42")?)
1115                                .predecessor(room_id_0),
1116                        ),
1117                )
1118                .build_sync_response();
1119
1120            // The sync doesn't fail but…
1121            assert!(client.receive_sync_response(response).await.is_ok());
1122
1123            // … the tombstone event and the predecessor have not been saved.
1124            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        // Room 0, room 1 and room 2.
1146        //
1147        // Doing that in one sync, it's the only way to create such loop (otherwise it
1148        // implies overwriting the `m.room.create` event, or not setting it first, then
1149        // setting it later… anyway, it works in one sync)
1150        {
1151            let response = response_builder
1152                .add_joined_room(
1153                    JoinedRoomBuilder::new(room_id_0)
1154                        .add_timeline_event(
1155                            // Predecessor of room 0 is room 2
1156                            event_factory
1157                                .create(sender, RoomVersionId::try_from("42")?)
1158                                .predecessor(room_id_2),
1159                        )
1160                        .add_timeline_event(
1161                            // Successor of room 0 is room 1
1162                            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                            // Predecessor of room 1 is room 0
1171                            event_factory
1172                                .create(sender, RoomVersionId::try_from("43")?)
1173                                .predecessor(room_id_0),
1174                        )
1175                        .add_timeline_event(
1176                            // Successor of room 1 is room 2
1177                            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                            // Predecessor of room 2 is room 1
1186                            event_factory
1187                                .create(sender, RoomVersionId::try_from("44")?)
1188                                .predecessor(room_id_1),
1189                        )
1190                        .add_timeline_event(
1191                            // Successor of room 2 is room 0
1192                            event_factory
1193                                .room_tombstone("hello", room_id_0)
1194                                .event_id(event_id!("$ev2")),
1195                        ),
1196                )
1197                .build_sync_response();
1198
1199            // The sync doesn't fail but…
1200            assert!(client.receive_sync_response(response).await.is_ok());
1201
1202            // … the state event for room 2 -> room 0 has not been saved, but room 0 <- room
1203            // 2 has been saved.
1204            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            // this state event is missing because it creates a loop
1233            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        // Given a room
1244        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        // ensure that we have the power levels
1269        assert!(room.power_levels().await.is_ok());
1270
1271        // ensure that we have the topic
1272        assert_eq!(room.topic().unwrap(), "this is the test topic in the timeline");
1273
1274        Ok(())
1275    }
1276}