matrix_sdk_base/
sliding_sync.rs

1// Copyright 2023 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
15//! Extend `BaseClient` with capabilities to handle MSC4186.
16
17use std::collections::BTreeMap;
18#[cfg(feature = "e2e-encryption")]
19use std::ops::Deref;
20
21#[cfg(feature = "e2e-encryption")]
22use matrix_sdk_common::deserialized_responses::TimelineEvent;
23use ruma::{
24    api::client::sync::sync_events::{
25        v3::{self, InvitedRoom, KnockedRoom},
26        v5 as http,
27    },
28    events::{
29        room::member::MembershipState, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
30        AnySyncStateEvent,
31    },
32    serde::Raw,
33    JsOption, OwnedRoomId, RoomId, UserId,
34};
35#[cfg(feature = "e2e-encryption")]
36use ruma::{events::AnyToDeviceEvent, events::StateEventType};
37use tracing::{instrument, trace, warn};
38
39use super::BaseClient;
40use crate::{
41    error::Result,
42    read_receipts::{compute_unread_counts, PreviousEventsProvider},
43    response_processors::AccountDataProcessor,
44    rooms::{
45        normal::{RoomHero, RoomInfoNotableUpdateReasons},
46        RoomState,
47    },
48    ruma::assign,
49    store::{ambiguity_map::AmbiguityCache, StateChanges, Store},
50    sync::{JoinedRoomUpdate, LeftRoomUpdate, Notification, RoomUpdates, SyncResponse},
51    Room, RoomInfo,
52};
53#[cfg(feature = "e2e-encryption")]
54use crate::{
55    latest_event::{is_suitable_for_latest_event, LatestEvent, PossibleLatestEvent},
56    RoomMemberships,
57};
58
59impl BaseClient {
60    #[cfg(feature = "e2e-encryption")]
61    /// Processes the E2EE-related events from the Sliding Sync response.
62    ///
63    /// In addition to writes to the crypto store, this may also write into the
64    /// state store, in particular it may write latest-events to the state
65    /// store.
66    ///
67    /// Returns whether any change happened.
68    pub async fn process_sliding_sync_e2ee(
69        &self,
70        to_device: Option<&http::response::ToDevice>,
71        e2ee: &http::response::E2EE,
72    ) -> Result<Option<Vec<Raw<AnyToDeviceEvent>>>> {
73        if to_device.is_none() && e2ee.is_empty() {
74            return Ok(None);
75        }
76
77        let to_device_events =
78            to_device.as_ref().map(|to_device| to_device.events.clone()).unwrap_or_default();
79
80        trace!(
81            to_device_events = to_device_events.len(),
82            device_one_time_keys_count = e2ee.device_one_time_keys_count.len(),
83            device_unused_fallback_key_types =
84                e2ee.device_unused_fallback_key_types.as_ref().map(|v| v.len()),
85            "Processing sliding sync e2ee events",
86        );
87
88        let mut changes = StateChanges::default();
89        let mut room_info_notable_updates =
90            BTreeMap::<OwnedRoomId, RoomInfoNotableUpdateReasons>::new();
91
92        // Process the to-device events and other related e2ee data. This returns a list
93        // of all the to-device events that were passed in but encrypted ones
94        // were replaced with their decrypted version.
95        let to_device = self
96            .preprocess_to_device_events(
97                matrix_sdk_crypto::EncryptionSyncChanges {
98                    to_device_events,
99                    changed_devices: &e2ee.device_lists,
100                    one_time_keys_counts: &e2ee.device_one_time_keys_count,
101                    unused_fallback_keys: e2ee.device_unused_fallback_key_types.as_deref(),
102                    next_batch_token: to_device
103                        .as_ref()
104                        .map(|to_device| to_device.next_batch.clone()),
105                },
106                &mut changes,
107                &mut room_info_notable_updates,
108            )
109            .await?;
110
111        trace!("ready to submit e2ee changes to store");
112        self.store.save_changes(&changes).await?;
113        self.apply_changes(&changes, room_info_notable_updates);
114        trace!("applied e2ee changes");
115
116        Ok(Some(to_device))
117    }
118
119    /// Process a response from a sliding sync call.
120    ///
121    /// # Arguments
122    ///
123    /// * `response` - The response that we received after a successful sliding
124    ///   sync.
125    /// * `previous_events_provider` - Timeline events prior to the current
126    ///   sync.
127    #[instrument(skip_all, level = "trace")]
128    pub async fn process_sliding_sync<PEP: PreviousEventsProvider>(
129        &self,
130        response: &http::Response,
131        previous_events_provider: &PEP,
132    ) -> Result<SyncResponse> {
133        let http::Response {
134            // FIXME not yet supported by sliding sync. see
135            // https://github.com/matrix-org/matrix-rust-sdk/issues/1014
136            // next_batch,
137            rooms,
138            lists,
139            extensions,
140            // FIXME: missing compared to v3::Response
141            //presence,
142            ..
143        } = response;
144
145        trace!(
146            rooms = rooms.len(),
147            lists = lists.len(),
148            has_extensions = !extensions.is_empty(),
149            "Processing sliding sync room events"
150        );
151
152        if rooms.is_empty() && extensions.is_empty() {
153            // we received a room reshuffling event only, there won't be anything for us to
154            // process. stop early
155            return Ok(SyncResponse::default());
156        };
157
158        let mut changes = StateChanges::default();
159        let mut room_info_notable_updates =
160            BTreeMap::<OwnedRoomId, RoomInfoNotableUpdateReasons>::new();
161
162        let store = self.store.clone();
163        let mut ambiguity_cache = AmbiguityCache::new(store.inner.clone());
164
165        let account_data_processor = AccountDataProcessor::process(&extensions.account_data.global);
166
167        let mut new_rooms = RoomUpdates::default();
168        let mut notifications = Default::default();
169        let mut rooms_account_data = extensions.account_data.rooms.clone();
170
171        let user_id = self
172            .session_meta()
173            .expect("Sliding sync shouldn't run without an authenticated user.")
174            .user_id
175            .to_owned();
176
177        for (room_id, response_room_data) in rooms {
178            let (room_info, joined_room, left_room, invited_room, knocked_room) = self
179                .process_sliding_sync_room(
180                    room_id,
181                    response_room_data,
182                    &mut rooms_account_data,
183                    &store,
184                    &user_id,
185                    &account_data_processor,
186                    &mut changes,
187                    &mut room_info_notable_updates,
188                    &mut notifications,
189                    &mut ambiguity_cache,
190                )
191                .await?;
192
193            changes.add_room(room_info);
194
195            if let Some(joined_room) = joined_room {
196                new_rooms.join.insert(room_id.clone(), joined_room);
197            }
198
199            if let Some(left_room) = left_room {
200                new_rooms.leave.insert(room_id.clone(), left_room);
201            }
202
203            if let Some(invited_room) = invited_room {
204                new_rooms.invite.insert(room_id.clone(), invited_room);
205            }
206
207            if let Some(knocked_room) = knocked_room {
208                new_rooms.knocked.insert(room_id.clone(), knocked_room);
209            }
210        }
211
212        // Handle read receipts and typing notifications independently of the rooms:
213        // these both live in a different subsection of the server's response,
214        // so they may exist without any update for the associated room.
215
216        for (room_id, raw) in &extensions.receipts.rooms {
217            match raw.deserialize() {
218                Ok(event) => {
219                    changes.add_receipts(room_id, event.content);
220                }
221                Err(e) => {
222                    let event_id: Option<String> = raw.get_field("event_id").ok().flatten();
223                    #[rustfmt::skip]
224                    warn!(
225                        ?room_id, event_id,
226                        "Failed to deserialize read receipt room event: {e}"
227                    );
228                }
229            }
230
231            // We assume this can only happen in joined rooms, or something's very wrong.
232            new_rooms
233                .join
234                .entry(room_id.to_owned())
235                .or_insert_with(JoinedRoomUpdate::default)
236                .ephemeral
237                .push(raw.clone().cast());
238        }
239
240        for (room_id, raw) in &extensions.typing.rooms {
241            // We assume this can only happen in joined rooms, or something's very wrong.
242            new_rooms
243                .join
244                .entry(room_id.to_owned())
245                .or_insert_with(JoinedRoomUpdate::default)
246                .ephemeral
247                .push(raw.clone().cast());
248        }
249
250        // Handle room account data
251        for (room_id, raw) in &rooms_account_data {
252            self.handle_room_account_data(
253                room_id,
254                raw,
255                &mut changes,
256                &mut room_info_notable_updates,
257            )
258            .await;
259
260            if let Some(room) = self.store.room(room_id) {
261                match room.state() {
262                    RoomState::Joined => new_rooms
263                        .join
264                        .entry(room_id.to_owned())
265                        .or_insert_with(JoinedRoomUpdate::default)
266                        .account_data
267                        .append(&mut raw.to_vec()),
268                    RoomState::Left | RoomState::Banned => new_rooms
269                        .leave
270                        .entry(room_id.to_owned())
271                        .or_insert_with(LeftRoomUpdate::default)
272                        .account_data
273                        .append(&mut raw.to_vec()),
274                    RoomState::Invited | RoomState::Knocked => {}
275                }
276            }
277        }
278
279        // Rooms in `new_rooms.join` either have a timeline update, or a new read
280        // receipt. Update the read receipt accordingly.
281        let user_id = &self.session_meta().expect("logged in user").user_id;
282
283        for (room_id, joined_room_update) in &mut new_rooms.join {
284            if let Some(mut room_info) = changes
285                .room_infos
286                .get(room_id)
287                .cloned()
288                .or_else(|| self.get_room(room_id).map(|r| r.clone_info()))
289            {
290                let prev_read_receipts = room_info.read_receipts.clone();
291
292                compute_unread_counts(
293                    user_id,
294                    room_id,
295                    changes.receipts.get(room_id),
296                    previous_events_provider.for_room(room_id),
297                    &joined_room_update.timeline.events,
298                    &mut room_info.read_receipts,
299                );
300
301                if prev_read_receipts != room_info.read_receipts {
302                    room_info_notable_updates
303                        .entry(room_id.clone())
304                        .or_default()
305                        .insert(RoomInfoNotableUpdateReasons::READ_RECEIPT);
306
307                    changes.add_room(room_info);
308                }
309            }
310        }
311
312        account_data_processor.apply(&mut changes, &store).await;
313
314        // FIXME not yet supported by sliding sync.
315        // changes.presence = presence
316        //     .events
317        //     .iter()
318        //     .filter_map(|e| {
319        //         let event = e.deserialize().ok()?;
320        //         Some((event.sender, e.clone()))
321        //     })
322        //     .collect();
323
324        changes.ambiguity_maps = ambiguity_cache.cache;
325
326        trace!("ready to submit changes to store");
327        store.save_changes(&changes).await?;
328        self.apply_changes(&changes, room_info_notable_updates);
329        trace!("applied changes");
330
331        // Now that all the rooms information have been saved, update the display name
332        // cache (which relies on information stored in the database). This will
333        // live in memory, until the next sync which will saves the room info to
334        // disk; we do this to avoid saving that would be redundant with the
335        // above. Oh well.
336        new_rooms.update_in_memory_caches(&self.store).await;
337
338        Ok(SyncResponse {
339            rooms: new_rooms,
340            notifications,
341            // FIXME not yet supported by sliding sync.
342            presence: Default::default(),
343            account_data: extensions.account_data.global.clone(),
344            to_device: Default::default(),
345        })
346    }
347
348    #[allow(clippy::too_many_arguments)]
349    async fn process_sliding_sync_room(
350        &self,
351        room_id: &RoomId,
352        room_data: &http::response::Room,
353        rooms_account_data: &mut BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
354        store: &Store,
355        user_id: &UserId,
356        account_data_processor: &AccountDataProcessor,
357        changes: &mut StateChanges,
358        room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
359        notifications: &mut BTreeMap<OwnedRoomId, Vec<Notification>>,
360        ambiguity_cache: &mut AmbiguityCache,
361    ) -> Result<(
362        RoomInfo,
363        Option<JoinedRoomUpdate>,
364        Option<LeftRoomUpdate>,
365        Option<InvitedRoom>,
366        Option<KnockedRoom>,
367    )> {
368        let (raw_state_events, state_events): (Vec<_>, Vec<_>) = {
369            // Read state events from the `required_state` field.
370            let state_events = Self::deserialize_state_events(&room_data.required_state);
371
372            // Don't read state events from the `timeline` field, because they might be
373            // incomplete or staled already. We must only read state events from
374            // `required_state`.
375
376            state_events.into_iter().unzip()
377        };
378
379        // Find or create the room in the store
380        let is_new_room = !store.room_exists(room_id);
381
382        let stripped_state: Option<Vec<(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)>> =
383            room_data
384                .invite_state
385                .as_ref()
386                .map(|invite_state| Self::deserialize_stripped_state_events(invite_state));
387
388        #[allow(unused_mut)] // Required for some feature flag combinations
389        let (mut room, mut room_info, invited_room, knocked_room) = self
390            .process_sliding_sync_room_membership(
391                &state_events,
392                stripped_state.as_ref(),
393                store,
394                user_id,
395                room_id,
396                room_info_notable_updates,
397            );
398
399        room_info.mark_state_partially_synced();
400
401        let mut user_ids = if !state_events.is_empty() {
402            self.handle_state(
403                &raw_state_events,
404                &state_events,
405                &mut room_info,
406                changes,
407                ambiguity_cache,
408            )
409            .await?
410        } else {
411            Default::default()
412        };
413
414        let push_rules = self.get_push_rules(account_data_processor).await?;
415
416        // This will be used for both invited and knocked rooms.
417        if let Some(invite_state) = &stripped_state {
418            self.handle_invited_state(
419                &room,
420                invite_state,
421                &push_rules,
422                &mut room_info,
423                changes,
424                notifications,
425            )
426            .await?;
427        }
428
429        process_room_properties(
430            room_id,
431            room_data,
432            &mut room_info,
433            is_new_room,
434            room_info_notable_updates,
435        );
436
437        let timeline = self
438            .handle_timeline(
439                &room,
440                room_data.limited,
441                room_data.timeline.clone(),
442                true,
443                room_data.prev_batch.clone(),
444                &push_rules,
445                &mut user_ids,
446                &mut room_info,
447                changes,
448                notifications,
449                ambiguity_cache,
450            )
451            .await?;
452
453        // Cache the latest decrypted event in room_info, and also keep any later
454        // encrypted events, so we can slot them in when we get the keys.
455        #[cfg(feature = "e2e-encryption")]
456        cache_latest_events(&room, &mut room_info, &timeline.events, Some(changes), Some(store))
457            .await;
458
459        #[cfg(feature = "e2e-encryption")]
460        if room_info.is_encrypted() {
461            if let Some(o) = self.olm_machine().await.as_ref() {
462                if !room.is_encrypted() {
463                    // The room turned on encryption in this sync, we need
464                    // to also get all the existing users and mark them for
465                    // tracking.
466                    let user_ids = store.get_user_ids(room_id, RoomMemberships::ACTIVE).await?;
467                    o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?
468                }
469
470                if !user_ids.is_empty() {
471                    o.update_tracked_users(user_ids.iter().map(Deref::deref)).await?;
472                }
473            }
474        }
475
476        let notification_count = room_data.unread_notifications.clone().into();
477        room_info.update_notification_count(notification_count);
478
479        let ambiguity_changes = ambiguity_cache.changes.remove(room_id).unwrap_or_default();
480        let room_account_data = rooms_account_data.get(room_id).cloned();
481
482        match room_info.state() {
483            RoomState::Joined => {
484                // Ephemeral events are added separately, because we might not
485                // have a room subsection in the response, yet we may have receipts for
486                // that room.
487                let ephemeral = Vec::new();
488
489                Ok((
490                    room_info,
491                    Some(JoinedRoomUpdate::new(
492                        timeline,
493                        raw_state_events,
494                        room_account_data.unwrap_or_default(),
495                        ephemeral,
496                        notification_count,
497                        ambiguity_changes,
498                    )),
499                    None,
500                    None,
501                    None,
502                ))
503            }
504
505            RoomState::Left | RoomState::Banned => Ok((
506                room_info,
507                None,
508                Some(LeftRoomUpdate::new(
509                    timeline,
510                    raw_state_events,
511                    room_account_data.unwrap_or_default(),
512                    ambiguity_changes,
513                )),
514                None,
515                None,
516            )),
517
518            RoomState::Invited => Ok((room_info, None, None, invited_room, None)),
519
520            RoomState::Knocked => Ok((room_info, None, None, None, knocked_room)),
521        }
522    }
523
524    /// Look through the sliding sync data for this room, find/create it in the
525    /// store, and process any invite information.
526    /// If any invite_state exists, we take it to mean that we are invited to
527    /// this room, unless that state contains membership events that specify
528    /// otherwise. https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/sync-v3/proposals/3575-sync.md#room-list-parameters
529    fn process_sliding_sync_room_membership(
530        &self,
531        state_events: &[AnySyncStateEvent],
532        stripped_state: Option<&Vec<(Raw<AnyStrippedStateEvent>, AnyStrippedStateEvent)>>,
533        store: &Store,
534        user_id: &UserId,
535        room_id: &RoomId,
536        room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
537    ) -> (Room, RoomInfo, Option<InvitedRoom>, Option<KnockedRoom>) {
538        if let Some(stripped_state) = stripped_state {
539            let room = store.get_or_create_room(
540                room_id,
541                RoomState::Invited,
542                self.room_info_notable_update_sender.clone(),
543            );
544            let mut room_info = room.clone_info();
545
546            // We need to find the membership event since it could be for either an invited
547            // or knocked room
548            let membership_event_content = stripped_state.iter().find_map(|(_, event)| {
549                if let AnyStrippedStateEvent::RoomMember(membership_event) = event {
550                    if membership_event.state_key == user_id {
551                        return Some(membership_event.content.clone());
552                    }
553                }
554                None
555            });
556
557            if let Some(membership_event_content) = membership_event_content {
558                if membership_event_content.membership == MembershipState::Knock {
559                    // If we have a `Knock` membership state, set the room as such
560                    room_info.mark_as_knocked();
561                    let raw_events = stripped_state.iter().map(|(raw, _)| raw.clone()).collect();
562                    let knock_state = assign!(v3::KnockState::default(), { events: raw_events });
563                    let knocked_room =
564                        assign!(KnockedRoom::default(), { knock_state: knock_state });
565                    return (room, room_info, None, Some(knocked_room));
566                }
567            }
568
569            // Otherwise assume it's an invited room
570            room_info.mark_as_invited();
571            let raw_events = stripped_state.iter().map(|(raw, _)| raw.clone()).collect::<Vec<_>>();
572            let invited_room = InvitedRoom::from(v3::InviteState::from(raw_events));
573            (room, room_info, Some(invited_room), None)
574        } else {
575            let room = store.get_or_create_room(
576                room_id,
577                RoomState::Joined,
578                self.room_info_notable_update_sender.clone(),
579            );
580            let mut room_info = room.clone_info();
581
582            // We default to considering this room joined if it's not an invite. If it's
583            // actually left (and we remembered to request membership events in
584            // our sync request), then we can find this out from the events in
585            // required_state by calling handle_own_room_membership.
586            room_info.mark_as_joined();
587
588            // We don't need to do this in a v2 sync, because the membership of a room can
589            // be figured out by whether the room is in the "join", "leave" etc.
590            // property. In sliding sync we only have invite_state,
591            // required_state and timeline, so we must process required_state and timeline
592            // looking for relevant membership events.
593            self.handle_own_room_membership(
594                state_events,
595                &mut room_info,
596                room_info_notable_updates,
597            );
598
599            (room, room_info, None, None)
600        }
601    }
602
603    /// Find any m.room.member events that refer to the current user, and update
604    /// the state in room_info to reflect the "membership" property.
605    pub(crate) fn handle_own_room_membership(
606        &self,
607        state_events: &[AnySyncStateEvent],
608        room_info: &mut RoomInfo,
609        room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
610    ) {
611        let Some(meta) = self.session_meta() else {
612            return;
613        };
614
615        // Start from the last event; the first membership event we see in that order is
616        // the last in the regular order, so that's the only one we need to
617        // consider.
618        for event in state_events.iter().rev() {
619            if let AnySyncStateEvent::RoomMember(member) = &event {
620                // If this event updates the current user's membership, record that in the
621                // room_info.
622                if member.state_key() == meta.user_id.as_str() {
623                    let new_state: RoomState = member.membership().into();
624                    if new_state != room_info.state() {
625                        room_info.set_state(new_state);
626                        // Update an existing notable update entry or create a new one
627                        room_info_notable_updates
628                            .entry(room_info.room_id.to_owned())
629                            .or_default()
630                            .insert(RoomInfoNotableUpdateReasons::MEMBERSHIP);
631                    }
632                    break;
633                }
634            }
635        }
636    }
637}
638
639/// Find the most recent decrypted event and cache it in the supplied RoomInfo.
640///
641/// If any encrypted events are found after that one, store them in the RoomInfo
642/// too so we can use them when we get the relevant keys.
643///
644/// It is the responsibility of the caller to update the `RoomInfo` instance
645/// stored in the `Room`.
646#[cfg(feature = "e2e-encryption")]
647async fn cache_latest_events(
648    room: &Room,
649    room_info: &mut RoomInfo,
650    events: &[TimelineEvent],
651    changes: Option<&StateChanges>,
652    store: Option<&Store>,
653) {
654    use crate::{
655        deserialized_responses::DisplayName, store::ambiguity_map::is_display_name_ambiguous,
656    };
657
658    let mut encrypted_events =
659        Vec::with_capacity(room.latest_encrypted_events.read().unwrap().capacity());
660
661    // Try to get room power levels from the current changes
662    let power_levels_from_changes = || {
663        let state_changes = changes?.state.get(room_info.room_id())?;
664        let room_power_levels_state =
665            state_changes.get(&StateEventType::RoomPowerLevels)?.values().next()?;
666        match room_power_levels_state.deserialize().ok()? {
667            AnySyncStateEvent::RoomPowerLevels(ev) => Some(ev.power_levels()),
668            _ => None,
669        }
670    };
671
672    // If we didn't get any info, try getting it from local data
673    let power_levels = match power_levels_from_changes() {
674        Some(power_levels) => Some(power_levels),
675        None => room.power_levels().await.ok(),
676    };
677
678    let power_levels_info = Some(room.own_user_id()).zip(power_levels.as_ref());
679
680    for event in events.iter().rev() {
681        if let Ok(timeline_event) = event.raw().deserialize() {
682            match is_suitable_for_latest_event(&timeline_event, power_levels_info) {
683                PossibleLatestEvent::YesRoomMessage(_)
684                | PossibleLatestEvent::YesPoll(_)
685                | PossibleLatestEvent::YesCallInvite(_)
686                | PossibleLatestEvent::YesCallNotify(_)
687                | PossibleLatestEvent::YesSticker(_)
688                | PossibleLatestEvent::YesKnockedStateEvent(_) => {
689                    // We found a suitable latest event. Store it.
690
691                    // In order to make the latest event fast to read, we want to keep the
692                    // associated sender in cache. This is a best-effort to gather enough
693                    // information for creating a user profile as fast as possible. If information
694                    // are missing, let's go back on the “slow” path.
695
696                    let mut sender_profile = None;
697                    let mut sender_name_is_ambiguous = None;
698
699                    // First off, look up the sender's profile from the `StateChanges`, they are
700                    // likely to be the most recent information.
701                    if let Some(changes) = changes {
702                        sender_profile = changes
703                            .profiles
704                            .get(room.room_id())
705                            .and_then(|profiles_by_user| {
706                                profiles_by_user.get(timeline_event.sender())
707                            })
708                            .cloned();
709
710                        if let Some(sender_profile) = sender_profile.as_ref() {
711                            sender_name_is_ambiguous = sender_profile
712                                .as_original()
713                                .and_then(|profile| profile.content.displayname.as_ref())
714                                .and_then(|display_name| {
715                                    let display_name = DisplayName::new(display_name);
716
717                                    changes.ambiguity_maps.get(room.room_id()).and_then(
718                                        |map_for_room| {
719                                            map_for_room.get(&display_name).map(|users| {
720                                                is_display_name_ambiguous(&display_name, users)
721                                            })
722                                        },
723                                    )
724                                });
725                        }
726                    }
727
728                    // Otherwise, look up the sender's profile from the `Store`.
729                    if sender_profile.is_none() {
730                        if let Some(store) = store {
731                            sender_profile = store
732                                .get_profile(room.room_id(), timeline_event.sender())
733                                .await
734                                .ok()
735                                .flatten();
736
737                            // TODO: need to update `sender_name_is_ambiguous`,
738                            // but how?
739                        }
740                    }
741
742                    let latest_event = Box::new(LatestEvent::new_with_sender_details(
743                        event.clone(),
744                        sender_profile,
745                        sender_name_is_ambiguous,
746                    ));
747
748                    // Store it in the return RoomInfo (it will be saved for us in the room later).
749                    room_info.latest_event = Some(latest_event);
750                    // We don't need any of the older encrypted events because we have a new
751                    // decrypted one.
752                    room.latest_encrypted_events.write().unwrap().clear();
753                    // We can stop looking through the timeline now because everything else is
754                    // older.
755                    break;
756                }
757                PossibleLatestEvent::NoEncrypted => {
758                    // m.room.encrypted - this might be the latest event later - we can't tell until
759                    // we are able to decrypt it, so store it for now
760                    //
761                    // Check how many encrypted events we have seen. Only store another if we
762                    // haven't already stored the maximum number.
763                    if encrypted_events.len() < encrypted_events.capacity() {
764                        encrypted_events.push(event.raw().clone());
765                    }
766                }
767                _ => {
768                    // Ignore unsuitable events
769                }
770            }
771        } else {
772            warn!(
773                "Failed to deserialize event as AnySyncTimelineEvent. ID={}",
774                event.event_id().expect("Event has no ID!")
775            );
776        }
777    }
778
779    // Push the encrypted events we found into the Room, in reverse order, so
780    // the latest is last
781    room.latest_encrypted_events.write().unwrap().extend(encrypted_events.into_iter().rev());
782}
783
784fn process_room_properties(
785    room_id: &RoomId,
786    room_data: &http::response::Room,
787    room_info: &mut RoomInfo,
788    is_new_room: bool,
789    room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
790) {
791    // Handle the room's avatar.
792    //
793    // It can be updated via the state events, or via the
794    // [`http::ResponseRoom::avatar`] field. This part of the code handles the
795    // latter case. The former case is handled by [`BaseClient::handle_state`].
796    match &room_data.avatar {
797        // A new avatar!
798        JsOption::Some(avatar_uri) => room_info.update_avatar(Some(avatar_uri.to_owned())),
799        // Avatar must be removed.
800        JsOption::Null => room_info.update_avatar(None),
801        // Nothing to do.
802        JsOption::Undefined => {}
803    }
804
805    // Sliding sync doesn't have a room summary, nevertheless it contains the joined
806    // and invited member counts, in addition to the heroes if it's been configured
807    // to return them (see the [`http::RequestRoomSubscription::include_heroes`]).
808    if let Some(count) = room_data.joined_count {
809        room_info.update_joined_member_count(count.into());
810    }
811    if let Some(count) = room_data.invited_count {
812        room_info.update_invited_member_count(count.into());
813    }
814
815    if let Some(heroes) = &room_data.heroes {
816        room_info.update_heroes(
817            heroes
818                .iter()
819                .map(|hero| RoomHero {
820                    user_id: hero.user_id.clone(),
821                    display_name: hero.name.clone(),
822                    avatar_url: hero.avatar.clone(),
823                })
824                .collect(),
825        );
826    }
827
828    room_info.set_prev_batch(room_data.prev_batch.as_deref());
829
830    if room_data.limited {
831        room_info.mark_members_missing();
832    }
833
834    if let Some(recency_stamp) = &room_data.bump_stamp {
835        let recency_stamp: u64 = (*recency_stamp).into();
836
837        if room_info.recency_stamp.as_ref() != Some(&recency_stamp) {
838            room_info.update_recency_stamp(recency_stamp);
839
840            // If it's not a new room, let's emit a `RECENCY_STAMP` update.
841            // For a new room, the room will appear as new, so we don't care about this
842            // update.
843            if !is_new_room {
844                room_info_notable_updates
845                    .entry(room_id.to_owned())
846                    .or_default()
847                    .insert(RoomInfoNotableUpdateReasons::RECENCY_STAMP);
848            }
849        }
850    }
851}
852
853#[cfg(all(test, not(target_family = "wasm")))]
854mod tests {
855    use std::collections::{BTreeMap, HashSet};
856    #[cfg(feature = "e2e-encryption")]
857    use std::sync::{Arc, RwLock as SyncRwLock};
858
859    use assert_matches::assert_matches;
860    use matrix_sdk_common::deserialized_responses::TimelineEvent;
861    #[cfg(feature = "e2e-encryption")]
862    use matrix_sdk_common::{
863        deserialized_responses::{UnableToDecryptInfo, UnableToDecryptReason},
864        ring_buffer::RingBuffer,
865    };
866    use matrix_sdk_test::async_test;
867    use ruma::{
868        api::client::sync::sync_events::UnreadNotificationsCount,
869        assign, event_id,
870        events::{
871            direct::{DirectEventContent, DirectUserIdentifier, OwnedDirectUserIdentifier},
872            room::{
873                avatar::RoomAvatarEventContent,
874                canonical_alias::RoomCanonicalAliasEventContent,
875                member::{MembershipState, RoomMemberEventContent},
876                message::SyncRoomMessageEvent,
877                name::RoomNameEventContent,
878                pinned_events::RoomPinnedEventsEventContent,
879            },
880            AnySyncMessageLikeEvent, AnySyncTimelineEvent, GlobalAccountDataEventContent,
881            StateEventContent,
882        },
883        mxc_uri, owned_event_id, owned_mxc_uri, owned_user_id, room_alias_id, room_id,
884        serde::Raw,
885        uint, user_id, JsOption, MxcUri, OwnedRoomId, OwnedUserId, RoomAliasId, RoomId, UserId,
886    };
887    use serde_json::json;
888
889    #[cfg(feature = "e2e-encryption")]
890    use super::cache_latest_events;
891    use super::http;
892    use crate::{
893        rooms::normal::{RoomHero, RoomInfoNotableUpdateReasons},
894        test_utils::logged_in_base_client,
895        BaseClient, RoomInfoNotableUpdate, RoomState,
896    };
897    #[cfg(feature = "e2e-encryption")]
898    use crate::{store::MemoryStore, Room};
899
900    #[async_test]
901    async fn test_notification_count_set() {
902        let client = logged_in_base_client(None).await;
903
904        let mut response = http::Response::new("42".to_owned());
905        let room_id = room_id!("!room:example.org");
906        let count = assign!(UnreadNotificationsCount::default(), {
907            highlight_count: Some(uint!(13)),
908            notification_count: Some(uint!(37)),
909        });
910
911        response.rooms.insert(
912            room_id.to_owned(),
913            assign!(http::response::Room::new(), {
914                unread_notifications: count.clone()
915            }),
916        );
917
918        let sync_response =
919            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
920
921        // Check it's present in the response.
922        let room = sync_response.rooms.join.get(room_id).unwrap();
923        assert_eq!(room.unread_notifications, count.clone().into());
924
925        // Check it's been updated in the store.
926        let room = client.get_room(room_id).expect("found room");
927        assert_eq!(room.unread_notification_counts(), count.into());
928    }
929
930    #[async_test]
931    async fn test_can_process_empty_sliding_sync_response() {
932        let client = logged_in_base_client(None).await;
933        let empty_response = http::Response::new("5".to_owned());
934        client.process_sliding_sync(&empty_response, &()).await.expect("Failed to process sync");
935    }
936
937    #[async_test]
938    async fn test_room_with_unspecified_state_is_added_to_client_and_joined_list() {
939        // Given a logged-in client
940        let client = logged_in_base_client(None).await;
941        let room_id = room_id!("!r:e.uk");
942
943        // When I send sliding sync response containing a room (with identifiable data
944        // in joined_count)
945        let mut room = http::response::Room::new();
946        room.joined_count = Some(uint!(41));
947        let response = response_with_room(room_id, room);
948        let sync_resp =
949            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
950
951        // Then the room appears in the client (with the same joined count)
952        let client_room = client.get_room(room_id).expect("No room found");
953        assert_eq!(client_room.room_id(), room_id);
954        assert_eq!(client_room.joined_members_count(), 41);
955        assert_eq!(client_room.state(), RoomState::Joined);
956
957        // And it is added to the list of joined rooms only.
958        assert!(sync_resp.rooms.join.contains_key(room_id));
959        assert!(!sync_resp.rooms.leave.contains_key(room_id));
960        assert!(!sync_resp.rooms.invite.contains_key(room_id));
961    }
962
963    #[async_test]
964    async fn test_missing_room_name_event() {
965        // Given a logged-in client
966        let client = logged_in_base_client(None).await;
967        let room_id = room_id!("!r:e.uk");
968
969        // When I send sliding sync response containing a room with a name set in the
970        // sliding sync response,
971        let mut room = http::response::Room::new();
972        room.name = Some("little room".to_owned());
973        let response = response_with_room(room_id, room);
974        let sync_resp =
975            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
976
977        // No m.room.name event, no heroes, no members => considered an empty room!
978        let client_room = client.get_room(room_id).expect("No room found");
979        assert!(client_room.name().is_none());
980        assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "Empty Room");
981        assert_eq!(client_room.state(), RoomState::Joined);
982
983        // And it is added to the list of joined rooms only.
984        assert!(sync_resp.rooms.join.contains_key(room_id));
985        assert!(!sync_resp.rooms.leave.contains_key(room_id));
986        assert!(!sync_resp.rooms.invite.contains_key(room_id));
987        assert!(!sync_resp.rooms.knocked.contains_key(room_id));
988    }
989
990    #[async_test]
991    async fn test_room_name_event() {
992        // Given a logged-in client
993        let client = logged_in_base_client(None).await;
994        let room_id = room_id!("!r:e.uk");
995
996        // When I send sliding sync response containing a room with a name set in the
997        // sliding sync response, and a m.room.name event,
998        let mut room = http::response::Room::new();
999
1000        room.name = Some("little room".to_owned());
1001        set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
1002
1003        let response = response_with_room(room_id, room);
1004        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1005
1006        // The name is known.
1007        let client_room = client.get_room(room_id).expect("No room found");
1008        assert_eq!(client_room.name().as_deref(), Some("The Name"));
1009        assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
1010    }
1011
1012    #[async_test]
1013    async fn test_missing_invited_room_name_event() {
1014        // Given a logged-in client,
1015        let client = logged_in_base_client(None).await;
1016        let room_id = room_id!("!r:e.uk");
1017        let user_id = user_id!("@w:e.uk");
1018        let inviter = user_id!("@john:mastodon.org");
1019
1020        // When I send sliding sync response containing a room with a name set in the
1021        // sliding sync response,
1022        let mut room = http::response::Room::new();
1023        set_room_invited(&mut room, inviter, user_id);
1024        room.name = Some("name from sliding sync response".to_owned());
1025        let response = response_with_room(room_id, room);
1026        let sync_resp =
1027            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1028
1029        // Then the room doesn't have the name in the client.
1030        let client_room = client.get_room(room_id).expect("No room found");
1031        assert!(client_room.name().is_none());
1032
1033        // No m.room.name event, no heroes => using the invited member.
1034        assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "w");
1035
1036        assert_eq!(client_room.state(), RoomState::Invited);
1037
1038        // And it is added to the list of invited rooms only.
1039        assert!(!sync_resp.rooms.join.contains_key(room_id));
1040        assert!(!sync_resp.rooms.leave.contains_key(room_id));
1041        assert!(sync_resp.rooms.invite.contains_key(room_id));
1042        assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1043    }
1044
1045    #[async_test]
1046    async fn test_invited_room_name_event() {
1047        // Given a logged-in client,
1048        let client = logged_in_base_client(None).await;
1049        let room_id = room_id!("!r:e.uk");
1050        let user_id = user_id!("@w:e.uk");
1051        let inviter = user_id!("@john:mastodon.org");
1052
1053        // When I send sliding sync response containing a room with a name set in the
1054        // sliding sync response, and a m.room.name event,
1055        let mut room = http::response::Room::new();
1056
1057        set_room_invited(&mut room, inviter, user_id);
1058
1059        room.name = Some("name from sliding sync response".to_owned());
1060        set_room_name(&mut room, user_id!("@a:b.c"), "The Name".to_owned());
1061
1062        let response = response_with_room(room_id, room);
1063        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1064
1065        // The name is known.
1066        let client_room = client.get_room(room_id).expect("No room found");
1067        assert_eq!(client_room.name().as_deref(), Some("The Name"));
1068        assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "The Name");
1069    }
1070
1071    #[async_test]
1072    async fn test_receiving_a_knocked_room_membership_event_creates_a_knocked_room() {
1073        // Given a logged-in client,
1074        let client = logged_in_base_client(None).await;
1075        let room_id = room_id!("!r:e.uk");
1076        let user_id = client.session_meta().unwrap().user_id.to_owned();
1077
1078        // When the room is properly set as knocked with the current user id as state
1079        // key,
1080        let mut room = http::response::Room::new();
1081        set_room_knocked(&mut room, &user_id);
1082
1083        let response = response_with_room(room_id, room);
1084        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1085
1086        // The room is knocked.
1087        let client_room = client.get_room(room_id).expect("No room found");
1088        assert_eq!(client_room.state(), RoomState::Knocked);
1089    }
1090
1091    #[async_test]
1092    async fn test_receiving_a_knocked_room_membership_event_with_wrong_state_key_creates_an_invited_room(
1093    ) {
1094        // Given a logged-in client,
1095        let client = logged_in_base_client(None).await;
1096        let room_id = room_id!("!r:e.uk");
1097        let user_id = user_id!("@w:e.uk");
1098
1099        // When the room is set as knocked with a random user id as state key,
1100        let mut room = http::response::Room::new();
1101        set_room_knocked(&mut room, user_id);
1102
1103        let response = response_with_room(room_id, room);
1104        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1105
1106        // The room is invited since the membership event doesn't belong to the current
1107        // user.
1108        let client_room = client.get_room(room_id).expect("No room found");
1109        assert_eq!(client_room.state(), RoomState::Invited);
1110    }
1111
1112    #[async_test]
1113    async fn test_receiving_an_unknown_room_membership_event_in_invite_state_creates_an_invited_room(
1114    ) {
1115        // Given a logged-in client,
1116        let client = logged_in_base_client(None).await;
1117        let room_id = room_id!("!r:e.uk");
1118        let user_id = client.session_meta().unwrap().user_id.to_owned();
1119
1120        // When the room has the wrong membership state in its invite_state
1121        let mut room = http::response::Room::new();
1122        let event = Raw::new(&json!({
1123            "type": "m.room.member",
1124            "sender": user_id,
1125            "content": {
1126                "is_direct": true,
1127                "membership": "join",
1128            },
1129            "state_key": user_id,
1130        }))
1131        .expect("Failed to make raw event")
1132        .cast();
1133        room.invite_state = Some(vec![event]);
1134
1135        let response = response_with_room(room_id, room);
1136        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1137
1138        // The room is marked as invited.
1139        let client_room = client.get_room(room_id).expect("No room found");
1140        assert_eq!(client_room.state(), RoomState::Invited);
1141    }
1142
1143    #[async_test]
1144    async fn test_left_a_room_from_required_state_event() {
1145        // Given a logged-in client
1146        let client = logged_in_base_client(None).await;
1147        let room_id = room_id!("!r:e.uk");
1148        let user_id = user_id!("@u:e.uk");
1149
1150        // When I join…
1151        let mut room = http::response::Room::new();
1152        set_room_joined(&mut room, user_id);
1153        let response = response_with_room(room_id, room);
1154        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1155        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1156
1157        // And then leave with a `required_state` state event…
1158        let mut room = http::response::Room::new();
1159        set_room_left(&mut room, user_id);
1160        let response = response_with_room(room_id, room);
1161        let sync_resp =
1162            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1163
1164        // The room is left.
1165        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1166
1167        // And it is added to the list of left rooms only.
1168        assert!(!sync_resp.rooms.join.contains_key(room_id));
1169        assert!(sync_resp.rooms.leave.contains_key(room_id));
1170        assert!(!sync_resp.rooms.invite.contains_key(room_id));
1171        assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1172    }
1173
1174    #[async_test]
1175    async fn test_kick_or_ban_updates_room_to_left() {
1176        for membership in [MembershipState::Leave, MembershipState::Ban] {
1177            let room_id = room_id!("!r:e.uk");
1178            let user_a_id = user_id!("@a:e.uk");
1179            let user_b_id = user_id!("@b:e.uk");
1180            let client = logged_in_base_client(Some(user_a_id)).await;
1181
1182            // When I join…
1183            let mut room = http::response::Room::new();
1184            set_room_joined(&mut room, user_a_id);
1185            let response = response_with_room(room_id, room);
1186            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1187            assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1188
1189            // And then get kicked/banned with a `required_state` state event…
1190            let mut room = http::response::Room::new();
1191            room.required_state.push(make_state_event(
1192                user_b_id,
1193                user_a_id.as_str(),
1194                RoomMemberEventContent::new(membership.clone()),
1195                None,
1196            ));
1197            let response = response_with_room(room_id, room);
1198            let sync_resp =
1199                client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1200
1201            match membership {
1202                MembershipState::Leave => {
1203                    // The room is left.
1204                    assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1205                }
1206                MembershipState::Ban => {
1207                    // The room is banned.
1208                    assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Banned);
1209                }
1210                _ => panic!("Unexpected membership state found: {membership}"),
1211            }
1212
1213            // And it is added to the list of left rooms only.
1214            assert!(!sync_resp.rooms.join.contains_key(room_id));
1215            assert!(sync_resp.rooms.leave.contains_key(room_id));
1216            assert!(!sync_resp.rooms.invite.contains_key(room_id));
1217            assert!(!sync_resp.rooms.knocked.contains_key(room_id));
1218        }
1219    }
1220
1221    #[async_test]
1222    async fn test_left_a_room_from_timeline_state_event() {
1223        // Given a logged-in client
1224        let client = logged_in_base_client(None).await;
1225        let room_id = room_id!("!r:e.uk");
1226        let user_id = user_id!("@u:e.uk");
1227
1228        // When I join…
1229        let mut room = http::response::Room::new();
1230        set_room_joined(&mut room, user_id);
1231        let response = response_with_room(room_id, room);
1232        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1233        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1234
1235        // And then leave with a `timeline` state event…
1236        let mut room = http::response::Room::new();
1237        set_room_left_as_timeline_event(&mut room, user_id);
1238        let response = response_with_room(room_id, room);
1239        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1240
1241        // The room is NOT left because state events from `timeline` must be IGNORED!
1242        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1243    }
1244
1245    #[async_test]
1246    async fn test_can_be_reinvited_to_a_left_room() {
1247        // See https://github.com/matrix-org/matrix-rust-sdk/issues/1834
1248
1249        // Given a logged-in client
1250        let client = logged_in_base_client(None).await;
1251        let room_id = room_id!("!r:e.uk");
1252        let user_id = user_id!("@u:e.uk");
1253
1254        // When I join...
1255        let mut room = http::response::Room::new();
1256        set_room_joined(&mut room, user_id);
1257        let response = response_with_room(room_id, room);
1258        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1259        // (sanity: state is join)
1260        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Joined);
1261
1262        // And then leave...
1263        let mut room = http::response::Room::new();
1264        set_room_left(&mut room, user_id);
1265        let response = response_with_room(room_id, room);
1266        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1267        // (sanity: state is left)
1268        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1269
1270        // And then get invited back
1271        let mut room = http::response::Room::new();
1272        set_room_invited(&mut room, user_id, user_id);
1273        let response = response_with_room(room_id, room);
1274        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1275
1276        // Then the room is in the invite state
1277        assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1278    }
1279
1280    #[async_test]
1281    async fn test_other_person_leaving_a_dm_is_reflected_in_their_membership_and_direct_targets() {
1282        let room_id = room_id!("!r:e.uk");
1283        let user_a_id = user_id!("@a:e.uk");
1284        let user_b_id = user_id!("@b:e.uk");
1285
1286        // Given we have a DM with B, who is joined
1287        let client = logged_in_base_client(None).await;
1288        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1289
1290        // (Sanity: B is a direct target, and is in Join state)
1291        assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1292        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1293
1294        // When B leaves
1295        update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1296
1297        // Then B is still a direct target, and is in Leave state (B is a direct target
1298        // because we want to return to our old DM in the UI even if the other
1299        // user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017)
1300        assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1301        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1302    }
1303
1304    #[async_test]
1305    async fn test_other_person_refusing_invite_to_a_dm_is_reflected_in_their_membership_and_direct_targets(
1306    ) {
1307        let room_id = room_id!("!r:e.uk");
1308        let user_a_id = user_id!("@a:e.uk");
1309        let user_b_id = user_id!("@b:e.uk");
1310
1311        // Given I have invited B to a DM
1312        let client = logged_in_base_client(None).await;
1313        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1314
1315        // (Sanity: B is a direct target, and is in Invite state)
1316        assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1317        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1318
1319        // When B declines the invitation (i.e. leaves)
1320        update_room_membership(&client, room_id, user_b_id, MembershipState::Leave).await;
1321
1322        // Then B is still a direct target, and is in Leave state (B is a direct target
1323        // because we want to return to our old DM in the UI even if the other
1324        // user left, so we can reinvite them. See https://github.com/matrix-org/matrix-rust-sdk/issues/2017)
1325        assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1326        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Leave);
1327    }
1328
1329    #[async_test]
1330    async fn test_members_count_in_a_dm_where_other_person_has_joined() {
1331        let room_id = room_id!("!r:bar.org");
1332        let user_a_id = user_id!("@a:bar.org");
1333        let user_b_id = user_id!("@b:bar.org");
1334
1335        // Given we have a DM with B, who is joined
1336        let client = logged_in_base_client(None).await;
1337        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Join).await;
1338
1339        // (Sanity: A is in Join state)
1340        assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1341
1342        // (Sanity: B is a direct target, and is in Join state)
1343        assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1344        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Join);
1345
1346        let room = client.get_room(room_id).unwrap();
1347
1348        assert_eq!(room.active_members_count(), 2);
1349        assert_eq!(room.joined_members_count(), 2);
1350        assert_eq!(room.invited_members_count(), 0);
1351    }
1352
1353    #[async_test]
1354    async fn test_members_count_in_a_dm_where_other_person_is_invited() {
1355        let room_id = room_id!("!r:bar.org");
1356        let user_a_id = user_id!("@a:bar.org");
1357        let user_b_id = user_id!("@b:bar.org");
1358
1359        // Given we have a DM with B, who is joined
1360        let client = logged_in_base_client(None).await;
1361        create_dm(&client, room_id, user_a_id, user_b_id, MembershipState::Invite).await;
1362
1363        // (Sanity: A is in Join state)
1364        assert_eq!(membership(&client, room_id, user_a_id).await, MembershipState::Join);
1365
1366        // (Sanity: B is a direct target, and is in Join state)
1367        assert!(direct_targets(&client, room_id).contains(<&DirectUserIdentifier>::from(user_b_id)));
1368        assert_eq!(membership(&client, room_id, user_b_id).await, MembershipState::Invite);
1369
1370        let room = client.get_room(room_id).unwrap();
1371
1372        assert_eq!(room.active_members_count(), 2);
1373        assert_eq!(room.joined_members_count(), 1);
1374        assert_eq!(room.invited_members_count(), 1);
1375    }
1376
1377    #[async_test]
1378    async fn test_avatar_is_found_when_processing_sliding_sync_response() {
1379        // Given a logged-in client
1380        let client = logged_in_base_client(None).await;
1381        let room_id = room_id!("!r:e.uk");
1382
1383        // When I send sliding sync response containing a room with an avatar
1384        let room = {
1385            let mut room = http::response::Room::new();
1386            room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1387
1388            room
1389        };
1390        let response = response_with_room(room_id, room);
1391        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1392
1393        // Then the room in the client has the avatar
1394        let client_room = client.get_room(room_id).expect("No room found");
1395        assert_eq!(
1396            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1397            "med1"
1398        );
1399    }
1400
1401    #[async_test]
1402    async fn test_avatar_can_be_unset_when_processing_sliding_sync_response() {
1403        // Given a logged-in client
1404        let client = logged_in_base_client(None).await;
1405        let room_id = room_id!("!r:e.uk");
1406
1407        // Set the avatar.
1408
1409        // When I send sliding sync response containing a room with an avatar
1410        let room = {
1411            let mut room = http::response::Room::new();
1412            room.avatar = JsOption::from_option(Some(mxc_uri!("mxc://e.uk/med1").to_owned()));
1413
1414            room
1415        };
1416        let response = response_with_room(room_id, room);
1417        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1418
1419        // Then the room in the client has the avatar
1420        let client_room = client.get_room(room_id).expect("No room found");
1421        assert_eq!(
1422            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1423            "med1"
1424        );
1425
1426        // No avatar. Still here.
1427
1428        // When I send sliding sync response containing no avatar.
1429        let room = http::response::Room::new();
1430        let response = response_with_room(room_id, room);
1431        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1432
1433        // Then the room in the client still has the avatar
1434        let client_room = client.get_room(room_id).expect("No room found");
1435        assert_eq!(
1436            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1437            "med1"
1438        );
1439
1440        // Avatar is unset.
1441
1442        // When I send sliding sync response containing an avatar set to `null` (!).
1443        let room = {
1444            let mut room = http::response::Room::new();
1445            room.avatar = JsOption::Null;
1446
1447            room
1448        };
1449        let response = response_with_room(room_id, room);
1450        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1451
1452        // Then the room in the client has no more avatar
1453        let client_room = client.get_room(room_id).expect("No room found");
1454        assert!(client_room.avatar_url().is_none());
1455    }
1456
1457    #[async_test]
1458    async fn test_avatar_is_found_from_required_state_when_processing_sliding_sync_response() {
1459        // Given a logged-in client
1460        let client = logged_in_base_client(None).await;
1461        let room_id = room_id!("!r:e.uk");
1462        let user_id = user_id!("@u:e.uk");
1463
1464        // When I send sliding sync response containing a room with an avatar
1465        let room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1466        let response = response_with_room(room_id, room);
1467        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1468
1469        // Then the room in the client has the avatar
1470        let client_room = client.get_room(room_id).expect("No room found");
1471        assert_eq!(
1472            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1473            "med1"
1474        );
1475    }
1476
1477    #[async_test]
1478    async fn test_invitation_room_is_added_to_client_and_invite_list() {
1479        // Given a logged-in client
1480        let client = logged_in_base_client(None).await;
1481        let room_id = room_id!("!r:e.uk");
1482        let user_id = user_id!("@u:e.uk");
1483
1484        // When I send sliding sync response containing an invited room
1485        let mut room = http::response::Room::new();
1486        set_room_invited(&mut room, user_id, user_id);
1487        let response = response_with_room(room_id, room);
1488        let sync_resp =
1489            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1490
1491        // Then the room is added to the client
1492        let client_room = client.get_room(room_id).expect("No room found");
1493        assert_eq!(client_room.room_id(), room_id);
1494        assert_eq!(client_room.state(), RoomState::Invited);
1495
1496        // And it is added to the list of invited rooms, not the joined ones
1497        assert!(!sync_resp.rooms.invite[room_id].invite_state.is_empty());
1498        assert!(!sync_resp.rooms.join.contains_key(room_id));
1499    }
1500
1501    #[async_test]
1502    async fn test_avatar_is_found_in_invitation_room_when_processing_sliding_sync_response() {
1503        // Given a logged-in client
1504        let client = logged_in_base_client(None).await;
1505        let room_id = room_id!("!r:e.uk");
1506        let user_id = user_id!("@u:e.uk");
1507
1508        // When I send sliding sync response containing an invited room with an avatar
1509        let mut room = room_with_avatar(mxc_uri!("mxc://e.uk/med1"), user_id);
1510        set_room_invited(&mut room, user_id, user_id);
1511        let response = response_with_room(room_id, room);
1512        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1513
1514        // Then the room in the client has the avatar
1515        let client_room = client.get_room(room_id).expect("No room found");
1516        assert_eq!(
1517            client_room.avatar_url().expect("No avatar URL").media_id().expect("No media ID"),
1518            "med1"
1519        );
1520    }
1521
1522    #[async_test]
1523    async fn test_canonical_alias_is_found_in_invitation_room_when_processing_sliding_sync_response(
1524    ) {
1525        // Given a logged-in client
1526        let client = logged_in_base_client(None).await;
1527        let room_id = room_id!("!r:e.uk");
1528        let user_id = user_id!("@u:e.uk");
1529        let room_alias_id = room_alias_id!("#myroom:e.uk");
1530
1531        // When I send sliding sync response containing an invited room with an avatar
1532        let mut room = room_with_canonical_alias(room_alias_id, user_id);
1533        set_room_invited(&mut room, user_id, user_id);
1534        let response = response_with_room(room_id, room);
1535        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1536
1537        // Then the room in the client has the avatar
1538        let client_room = client.get_room(room_id).expect("No room found");
1539        assert_eq!(client_room.canonical_alias(), Some(room_alias_id.to_owned()));
1540    }
1541
1542    #[async_test]
1543    async fn test_display_name_from_sliding_sync_doesnt_override_alias() {
1544        // Given a logged-in client
1545        let client = logged_in_base_client(None).await;
1546        let room_id = room_id!("!r:e.uk");
1547        let user_id = user_id!("@u:e.uk");
1548        let room_alias_id = room_alias_id!("#myroom:e.uk");
1549
1550        // When the sliding sync response contains an explicit room name as well as an
1551        // alias
1552        let mut room = room_with_canonical_alias(room_alias_id, user_id);
1553        room.name = Some("This came from the server".to_owned());
1554        let response = response_with_room(room_id, room);
1555        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1556
1557        // Then the room's name is NOT overridden by the server-computed display name.
1558        let client_room = client.get_room(room_id).expect("No room found");
1559        assert_eq!(client_room.compute_display_name().await.unwrap().to_string(), "myroom");
1560        assert!(client_room.name().is_none());
1561    }
1562
1563    #[async_test]
1564    async fn test_compute_heroes_from_sliding_sync() {
1565        // Given a logged-in client
1566        let client = logged_in_base_client(None).await;
1567        let room_id = room_id!("!r:e.uk");
1568        let gordon = user_id!("@gordon:e.uk").to_owned();
1569        let alice = user_id!("@alice:e.uk").to_owned();
1570
1571        // When I send sliding sync response containing a room (with identifiable data
1572        // in `heroes`)
1573        let mut room = http::response::Room::new();
1574        room.heroes = Some(vec![
1575            assign!(http::response::Hero::new(gordon), {
1576                name: Some("Gordon".to_owned()),
1577            }),
1578            assign!(http::response::Hero::new(alice), {
1579                name: Some("Alice".to_owned()),
1580                avatar: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1581            }),
1582        ]);
1583        let response = response_with_room(room_id, room);
1584        let _sync_resp =
1585            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1586
1587        // Then the room appears in the client.
1588        let client_room = client.get_room(room_id).expect("No room found");
1589        assert_eq!(client_room.room_id(), room_id);
1590        assert_eq!(client_room.state(), RoomState::Joined);
1591
1592        // And heroes are part of the summary.
1593        assert_eq!(
1594            client_room.clone_info().summary.heroes(),
1595            &[
1596                RoomHero {
1597                    user_id: owned_user_id!("@gordon:e.uk"),
1598                    display_name: Some("Gordon".to_owned()),
1599                    avatar_url: None
1600                },
1601                RoomHero {
1602                    user_id: owned_user_id!("@alice:e.uk"),
1603                    display_name: Some("Alice".to_owned()),
1604                    avatar_url: Some(owned_mxc_uri!("mxc://e.uk/med1"))
1605                },
1606            ]
1607        );
1608    }
1609
1610    #[async_test]
1611    async fn test_last_event_from_sliding_sync_is_cached() {
1612        // Given a logged-in client
1613        let client = logged_in_base_client(None).await;
1614        let room_id = room_id!("!r:e.uk");
1615        let event_a = json!({
1616            "sender":"@alice:example.com",
1617            "type":"m.room.message",
1618            "event_id": "$ida",
1619            "origin_server_ts": 12344446,
1620            "content":{"body":"A", "msgtype": "m.text"}
1621        });
1622        let event_b = json!({
1623            "sender":"@alice:example.com",
1624            "type":"m.room.message",
1625            "event_id": "$idb",
1626            "origin_server_ts": 12344447,
1627            "content":{"body":"B", "msgtype": "m.text"}
1628        });
1629
1630        // When the sliding sync response contains a timeline
1631        let events = &[event_a, event_b.clone()];
1632        let room = room_with_timeline(events);
1633        let response = response_with_room(room_id, room);
1634        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1635
1636        // Then the room holds the latest event
1637        let client_room = client.get_room(room_id).expect("No room found");
1638        assert_eq!(
1639            ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1640            "$idb"
1641        );
1642    }
1643
1644    #[async_test]
1645    async fn test_last_knock_event_from_sliding_sync_is_cached_if_user_has_permissions() {
1646        let own_user_id = user_id!("@me:e.uk");
1647        // Given a logged-in client
1648        let client = logged_in_base_client(Some(own_user_id)).await;
1649        let room_id = room_id!("!r:e.uk");
1650
1651        // Give the current user invite or kick permissions in this room
1652        let power_levels = json!({
1653            "sender":"@alice:example.com",
1654            "state_key":"",
1655            "type":"m.room.power_levels",
1656            "event_id": "$idb",
1657            "origin_server_ts": 12344445,
1658            "content":{ "invite": 100, "kick": 100, "users": { own_user_id: 100 } },
1659            "room_id": room_id,
1660        });
1661
1662        // And a knock member state event
1663        let knock_event = json!({
1664            "sender":"@alice:example.com",
1665            "state_key":"@alice:example.com",
1666            "type":"m.room.member",
1667            "event_id": "$ida",
1668            "origin_server_ts": 12344446,
1669            "content":{"membership": "knock"},
1670            "room_id": room_id,
1671        });
1672
1673        // When the sliding sync response contains a timeline
1674        let events = &[knock_event];
1675        let mut room = room_with_timeline(events);
1676        room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1677        let response = response_with_room(room_id, room);
1678        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1679
1680        // Then the room holds the latest knock state event
1681        let client_room = client.get_room(room_id).expect("No room found");
1682        assert_eq!(
1683            ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1684            "$ida"
1685        );
1686    }
1687
1688    #[async_test]
1689    async fn test_last_knock_event_from_sliding_sync_is_not_cached_without_permissions() {
1690        let own_user_id = user_id!("@me:e.uk");
1691        // Given a logged-in client
1692        let client = logged_in_base_client(Some(own_user_id)).await;
1693        let room_id = room_id!("!r:e.uk");
1694
1695        // Set the user as a user with no permission to invite or kick other users in
1696        // this room
1697        let power_levels = json!({
1698            "sender":"@alice:example.com",
1699            "state_key":"",
1700            "type":"m.room.power_levels",
1701            "event_id": "$idb",
1702            "origin_server_ts": 12344445,
1703            "content":{ "invite": 50, "kick": 50, "users": { own_user_id: 0 } },
1704            "room_id": room_id,
1705        });
1706
1707        // And a knock member state event
1708        let knock_event = json!({
1709            "sender":"@alice:example.com",
1710            "state_key":"@alice:example.com",
1711            "type":"m.room.member",
1712            "event_id": "$ida",
1713            "origin_server_ts": 12344446,
1714            "content":{"membership": "knock"},
1715            "room_id": room_id,
1716        });
1717
1718        // When the sliding sync response contains a timeline
1719        let events = &[knock_event];
1720        let mut room = room_with_timeline(events);
1721        room.required_state.push(Raw::new(&power_levels).unwrap().cast());
1722        let response = response_with_room(room_id, room);
1723        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1724
1725        // Then the room doesn't hold the knock state event as the latest event
1726        let client_room = client.get_room(room_id).expect("No room found");
1727        assert!(client_room.latest_event().is_none());
1728    }
1729
1730    #[async_test]
1731    async fn test_last_non_knock_member_state_event_from_sliding_sync_is_not_cached() {
1732        // Given a logged-in client
1733        let client = logged_in_base_client(None).await;
1734        let room_id = room_id!("!r:e.uk");
1735        // And a join member state event
1736        let join_event = json!({
1737            "sender":"@alice:example.com",
1738            "state_key":"@alice:example.com",
1739            "type":"m.room.member",
1740            "event_id": "$ida",
1741            "origin_server_ts": 12344446,
1742            "content":{"membership": "join"},
1743            "room_id": room_id,
1744        });
1745
1746        // When the sliding sync response contains a timeline
1747        let events = &[join_event];
1748        let room = room_with_timeline(events);
1749        let response = response_with_room(room_id, room);
1750        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1751
1752        // Then the room doesn't hold the join state event as the latest event
1753        let client_room = client.get_room(room_id).expect("No room found");
1754        assert!(client_room.latest_event().is_none());
1755    }
1756
1757    #[async_test]
1758    async fn test_cached_latest_event_can_be_redacted() {
1759        // Given a logged-in client
1760        let client = logged_in_base_client(None).await;
1761        let room_id = room_id!("!r:e.uk");
1762        let event_a = json!({
1763            "sender": "@alice:example.com",
1764            "type": "m.room.message",
1765            "event_id": "$ida",
1766            "origin_server_ts": 12344446,
1767            "content": { "body":"A", "msgtype": "m.text" },
1768        });
1769
1770        // When the sliding sync response contains a timeline
1771        let room = room_with_timeline(&[event_a]);
1772        let response = response_with_room(room_id, room);
1773        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1774
1775        // Then the room holds the latest event
1776        let client_room = client.get_room(room_id).expect("No room found");
1777        assert_eq!(
1778            ev_id(client_room.latest_event().map(|latest_event| latest_event.event().clone())),
1779            "$ida"
1780        );
1781
1782        let redaction = json!({
1783            "sender": "@alice:example.com",
1784            "type": "m.room.redaction",
1785            "event_id": "$idb",
1786            "redacts": "$ida",
1787            "origin_server_ts": 12344448,
1788            "content": {},
1789        });
1790
1791        // When a redaction for that event is received
1792        let room = room_with_timeline(&[redaction]);
1793        let response = response_with_room(room_id, room);
1794        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
1795
1796        // Then the room still holds the latest event
1797        let client_room = client.get_room(room_id).expect("No room found");
1798        let latest_event = client_room.latest_event().unwrap();
1799        assert_eq!(latest_event.event_id().unwrap(), "$ida");
1800
1801        // But it's now redacted
1802        assert_matches!(
1803            latest_event.event().raw().deserialize().unwrap(),
1804            AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
1805                SyncRoomMessageEvent::Redacted(_)
1806            ))
1807        );
1808    }
1809
1810    #[cfg(feature = "e2e-encryption")]
1811    #[async_test]
1812    async fn test_when_no_events_we_dont_cache_any() {
1813        let events = &[];
1814        let chosen = choose_event_to_cache(events).await;
1815        assert!(chosen.is_none());
1816    }
1817
1818    #[cfg(feature = "e2e-encryption")]
1819    #[async_test]
1820    async fn test_when_only_one_event_we_cache_it() {
1821        let event1 = make_event("m.room.message", "$1");
1822        let events = &[event1.clone()];
1823        let chosen = choose_event_to_cache(events).await;
1824        assert_eq!(ev_id(chosen), rawev_id(event1));
1825    }
1826
1827    #[cfg(feature = "e2e-encryption")]
1828    #[async_test]
1829    async fn test_with_multiple_events_we_cache_the_last_one() {
1830        let event1 = make_event("m.room.message", "$1");
1831        let event2 = make_event("m.room.message", "$2");
1832        let events = &[event1, event2.clone()];
1833        let chosen = choose_event_to_cache(events).await;
1834        assert_eq!(ev_id(chosen), rawev_id(event2));
1835    }
1836
1837    #[cfg(feature = "e2e-encryption")]
1838    #[async_test]
1839    async fn test_cache_the_latest_relevant_event_and_ignore_irrelevant_ones_even_if_later() {
1840        let event1 = make_event("m.room.message", "$1");
1841        let event2 = make_event("m.room.message", "$2");
1842        let event3 = make_event("m.room.powerlevels", "$3");
1843        let event4 = make_event("m.room.powerlevels", "$5");
1844        let events = &[event1, event2.clone(), event3, event4];
1845        let chosen = choose_event_to_cache(events).await;
1846        assert_eq!(ev_id(chosen), rawev_id(event2));
1847    }
1848
1849    #[cfg(feature = "e2e-encryption")]
1850    #[async_test]
1851    async fn test_prefer_to_cache_nothing_rather_than_irrelevant_events() {
1852        let event1 = make_event("m.room.power_levels", "$1");
1853        let events = &[event1];
1854        let chosen = choose_event_to_cache(events).await;
1855        assert!(chosen.is_none());
1856    }
1857
1858    #[cfg(feature = "e2e-encryption")]
1859    #[async_test]
1860    async fn test_cache_encrypted_events_that_are_after_latest_message() {
1861        // Given two message events followed by two encrypted
1862        let event1 = make_event("m.room.message", "$1");
1863        let event2 = make_event("m.room.message", "$2");
1864        let event3 = make_encrypted_event("$3");
1865        let event4 = make_encrypted_event("$4");
1866        let events = &[event1, event2.clone(), event3.clone(), event4.clone()];
1867
1868        // When I ask to cache events
1869        let room = make_room();
1870        let mut room_info = room.clone_info();
1871        cache_latest_events(&room, &mut room_info, events, None, None).await;
1872
1873        // The latest message is stored
1874        assert_eq!(
1875            ev_id(room_info.latest_event.as_ref().map(|latest_event| latest_event.event().clone())),
1876            rawev_id(event2.clone())
1877        );
1878
1879        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1880        assert_eq!(
1881            ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1882            rawev_id(event2)
1883        );
1884
1885        // And also the two encrypted ones
1886        assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event4]));
1887    }
1888
1889    #[cfg(feature = "e2e-encryption")]
1890    #[async_test]
1891    async fn test_dont_cache_encrypted_events_that_are_before_latest_message() {
1892        // Given an encrypted event before and after the message
1893        let event1 = make_encrypted_event("$1");
1894        let event2 = make_event("m.room.message", "$2");
1895        let event3 = make_encrypted_event("$3");
1896        let events = &[event1, event2.clone(), event3.clone()];
1897
1898        // When I ask to cache events
1899        let room = make_room();
1900        let mut room_info = room.clone_info();
1901        cache_latest_events(&room, &mut room_info, events, None, None).await;
1902        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1903
1904        // The latest message is stored
1905        assert_eq!(
1906            ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1907            rawev_id(event2)
1908        );
1909
1910        // And also the encrypted one that was after it, but not the one before
1911        assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3]));
1912    }
1913
1914    #[cfg(feature = "e2e-encryption")]
1915    #[async_test]
1916    async fn test_skip_irrelevant_events_eg_receipts_even_if_after_message() {
1917        // Given two message events followed by two encrypted, with a receipt in the
1918        // middle
1919        let event1 = make_event("m.room.message", "$1");
1920        let event2 = make_event("m.room.message", "$2");
1921        let event3 = make_encrypted_event("$3");
1922        let event4 = make_event("m.read", "$4");
1923        let event5 = make_encrypted_event("$5");
1924        let events = &[event1, event2.clone(), event3.clone(), event4, event5.clone()];
1925
1926        // When I ask to cache events
1927        let room = make_room();
1928        let mut room_info = room.clone_info();
1929        cache_latest_events(&room, &mut room_info, events, None, None).await;
1930        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1931
1932        // The latest message is stored, ignoring the receipt
1933        assert_eq!(
1934            ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1935            rawev_id(event2)
1936        );
1937
1938        // The two encrypted ones are stored, but not the receipt
1939        assert_eq!(rawevs_ids(&room.latest_encrypted_events), evs_ids(&[event3, event5]));
1940    }
1941
1942    #[cfg(feature = "e2e-encryption")]
1943    #[async_test]
1944    async fn test_only_store_the_max_number_of_encrypted_events() {
1945        // Given two message events followed by lots of encrypted and other irrelevant
1946        // events
1947        let evente = make_event("m.room.message", "$e");
1948        let eventd = make_event("m.room.message", "$d");
1949        let eventc = make_encrypted_event("$c");
1950        let event9 = make_encrypted_event("$9");
1951        let event8 = make_encrypted_event("$8");
1952        let event7 = make_encrypted_event("$7");
1953        let eventb = make_event("m.read", "$b");
1954        let event6 = make_encrypted_event("$6");
1955        let event5 = make_encrypted_event("$5");
1956        let event4 = make_encrypted_event("$4");
1957        let event3 = make_encrypted_event("$3");
1958        let event2 = make_encrypted_event("$2");
1959        let eventa = make_event("m.read", "$a");
1960        let event1 = make_encrypted_event("$1");
1961        let event0 = make_encrypted_event("$0");
1962        let events = &[
1963            evente,
1964            eventd.clone(),
1965            eventc,
1966            event9.clone(),
1967            event8.clone(),
1968            event7.clone(),
1969            eventb,
1970            event6.clone(),
1971            event5.clone(),
1972            event4.clone(),
1973            event3.clone(),
1974            event2.clone(),
1975            eventa,
1976            event1.clone(),
1977            event0.clone(),
1978        ];
1979
1980        // When I ask to cache events
1981        let room = make_room();
1982        let mut room_info = room.clone_info();
1983        cache_latest_events(&room, &mut room_info, events, None, None).await;
1984        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
1985
1986        // The latest message is stored, ignoring encrypted and receipts
1987        assert_eq!(
1988            ev_id(room.latest_event().map(|latest_event| latest_event.event().clone())),
1989            rawev_id(eventd)
1990        );
1991
1992        // Only 10 encrypted are stored, even though there were more
1993        assert_eq!(
1994            rawevs_ids(&room.latest_encrypted_events),
1995            evs_ids(&[
1996                event9, event8, event7, event6, event5, event4, event3, event2, event1, event0
1997            ])
1998        );
1999    }
2000
2001    #[cfg(feature = "e2e-encryption")]
2002    #[async_test]
2003    async fn test_dont_overflow_capacity_if_previous_encrypted_events_exist() {
2004        // Given a RoomInfo with lots of encrypted events already inside it
2005        let room = make_room();
2006        let mut room_info = room.clone_info();
2007        cache_latest_events(
2008            &room,
2009            &mut room_info,
2010            &[
2011                make_encrypted_event("$0"),
2012                make_encrypted_event("$1"),
2013                make_encrypted_event("$2"),
2014                make_encrypted_event("$3"),
2015                make_encrypted_event("$4"),
2016                make_encrypted_event("$5"),
2017                make_encrypted_event("$6"),
2018                make_encrypted_event("$7"),
2019                make_encrypted_event("$8"),
2020                make_encrypted_event("$9"),
2021            ],
2022            None,
2023            None,
2024        )
2025        .await;
2026        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2027
2028        // Sanity: room_info has 10 encrypted events inside it
2029        assert_eq!(room.latest_encrypted_events.read().unwrap().len(), 10);
2030
2031        // When I ask to cache more encrypted events
2032        let eventa = make_encrypted_event("$a");
2033        let mut room_info = room.clone_info();
2034        cache_latest_events(&room, &mut room_info, &[eventa], None, None).await;
2035        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2036
2037        // The oldest event is gone
2038        assert!(!rawevs_ids(&room.latest_encrypted_events).contains(&"$0".to_owned()));
2039
2040        // The newest event is last in the list
2041        assert_eq!(rawevs_ids(&room.latest_encrypted_events)[9], "$a");
2042    }
2043
2044    #[cfg(feature = "e2e-encryption")]
2045    #[async_test]
2046    async fn test_existing_encrypted_events_are_deleted_if_we_receive_unencrypted() {
2047        // Given a RoomInfo with some encrypted events already inside it
2048        let room = make_room();
2049        let mut room_info = room.clone_info();
2050        cache_latest_events(
2051            &room,
2052            &mut room_info,
2053            &[make_encrypted_event("$0"), make_encrypted_event("$1"), make_encrypted_event("$2")],
2054            None,
2055            None,
2056        )
2057        .await;
2058        room.set_room_info(room_info.clone(), RoomInfoNotableUpdateReasons::empty());
2059
2060        // When I ask to cache an unencrypted event, and some more encrypted events
2061        let eventa = make_event("m.room.message", "$a");
2062        let eventb = make_encrypted_event("$b");
2063        cache_latest_events(&room, &mut room_info, &[eventa, eventb], None, None).await;
2064        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2065
2066        // The only encrypted events stored are the ones after the decrypted one
2067        assert_eq!(rawevs_ids(&room.latest_encrypted_events), &["$b"]);
2068
2069        // The decrypted one is stored as the latest
2070        assert_eq!(rawev_id(room.latest_event().unwrap().event().clone()), "$a");
2071    }
2072
2073    #[async_test]
2074    async fn test_recency_stamp_is_found_when_processing_sliding_sync_response() {
2075        // Given a logged-in client
2076        let client = logged_in_base_client(None).await;
2077        let room_id = room_id!("!r:e.uk");
2078
2079        // When I send sliding sync response containing a room with a recency stamp
2080        let room = assign!(http::response::Room::new(), {
2081            bump_stamp: Some(42u32.into()),
2082        });
2083        let response = response_with_room(room_id, room);
2084        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2085
2086        // Then the room in the client has the recency stamp
2087        let client_room = client.get_room(room_id).expect("No room found");
2088        assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2089    }
2090
2091    #[async_test]
2092    async fn test_recency_stamp_can_be_overwritten_when_present_in_a_sliding_sync_response() {
2093        // Given a logged-in client
2094        let client = logged_in_base_client(None).await;
2095        let room_id = room_id!("!r:e.uk");
2096
2097        {
2098            // When I send sliding sync response containing a room with a recency stamp
2099            let room = assign!(http::response::Room::new(), {
2100                bump_stamp: Some(42u32.into()),
2101            });
2102            let response = response_with_room(room_id, room);
2103            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2104
2105            // Then the room in the client has the recency stamp
2106            let client_room = client.get_room(room_id).expect("No room found");
2107            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2108        }
2109
2110        {
2111            // When I send sliding sync response containing a room with NO recency stamp
2112            let room = assign!(http::response::Room::new(), {
2113                bump_stamp: None,
2114            });
2115            let response = response_with_room(room_id, room);
2116            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2117
2118            // Then the room in the client has the previous recency stamp
2119            let client_room = client.get_room(room_id).expect("No room found");
2120            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 42);
2121        }
2122
2123        {
2124            // When I send sliding sync response containing a room with a NEW recency
2125            // timestamp
2126            let room = assign!(http::response::Room::new(), {
2127                bump_stamp: Some(153u32.into()),
2128            });
2129            let response = response_with_room(room_id, room);
2130            client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2131
2132            // Then the room in the client has the recency stamp
2133            let client_room = client.get_room(room_id).expect("No room found");
2134            assert_eq!(client_room.recency_stamp().expect("No recency stamp"), 153);
2135        }
2136    }
2137
2138    #[async_test]
2139    async fn test_recency_stamp_can_trigger_a_notable_update_reason() {
2140        // Given a logged-in client
2141        let client = logged_in_base_client(None).await;
2142        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2143        let room_id = room_id!("!r:e.uk");
2144
2145        // When I send sliding sync response containing a room with a recency stamp.
2146        let room = assign!(http::response::Room::new(), {
2147            bump_stamp: Some(42u32.into()),
2148        });
2149        let response = response_with_room(room_id, room);
2150        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2151
2152        // Then a room info notable update is NOT received, because it's the first time
2153        // the room is seen.
2154        assert_matches!(
2155            room_info_notable_update_stream.recv().await,
2156            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2157                assert_eq!(received_room_id, room_id);
2158                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2159            }
2160        );
2161
2162        // When I send sliding sync response containing a room with a recency stamp.
2163        let room = assign!(http::response::Room::new(), {
2164            bump_stamp: Some(43u32.into()),
2165        });
2166        let response = response_with_room(room_id, room);
2167        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2168
2169        // Then a room info notable update is received.
2170        assert_matches!(
2171            room_info_notable_update_stream.recv().await,
2172            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2173                assert_eq!(received_room_id, room_id);
2174                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::RECENCY_STAMP));
2175            }
2176        );
2177    }
2178
2179    #[async_test]
2180    async fn test_read_receipt_can_trigger_a_notable_update_reason() {
2181        // Given a logged-in client
2182        let client = logged_in_base_client(None).await;
2183        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2184
2185        // When I send sliding sync response containing a new room.
2186        let room_id = room_id!("!r:e.uk");
2187        let room = http::response::Room::new();
2188        let response = response_with_room(room_id, room);
2189        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2190
2191        // Then a room info notable update is NOT received.
2192        assert_matches!(
2193            room_info_notable_update_stream.recv().await,
2194            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2195                assert_eq!(received_room_id, room_id);
2196                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::READ_RECEIPT));
2197            }
2198        );
2199
2200        // When I send sliding sync response containing a couple of events with no read
2201        // receipt.
2202        let room_id = room_id!("!r:e.uk");
2203        let events = vec![
2204            make_raw_event("m.room.message", "$3"),
2205            make_raw_event("m.room.message", "$4"),
2206            make_raw_event("m.read", "$5"),
2207        ];
2208        let room = assign!(http::response::Room::new(), {
2209            timeline: events,
2210        });
2211        let response = response_with_room(room_id, room);
2212        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2213
2214        // Then a room info notable update is received.
2215        assert_matches!(
2216            room_info_notable_update_stream.recv().await,
2217            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2218                assert_eq!(received_room_id, room_id);
2219                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::READ_RECEIPT));
2220            }
2221        );
2222    }
2223
2224    #[async_test]
2225    async fn test_leaving_room_can_trigger_a_notable_update_reason() {
2226        // Given a logged-in client
2227        let client = logged_in_base_client(None).await;
2228        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2229
2230        // When I send sliding sync response containing a new room.
2231        let room_id = room_id!("!r:e.uk");
2232        let room = http::response::Room::new();
2233        let response = response_with_room(room_id, room);
2234        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2235
2236        // Discard first room info update
2237        let _ = room_info_notable_update_stream.recv().await;
2238
2239        // Send sliding sync response containing a membership event with 'join' value.
2240        let room_id = room_id!("!r:e.uk");
2241        let events = vec![Raw::from_json_string(
2242            json!({
2243                "type": "m.room.member",
2244                "event_id": "$3",
2245                "content": { "membership": "join" },
2246                "sender": "@u:h.uk",
2247                "origin_server_ts": 12344445,
2248                "state_key": "@u:e.uk",
2249            })
2250            .to_string(),
2251        )
2252        .unwrap()];
2253        let room = assign!(http::response::Room::new(), {
2254            required_state: events,
2255        });
2256        let response = response_with_room(room_id, room);
2257        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2258
2259        // Room was already joined, no MEMBERSHIP update should be triggered here
2260        assert_matches!(
2261            room_info_notable_update_stream.recv().await,
2262            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2263                assert_eq!(received_room_id, room_id);
2264                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2265            }
2266        );
2267
2268        let events = vec![Raw::from_json_string(
2269            json!({
2270                "type": "m.room.member",
2271                "event_id": "$3",
2272                "content": { "membership": "leave" },
2273                "sender": "@u:h.uk",
2274                "origin_server_ts": 12344445,
2275                "state_key": "@u:e.uk",
2276            })
2277            .to_string(),
2278        )
2279        .unwrap()];
2280        let room = assign!(http::response::Room::new(), {
2281            required_state: events,
2282        });
2283        let response = response_with_room(room_id, room);
2284        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2285
2286        // Then a room info notable update is received.
2287        let update = room_info_notable_update_stream.recv().await;
2288        assert_matches!(
2289            update,
2290            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2291                assert_eq!(received_room_id, room_id);
2292                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::MEMBERSHIP));
2293            }
2294        );
2295    }
2296
2297    #[async_test]
2298    async fn test_unread_marker_can_trigger_a_notable_update_reason() {
2299        // Given a logged-in client,
2300        let client = logged_in_base_client(None).await;
2301        let mut room_info_notable_update_stream = client.room_info_notable_update_receiver();
2302
2303        // When I receive a sliding sync response containing a new room,
2304        let room_id = room_id!("!r:e.uk");
2305        let room = http::response::Room::new();
2306        let response = response_with_room(room_id, room);
2307        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2308
2309        // Then a room info notable update is NOT received.
2310        assert_matches!(
2311            room_info_notable_update_stream.recv().await,
2312            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2313                assert_eq!(received_room_id, room_id);
2314                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2315            }
2316        );
2317
2318        // When I receive a sliding sync response containing one update about an unread
2319        // marker,
2320        let room_id = room_id!("!r:e.uk");
2321        let room_account_data_events = vec![Raw::from_json_string(
2322            json!({
2323                "type": "com.famedly.marked_unread",
2324                "event_id": "$1",
2325                "content": { "unread": true },
2326                "sender": client.session_meta().unwrap().user_id,
2327                "origin_server_ts": 12344445,
2328            })
2329            .to_string(),
2330        )
2331        .unwrap()];
2332        let mut response = response_with_room(room_id, http::response::Room::new());
2333        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2334
2335        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2336
2337        // Then a room info notable update is received.
2338        assert_matches!(
2339            room_info_notable_update_stream.recv().await,
2340            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2341                assert_eq!(received_room_id, room_id);
2342                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER), "{received_reasons:?}");
2343            }
2344        );
2345
2346        // But getting it again won't trigger a new notable update…
2347        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2348
2349        assert_matches!(
2350            room_info_notable_update_stream.recv().await,
2351            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2352                assert_eq!(received_room_id, room_id);
2353                assert!(!received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2354            }
2355        );
2356
2357        // …Unless its value changes!
2358        let room_account_data_events = vec![Raw::from_json_string(
2359            json!({
2360                "type": "com.famedly.marked_unread",
2361                "event_id": "$1",
2362                "content": { "unread": false },
2363                "sender": client.session_meta().unwrap().user_id,
2364                "origin_server_ts": 12344445,
2365            })
2366            .to_string(),
2367        )
2368        .unwrap()];
2369        response.extensions.account_data.rooms.insert(room_id.to_owned(), room_account_data_events);
2370        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2371
2372        assert_matches!(
2373            room_info_notable_update_stream.recv().await,
2374            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons: received_reasons }) => {
2375                assert_eq!(received_room_id, room_id);
2376                assert!(received_reasons.contains(RoomInfoNotableUpdateReasons::UNREAD_MARKER));
2377            }
2378        );
2379    }
2380
2381    #[async_test]
2382    async fn test_pinned_events_are_updated_on_sync() {
2383        let user_a_id = user_id!("@a:e.uk");
2384        let client = logged_in_base_client(Some(user_a_id)).await;
2385        let room_id = room_id!("!r:e.uk");
2386        let pinned_event_id = owned_event_id!("$an-id:e.uk");
2387
2388        // Create room
2389        let mut room_response = http::response::Room::new();
2390        set_room_joined(&mut room_response, user_a_id);
2391        let response = response_with_room(room_id, room_response);
2392        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2393
2394        // The newly created room has no pinned event ids
2395        let room = client.get_room(room_id).unwrap();
2396        let pinned_event_ids = room.pinned_event_ids();
2397        assert_matches!(pinned_event_ids, None);
2398
2399        // Load new pinned event id
2400        let mut room_response = http::response::Room::new();
2401        room_response.required_state.push(make_state_event(
2402            user_a_id,
2403            "",
2404            RoomPinnedEventsEventContent::new(vec![pinned_event_id.clone()]),
2405            None,
2406        ));
2407        let response = response_with_room(room_id, room_response);
2408        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2409
2410        let pinned_event_ids = room.pinned_event_ids().unwrap_or_default();
2411        assert_eq!(pinned_event_ids.len(), 1);
2412        assert_eq!(pinned_event_ids[0], pinned_event_id);
2413
2414        // Pinned event ids are now empty
2415        let mut room_response = http::response::Room::new();
2416        room_response.required_state.push(make_state_event(
2417            user_a_id,
2418            "",
2419            RoomPinnedEventsEventContent::new(Vec::new()),
2420            None,
2421        ));
2422        let response = response_with_room(room_id, room_response);
2423        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2424        let pinned_event_ids = room.pinned_event_ids().unwrap();
2425        assert!(pinned_event_ids.is_empty());
2426    }
2427
2428    #[async_test]
2429    async fn test_dms_are_processed_in_any_sync_response() {
2430        let current_user_id = user_id!("@current:e.uk");
2431        let client = logged_in_base_client(Some(current_user_id)).await;
2432        let user_a_id = user_id!("@a:e.uk");
2433        let user_b_id = user_id!("@b:e.uk");
2434        let room_id_1 = room_id!("!r:e.uk");
2435        let room_id_2 = room_id!("!s:e.uk");
2436
2437        let mut room_response = http::response::Room::new();
2438        set_room_joined(&mut room_response, user_a_id);
2439        let mut response = response_with_room(room_id_1, room_response);
2440        let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2441            BTreeMap::new();
2442        direct_content.insert(user_a_id.into(), vec![room_id_1.to_owned()]);
2443        direct_content.insert(user_b_id.into(), vec![room_id_2.to_owned()]);
2444        response
2445            .extensions
2446            .account_data
2447            .global
2448            .push(make_global_account_data_event(DirectEventContent(direct_content)));
2449        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2450
2451        let room_1 = client.get_room(room_id_1).unwrap();
2452        assert!(room_1.is_direct().await.unwrap());
2453
2454        // Now perform a sync without new account data
2455        let mut room_response = http::response::Room::new();
2456        set_room_joined(&mut room_response, user_b_id);
2457        let response = response_with_room(room_id_2, room_response);
2458        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2459
2460        let room_2 = client.get_room(room_id_2).unwrap();
2461        assert!(room_2.is_direct().await.unwrap());
2462    }
2463
2464    #[cfg(feature = "e2e-encryption")]
2465    async fn choose_event_to_cache(events: &[TimelineEvent]) -> Option<TimelineEvent> {
2466        let room = make_room();
2467        let mut room_info = room.clone_info();
2468        cache_latest_events(&room, &mut room_info, events, None, None).await;
2469        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::empty());
2470        room.latest_event().map(|latest_event| latest_event.event().clone())
2471    }
2472
2473    #[cfg(feature = "e2e-encryption")]
2474    fn rawev_id(event: TimelineEvent) -> String {
2475        event.event_id().unwrap().to_string()
2476    }
2477
2478    fn ev_id(event: Option<TimelineEvent>) -> String {
2479        event.unwrap().event_id().unwrap().to_string()
2480    }
2481
2482    #[cfg(feature = "e2e-encryption")]
2483    fn rawevs_ids(events: &Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>) -> Vec<String> {
2484        events.read().unwrap().iter().map(|e| e.get_field("event_id").unwrap().unwrap()).collect()
2485    }
2486
2487    #[cfg(feature = "e2e-encryption")]
2488    fn evs_ids(events: &[TimelineEvent]) -> Vec<String> {
2489        events.iter().map(|e| e.event_id().unwrap().to_string()).collect()
2490    }
2491
2492    #[cfg(feature = "e2e-encryption")]
2493    fn make_room() -> Room {
2494        let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2495
2496        Room::new(
2497            user_id!("@u:e.co"),
2498            Arc::new(MemoryStore::new()),
2499            room_id!("!r:e.co"),
2500            RoomState::Joined,
2501            sender,
2502        )
2503    }
2504
2505    fn make_raw_event(typ: &str, id: &str) -> Raw<AnySyncTimelineEvent> {
2506        Raw::from_json_string(
2507            json!({
2508                "type": typ,
2509                "event_id": id,
2510                "content": { "msgtype": "m.text", "body": "my msg" },
2511                "sender": "@u:h.uk",
2512                "origin_server_ts": 12344445,
2513            })
2514            .to_string(),
2515        )
2516        .unwrap()
2517    }
2518
2519    #[cfg(feature = "e2e-encryption")]
2520    fn make_event(typ: &str, id: &str) -> TimelineEvent {
2521        TimelineEvent::new(make_raw_event(typ, id))
2522    }
2523
2524    #[cfg(feature = "e2e-encryption")]
2525    fn make_encrypted_event(id: &str) -> TimelineEvent {
2526        TimelineEvent::new_utd_event(
2527            Raw::from_json_string(
2528                json!({
2529                    "type": "m.room.encrypted",
2530                    "event_id": id,
2531                    "content": {
2532                        "algorithm": "m.megolm.v1.aes-sha2",
2533                        "ciphertext": "",
2534                        "sender_key": "",
2535                        "device_id": "",
2536                        "session_id": "",
2537                    },
2538                    "sender": "@u:h.uk",
2539                    "origin_server_ts": 12344445,
2540                })
2541                .to_string(),
2542            )
2543            .unwrap(),
2544            UnableToDecryptInfo {
2545                session_id: Some("".to_owned()),
2546                reason: UnableToDecryptReason::MissingMegolmSession { withheld_code: None },
2547            },
2548        )
2549    }
2550
2551    async fn membership(
2552        client: &BaseClient,
2553        room_id: &RoomId,
2554        user_id: &UserId,
2555    ) -> MembershipState {
2556        let room = client.get_room(room_id).expect("Room not found!");
2557        let member = room.get_member(user_id).await.unwrap().expect("B not in room");
2558        member.membership().clone()
2559    }
2560
2561    fn direct_targets(client: &BaseClient, room_id: &RoomId) -> HashSet<OwnedDirectUserIdentifier> {
2562        let room = client.get_room(room_id).expect("Room not found!");
2563        room.direct_targets()
2564    }
2565
2566    /// Create a DM with the other user, setting our membership to Join and
2567    /// theirs to other_state
2568    async fn create_dm(
2569        client: &BaseClient,
2570        room_id: &RoomId,
2571        my_id: &UserId,
2572        their_id: &UserId,
2573        other_state: MembershipState,
2574    ) {
2575        let mut room = http::response::Room::new();
2576        set_room_joined(&mut room, my_id);
2577
2578        match other_state {
2579            MembershipState::Join => {
2580                room.joined_count = Some(uint!(2));
2581                room.invited_count = None;
2582            }
2583
2584            MembershipState::Invite => {
2585                room.joined_count = Some(uint!(1));
2586                room.invited_count = Some(uint!(1));
2587            }
2588
2589            _ => {
2590                room.joined_count = Some(uint!(1));
2591                room.invited_count = None;
2592            }
2593        }
2594
2595        room.required_state.push(make_membership_event(their_id, other_state));
2596
2597        let mut response = response_with_room(room_id, room);
2598        set_direct_with(&mut response, their_id.to_owned(), vec![room_id.to_owned()]);
2599        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2600    }
2601
2602    /// Set this user's membership within this room to new_state
2603    async fn update_room_membership(
2604        client: &BaseClient,
2605        room_id: &RoomId,
2606        user_id: &UserId,
2607        new_state: MembershipState,
2608    ) {
2609        let mut room = http::response::Room::new();
2610        room.required_state.push(make_membership_event(user_id, new_state));
2611        let response = response_with_room(room_id, room);
2612        client.process_sliding_sync(&response, &()).await.expect("Failed to process sync");
2613    }
2614
2615    fn set_direct_with(
2616        response: &mut http::Response,
2617        user_id: OwnedUserId,
2618        room_ids: Vec<OwnedRoomId>,
2619    ) {
2620        let mut direct_content: BTreeMap<OwnedDirectUserIdentifier, Vec<OwnedRoomId>> =
2621            BTreeMap::new();
2622        direct_content.insert(user_id.into(), room_ids);
2623        response
2624            .extensions
2625            .account_data
2626            .global
2627            .push(make_global_account_data_event(DirectEventContent(direct_content)));
2628    }
2629
2630    fn response_with_room(room_id: &RoomId, room: http::response::Room) -> http::Response {
2631        let mut response = http::Response::new("5".to_owned());
2632        response.rooms.insert(room_id.to_owned(), room);
2633        response
2634    }
2635
2636    fn room_with_avatar(avatar_uri: &MxcUri, user_id: &UserId) -> http::response::Room {
2637        let mut room = http::response::Room::new();
2638
2639        let mut avatar_event_content = RoomAvatarEventContent::new();
2640        avatar_event_content.url = Some(avatar_uri.to_owned());
2641
2642        room.required_state.push(make_state_event(user_id, "", avatar_event_content, None));
2643
2644        room
2645    }
2646
2647    fn room_with_canonical_alias(
2648        room_alias_id: &RoomAliasId,
2649        user_id: &UserId,
2650    ) -> http::response::Room {
2651        let mut room = http::response::Room::new();
2652
2653        let mut canonical_alias_event_content = RoomCanonicalAliasEventContent::new();
2654        canonical_alias_event_content.alias = Some(room_alias_id.to_owned());
2655
2656        room.required_state.push(make_state_event(
2657            user_id,
2658            "",
2659            canonical_alias_event_content,
2660            None,
2661        ));
2662
2663        room
2664    }
2665
2666    fn room_with_timeline(events: &[serde_json::Value]) -> http::response::Room {
2667        let mut room = http::response::Room::new();
2668        room.timeline.extend(
2669            events
2670                .iter()
2671                .map(|e| Raw::from_json_string(e.to_string()).unwrap())
2672                .collect::<Vec<_>>(),
2673        );
2674        room
2675    }
2676
2677    fn set_room_name(room: &mut http::response::Room, sender: &UserId, name: String) {
2678        room.required_state.push(make_state_event(
2679            sender,
2680            "",
2681            RoomNameEventContent::new(name),
2682            None,
2683        ));
2684    }
2685
2686    fn set_room_invited(room: &mut http::response::Room, inviter: &UserId, invitee: &UserId) {
2687        // Sliding Sync shows an almost-empty event to indicate that we are invited to a
2688        // room. Just the type is supplied.
2689
2690        let evt = Raw::new(&json!({
2691            "type": "m.room.member",
2692            "sender": inviter,
2693            "content": {
2694                "is_direct": true,
2695                "membership": "invite",
2696            },
2697            "state_key": invitee,
2698        }))
2699        .expect("Failed to make raw event")
2700        .cast();
2701
2702        room.invite_state = Some(vec![evt]);
2703
2704        // We expect that there will also be an invite event in the required_state,
2705        // assuming you've asked for this type of event.
2706        room.required_state.push(make_state_event(
2707            inviter,
2708            invitee.as_str(),
2709            RoomMemberEventContent::new(MembershipState::Invite),
2710            None,
2711        ));
2712    }
2713
2714    fn set_room_knocked(room: &mut http::response::Room, knocker: &UserId) {
2715        // Sliding Sync shows an almost-empty event to indicate that we are invited to a
2716        // room. Just the type is supplied.
2717
2718        let evt = Raw::new(&json!({
2719            "type": "m.room.member",
2720            "sender": knocker,
2721            "content": {
2722                "is_direct": true,
2723                "membership": "knock",
2724            },
2725            "state_key": knocker,
2726        }))
2727        .expect("Failed to make raw event")
2728        .cast();
2729
2730        room.invite_state = Some(vec![evt]);
2731    }
2732
2733    fn set_room_joined(room: &mut http::response::Room, user_id: &UserId) {
2734        room.required_state.push(make_membership_event(user_id, MembershipState::Join));
2735    }
2736
2737    fn set_room_left(room: &mut http::response::Room, user_id: &UserId) {
2738        room.required_state.push(make_membership_event(user_id, MembershipState::Leave));
2739    }
2740
2741    fn set_room_left_as_timeline_event(room: &mut http::response::Room, user_id: &UserId) {
2742        room.timeline.push(make_membership_event(user_id, MembershipState::Leave));
2743    }
2744
2745    fn make_membership_event<K>(user_id: &UserId, state: MembershipState) -> Raw<K> {
2746        make_state_event(user_id, user_id.as_str(), RoomMemberEventContent::new(state), None)
2747    }
2748
2749    fn make_global_account_data_event<C: GlobalAccountDataEventContent, E>(content: C) -> Raw<E> {
2750        Raw::new(&json!({
2751            "type": content.event_type(),
2752            "content": content,
2753        }))
2754        .expect("Failed to create account data event")
2755        .cast()
2756    }
2757
2758    fn make_state_event<C: StateEventContent, E>(
2759        sender: &UserId,
2760        state_key: &str,
2761        content: C,
2762        prev_content: Option<C>,
2763    ) -> Raw<E> {
2764        let unsigned = if let Some(prev_content) = prev_content {
2765            json!({ "prev_content": prev_content })
2766        } else {
2767            json!({})
2768        };
2769
2770        Raw::new(&json!({
2771            "type": content.event_type(),
2772            "state_key": state_key,
2773            "content": content,
2774            "event_id": event_id!("$evt"),
2775            "sender": sender,
2776            "origin_server_ts": 10,
2777            "unsigned": unsigned,
2778        }))
2779        .expect("Failed to create state event")
2780        .cast()
2781    }
2782}