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