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