matrix_sdk_base/
sliding_sync.rs

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