matrix_sdk_base/
sliding_sync.rs

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