matrix_sdk_base/rooms/
normal.rs

1// Copyright 2020 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#[cfg(feature = "e2e-encryption")]
16use std::sync::RwLock as SyncRwLock;
17use std::{
18    collections::{BTreeMap, BTreeSet, HashSet},
19    mem,
20    sync::{atomic::AtomicBool, Arc},
21};
22
23use as_variant::as_variant;
24use bitflags::bitflags;
25use eyeball::{AsyncLock, ObservableWriteGuard, SharedObservable, Subscriber};
26use futures_util::{Stream, StreamExt};
27use matrix_sdk_common::deserialized_responses::TimelineEventKind;
28#[cfg(feature = "e2e-encryption")]
29use matrix_sdk_common::ring_buffer::RingBuffer;
30use ruma::{
31    api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
32    events::{
33        call::member::{CallMemberStateKey, MembershipData},
34        direct::OwnedDirectUserIdentifier,
35        ignored_user_list::IgnoredUserListEventContent,
36        member_hints::MemberHintsEventContent,
37        receipt::{Receipt, ReceiptThread, ReceiptType},
38        room::{
39            avatar::{self, RoomAvatarEventContent},
40            encryption::RoomEncryptionEventContent,
41            guest_access::GuestAccess,
42            history_visibility::HistoryVisibility,
43            join_rules::JoinRule,
44            member::{MembershipState, RoomMemberEventContent},
45            pinned_events::RoomPinnedEventsEventContent,
46            power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
47            redaction::SyncRoomRedactionEvent,
48            tombstone::RoomTombstoneEventContent,
49        },
50        tag::{TagEventContent, Tags},
51        AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
52        RoomAccountDataEventType, StateEventType, SyncStateEvent,
53    },
54    room::RoomType,
55    serde::Raw,
56    EventId, MxcUri, OwnedEventId, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedUserId,
57    RoomAliasId, RoomId, RoomVersionId, UserId,
58};
59use serde::{Deserialize, Serialize};
60use tokio::sync::broadcast;
61use tracing::{debug, field::debug, info, instrument, trace, warn};
62
63use super::{
64    members::MemberRoomInfo, BaseRoomInfo, RoomCreateWithCreatorEventContent, RoomDisplayName,
65    RoomMember, RoomNotableTags,
66};
67use crate::{
68    deserialized_responses::{
69        DisplayName, MemberEvent, RawMemberEvent, RawSyncOrStrippedState, SyncOrStrippedState,
70    },
71    latest_event::LatestEvent,
72    notification_settings::RoomNotificationMode,
73    read_receipts::RoomReadReceipts,
74    store::{DynStateStore, Result as StoreResult, StateStoreExt},
75    sync::UnreadNotificationsCount,
76    Error, MinimalStateEvent, OriginalMinimalStateEvent, RoomMemberships, StateStoreDataKey,
77    StateStoreDataValue, StoreError,
78};
79
80/// Indicates that a notable update of `RoomInfo` has been applied, and why.
81///
82/// A room info notable update is an update that can be interested for other
83/// parts of the code. This mechanism is used in coordination with
84/// [`BaseClient::room_info_notable_update_receiver`][baseclient] (and
85/// `Room::inner` plus `Room::room_info_notable_update_sender`) where `RoomInfo`
86/// can be observed and some of its updates can be spread to listeners.
87///
88/// [baseclient]: crate::BaseClient::room_info_notable_update_receiver
89#[derive(Debug, Clone)]
90pub struct RoomInfoNotableUpdate {
91    /// The room which was updated.
92    pub room_id: OwnedRoomId,
93
94    /// The reason for this update.
95    pub reasons: RoomInfoNotableUpdateReasons,
96}
97
98bitflags! {
99    /// The reason why a [`RoomInfoNotableUpdate`] is emitted.
100    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
101    pub struct RoomInfoNotableUpdateReasons: u8 {
102        /// The recency stamp of the `Room` has changed.
103        const RECENCY_STAMP = 0b0000_0001;
104
105        /// The latest event of the `Room` has changed.
106        const LATEST_EVENT = 0b0000_0010;
107
108        /// A read receipt has changed.
109        const READ_RECEIPT = 0b0000_0100;
110
111        /// The user-controlled unread marker value has changed.
112        const UNREAD_MARKER = 0b0000_1000;
113
114        /// A membership change happened for the current user.
115        const MEMBERSHIP = 0b0001_0000;
116    }
117}
118
119/// The result of a room summary computation.
120///
121/// If the homeserver does not provide a room summary, we perform a best-effort
122/// computation to generate one ourselves. If the homeserver does provide the
123/// summary, we augment it with additional information about the service members
124/// in the room.
125struct ComputedSummary {
126    /// The list of display names that will be used to calculate the room
127    /// display name.
128    heroes: Vec<String>,
129    /// The number of joined service members in the room.
130    num_service_members: u64,
131    /// The number of joined and invited members, not including any service
132    /// members.
133    num_joined_invited_guess: u64,
134}
135
136impl Default for RoomInfoNotableUpdateReasons {
137    fn default() -> Self {
138        Self::empty()
139    }
140}
141
142/// The underlying room data structure collecting state for joined, left and
143/// invited rooms.
144#[derive(Debug, Clone)]
145pub struct Room {
146    /// The room ID.
147    room_id: OwnedRoomId,
148
149    /// Our own user ID.
150    own_user_id: OwnedUserId,
151
152    inner: SharedObservable<RoomInfo>,
153    room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
154    store: Arc<DynStateStore>,
155
156    /// The most recent few encrypted events. When the keys come through to
157    /// decrypt these, the most recent relevant one will replace
158    /// `latest_event`. (We can't tell which one is relevant until
159    /// they are decrypted.)
160    ///
161    /// Currently, these are held in Room rather than RoomInfo, because we were
162    /// not sure whether holding too many of them might make the cache too
163    /// slow to load on startup. Keeping them here means they are not cached
164    /// to disk but held in memory.
165    #[cfg(feature = "e2e-encryption")]
166    pub latest_encrypted_events: Arc<SyncRwLock<RingBuffer<Raw<AnySyncTimelineEvent>>>>,
167
168    /// A map for ids of room membership events in the knocking state linked to
169    /// the user id of the user affected by the member event, that the current
170    /// user has marked as seen so they can be ignored.
171    pub seen_knock_request_ids_map:
172        SharedObservable<Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
173
174    /// A sender that will notify receivers when room member updates happen.
175    pub room_member_updates_sender: broadcast::Sender<RoomMembersUpdate>,
176}
177
178/// The room summary containing member counts and members that should be used to
179/// calculate the room display name.
180#[derive(Clone, Debug, Default, Serialize, Deserialize)]
181pub struct RoomSummary {
182    /// The heroes of the room, members that can be used as a fallback for the
183    /// room's display name or avatar if these haven't been set.
184    ///
185    /// This was called `heroes` and contained raw `String`s of the `UserId`
186    /// before. Following this it was called `heroes_user_ids` and a
187    /// complimentary `heroes_names` existed too; changing the field's name
188    /// helped with avoiding a migration.
189    #[serde(default, skip_serializing_if = "Vec::is_empty")]
190    pub(crate) room_heroes: Vec<RoomHero>,
191    /// The number of members that are considered to be joined to the room.
192    pub(crate) joined_member_count: u64,
193    /// The number of members that are considered to be invited to the room.
194    pub(crate) invited_member_count: u64,
195}
196
197/// Information about a member considered to be a room hero.
198#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
199pub struct RoomHero {
200    /// The user id of the hero.
201    pub user_id: OwnedUserId,
202    /// The display name of the hero.
203    pub display_name: Option<String>,
204    /// The avatar url of the hero.
205    pub avatar_url: Option<OwnedMxcUri>,
206}
207
208#[cfg(test)]
209impl RoomSummary {
210    pub(crate) fn heroes(&self) -> &[RoomHero] {
211        &self.room_heroes
212    }
213}
214
215/// Enum keeping track in which state the room is, e.g. if our own user is
216/// joined, RoomState::Invited, or has left the room.
217#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
218pub enum RoomState {
219    /// The room is in a joined state.
220    Joined,
221    /// The room is in a left state.
222    Left,
223    /// The room is in an invited state.
224    Invited,
225    /// The room is in a knocked state.
226    Knocked,
227    /// The room is in a banned state.
228    Banned,
229}
230
231impl From<&MembershipState> for RoomState {
232    fn from(membership_state: &MembershipState) -> Self {
233        match membership_state {
234            MembershipState::Ban => Self::Banned,
235            MembershipState::Invite => Self::Invited,
236            MembershipState::Join => Self::Joined,
237            MembershipState::Knock => Self::Knocked,
238            MembershipState::Leave => Self::Left,
239            _ => panic!("Unexpected MembershipState: {}", membership_state),
240        }
241    }
242}
243
244/// The number of heroes chosen to compute a room's name, if the room didn't
245/// have a name set by the users themselves.
246///
247/// A server must return at most 5 heroes, according to the paragraph below
248/// https://spec.matrix.org/v1.10/client-server-api/#get_matrixclientv3sync (grep for "heroes"). We
249/// try to behave similarly here.
250const NUM_HEROES: usize = 5;
251
252/// A filter to remove our own user and the users specified in the member hints
253/// state event, so called service members, from the list of heroes.
254///
255/// The heroes will then be used to calculate a display name for the room if one
256/// wasn't explicitly defined.
257fn heroes_filter<'a>(
258    own_user_id: &'a UserId,
259    member_hints: &'a MemberHintsEventContent,
260) -> impl Fn(&UserId) -> bool + use<'a> {
261    move |user_id| user_id != own_user_id && !member_hints.service_members.contains(user_id)
262}
263
264/// The kind of room member updates that just happened.
265#[derive(Debug, Clone)]
266pub enum RoomMembersUpdate {
267    /// The whole list room members was reloaded.
268    FullReload,
269    /// A few members were updated, their user ids are included.
270    Partial(BTreeSet<OwnedUserId>),
271}
272
273impl Room {
274    /// The size of the latest_encrypted_events RingBuffer
275    #[cfg(feature = "e2e-encryption")]
276    const MAX_ENCRYPTED_EVENTS: std::num::NonZeroUsize = std::num::NonZeroUsize::new(10).unwrap();
277
278    pub(crate) fn new(
279        own_user_id: &UserId,
280        store: Arc<DynStateStore>,
281        room_id: &RoomId,
282        room_state: RoomState,
283        room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
284    ) -> Self {
285        let room_info = RoomInfo::new(room_id, room_state);
286        Self::restore(own_user_id, store, room_info, room_info_notable_update_sender)
287    }
288
289    pub(crate) fn restore(
290        own_user_id: &UserId,
291        store: Arc<DynStateStore>,
292        room_info: RoomInfo,
293        room_info_notable_update_sender: broadcast::Sender<RoomInfoNotableUpdate>,
294    ) -> Self {
295        let (room_member_updates_sender, _) = broadcast::channel(10);
296        Self {
297            own_user_id: own_user_id.into(),
298            room_id: room_info.room_id.clone(),
299            store,
300            inner: SharedObservable::new(room_info),
301            #[cfg(feature = "e2e-encryption")]
302            latest_encrypted_events: Arc::new(SyncRwLock::new(RingBuffer::new(
303                Self::MAX_ENCRYPTED_EVENTS,
304            ))),
305            room_info_notable_update_sender,
306            seen_knock_request_ids_map: SharedObservable::new_async(None),
307            room_member_updates_sender,
308        }
309    }
310
311    /// Get the unique room id of the room.
312    pub fn room_id(&self) -> &RoomId {
313        &self.room_id
314    }
315
316    /// Get a copy of the room creator.
317    pub fn creator(&self) -> Option<OwnedUserId> {
318        self.inner.read().creator().map(ToOwned::to_owned)
319    }
320
321    /// Get our own user id.
322    pub fn own_user_id(&self) -> &UserId {
323        &self.own_user_id
324    }
325
326    /// Get the state of the room.
327    pub fn state(&self) -> RoomState {
328        self.inner.read().room_state
329    }
330
331    /// Get the previous state of the room, if it had any.
332    pub fn prev_state(&self) -> Option<RoomState> {
333        self.inner.read().prev_room_state
334    }
335
336    /// Whether this room's [`RoomType`] is `m.space`.
337    pub fn is_space(&self) -> bool {
338        self.inner.read().room_type().is_some_and(|t| *t == RoomType::Space)
339    }
340
341    /// Returns the room's type as defined in its creation event
342    /// (`m.room.create`).
343    pub fn room_type(&self) -> Option<RoomType> {
344        self.inner.read().room_type().map(ToOwned::to_owned)
345    }
346
347    /// Get the unread notification counts.
348    pub fn unread_notification_counts(&self) -> UnreadNotificationsCount {
349        self.inner.read().notification_counts
350    }
351
352    /// Get the number of unread messages (computed client-side).
353    ///
354    /// This might be more precise than [`Self::unread_notification_counts`] for
355    /// encrypted rooms.
356    pub fn num_unread_messages(&self) -> u64 {
357        self.inner.read().read_receipts.num_unread
358    }
359
360    /// Get the detailed information about read receipts for the room.
361    pub fn read_receipts(&self) -> RoomReadReceipts {
362        self.inner.read().read_receipts.clone()
363    }
364
365    /// Get the number of unread notifications (computed client-side).
366    ///
367    /// This might be more precise than [`Self::unread_notification_counts`] for
368    /// encrypted rooms.
369    pub fn num_unread_notifications(&self) -> u64 {
370        self.inner.read().read_receipts.num_notifications
371    }
372
373    /// Get the number of unread mentions (computed client-side), that is,
374    /// messages causing a highlight in a room.
375    ///
376    /// This might be more precise than [`Self::unread_notification_counts`] for
377    /// encrypted rooms.
378    pub fn num_unread_mentions(&self) -> u64 {
379        self.inner.read().read_receipts.num_mentions
380    }
381
382    /// Check if the room has its members fully synced.
383    ///
384    /// Members might be missing if lazy member loading was enabled for the
385    /// sync.
386    ///
387    /// Returns true if no members are missing, false otherwise.
388    pub fn are_members_synced(&self) -> bool {
389        self.inner.read().members_synced
390    }
391
392    /// Mark this Room as holding all member information.
393    ///
394    /// Useful in tests if we want to persuade the Room not to sync when asked
395    /// about its members.
396    #[cfg(feature = "testing")]
397    pub fn mark_members_synced(&self) {
398        self.inner.update(|info| {
399            info.members_synced = true;
400        });
401    }
402
403    /// Mark this Room as still missing member information.
404    pub fn mark_members_missing(&self) {
405        self.inner.update_if(|info| {
406            // notify observable subscribers only if the previous value was false
407            mem::replace(&mut info.members_synced, false)
408        })
409    }
410
411    /// Check if the room states have been synced
412    ///
413    /// States might be missing if we have only seen the room_id of this Room
414    /// so far, for example as the response for a `create_room` request without
415    /// being synced yet.
416    ///
417    /// Returns true if the state is fully synced, false otherwise.
418    pub fn is_state_fully_synced(&self) -> bool {
419        self.inner.read().sync_info == SyncInfo::FullySynced
420    }
421
422    /// Check if the room state has been at least partially synced.
423    ///
424    /// See [`Room::is_state_fully_synced`] for more info.
425    pub fn is_state_partially_or_fully_synced(&self) -> bool {
426        self.inner.read().sync_info != SyncInfo::NoState
427    }
428
429    /// Get the `prev_batch` token that was received from the last sync. May be
430    /// `None` if the last sync contained the full room history.
431    pub fn last_prev_batch(&self) -> Option<String> {
432        self.inner.read().last_prev_batch.clone()
433    }
434
435    /// Get the avatar url of this room.
436    pub fn avatar_url(&self) -> Option<OwnedMxcUri> {
437        self.inner.read().avatar_url().map(ToOwned::to_owned)
438    }
439
440    /// Get information about the avatar of this room.
441    pub fn avatar_info(&self) -> Option<avatar::ImageInfo> {
442        self.inner.read().avatar_info().map(ToOwned::to_owned)
443    }
444
445    /// Get the canonical alias of this room.
446    pub fn canonical_alias(&self) -> Option<OwnedRoomAliasId> {
447        self.inner.read().canonical_alias().map(ToOwned::to_owned)
448    }
449
450    /// Get the canonical alias of this room.
451    pub fn alt_aliases(&self) -> Vec<OwnedRoomAliasId> {
452        self.inner.read().alt_aliases().to_owned()
453    }
454
455    /// Get the `m.room.create` content of this room.
456    ///
457    /// This usually isn't optional but some servers might not send an
458    /// `m.room.create` event as the first event for a given room, thus this can
459    /// be optional.
460    ///
461    /// For room versions earlier than room version 11, if the event is
462    /// redacted, all fields except `creator` will be set to their default
463    /// value.
464    pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
465        match self.inner.read().base_info.create.as_ref()? {
466            MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
467            MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
468        }
469    }
470
471    /// Is this room considered a direct message.
472    ///
473    /// Async because it can read room info from storage.
474    #[instrument(skip_all, fields(room_id = ?self.room_id))]
475    pub async fn is_direct(&self) -> StoreResult<bool> {
476        match self.state() {
477            RoomState::Joined | RoomState::Left | RoomState::Banned => {
478                Ok(!self.inner.read().base_info.dm_targets.is_empty())
479            }
480
481            RoomState::Invited => {
482                let member = self.get_member(self.own_user_id()).await?;
483
484                match member {
485                    None => {
486                        info!("RoomMember not found for the user's own id");
487                        Ok(false)
488                    }
489                    Some(member) => match member.event.as_ref() {
490                        MemberEvent::Sync(_) => {
491                            warn!("Got MemberEvent::Sync in an invited room");
492                            Ok(false)
493                        }
494                        MemberEvent::Stripped(event) => {
495                            Ok(event.content.is_direct.unwrap_or(false))
496                        }
497                    },
498                }
499            }
500
501            // TODO: implement logic once we have the stripped events as we'd have with an Invite
502            RoomState::Knocked => Ok(false),
503        }
504    }
505
506    /// If this room is a direct message, get the members that we're sharing the
507    /// room with.
508    ///
509    /// *Note*: The member list might have been modified in the meantime and
510    /// the targets might not even be in the room anymore. This setting should
511    /// only be considered as guidance. We leave members in this list to allow
512    /// us to re-find a DM with a user even if they have left, since we may
513    /// want to re-invite them.
514    pub fn direct_targets(&self) -> HashSet<OwnedDirectUserIdentifier> {
515        self.inner.read().base_info.dm_targets.clone()
516    }
517
518    /// If this room is a direct message, returns the number of members that
519    /// we're sharing the room with.
520    pub fn direct_targets_length(&self) -> usize {
521        self.inner.read().base_info.dm_targets.len()
522    }
523
524    /// Get the encryption state of this room.
525    pub fn encryption_state(&self) -> EncryptionState {
526        self.inner.read().encryption_state()
527    }
528
529    /// Get the `m.room.encryption` content that enabled end to end encryption
530    /// in the room.
531    pub fn encryption_settings(&self) -> Option<RoomEncryptionEventContent> {
532        self.inner.read().base_info.encryption.clone()
533    }
534
535    /// Get the guest access policy of this room.
536    pub fn guest_access(&self) -> GuestAccess {
537        self.inner.read().guest_access().clone()
538    }
539
540    /// Get the history visibility policy of this room.
541    pub fn history_visibility(&self) -> Option<HistoryVisibility> {
542        self.inner.read().history_visibility().cloned()
543    }
544
545    /// Get the history visibility policy of this room, or a sensible default if
546    /// the event is missing.
547    pub fn history_visibility_or_default(&self) -> HistoryVisibility {
548        self.inner.read().history_visibility_or_default().clone()
549    }
550
551    /// Is the room considered to be public.
552    pub fn is_public(&self) -> bool {
553        matches!(self.join_rule(), JoinRule::Public)
554    }
555
556    /// Get the join rule policy of this room.
557    pub fn join_rule(&self) -> JoinRule {
558        self.inner.read().join_rule().clone()
559    }
560
561    /// Get the maximum power level that this room contains.
562    ///
563    /// This is useful if one wishes to normalize the power levels, e.g. from
564    /// 0-100 where 100 would be the max power level.
565    pub fn max_power_level(&self) -> i64 {
566        self.inner.read().base_info.max_power_level
567    }
568
569    /// Get the current power levels of this room.
570    pub async fn power_levels(&self) -> Result<RoomPowerLevels, Error> {
571        Ok(self
572            .store
573            .get_state_event_static::<RoomPowerLevelsEventContent>(self.room_id())
574            .await?
575            .ok_or(Error::InsufficientData)?
576            .deserialize()?
577            .power_levels())
578    }
579
580    /// Get the `m.room.name` of this room.
581    ///
582    /// The returned string may be empty if the event has been redacted, or it's
583    /// missing from storage.
584    pub fn name(&self) -> Option<String> {
585        self.inner.read().name().map(ToOwned::to_owned)
586    }
587
588    /// Has the room been tombstoned.
589    pub fn is_tombstoned(&self) -> bool {
590        self.inner.read().base_info.tombstone.is_some()
591    }
592
593    /// Get the `m.room.tombstone` content of this room if there is one.
594    pub fn tombstone(&self) -> Option<RoomTombstoneEventContent> {
595        self.inner.read().tombstone().cloned()
596    }
597
598    /// Get the topic of the room.
599    pub fn topic(&self) -> Option<String> {
600        self.inner.read().topic().map(ToOwned::to_owned)
601    }
602
603    /// Is there a non expired membership with application "m.call" and scope
604    /// "m.room" in this room
605    pub fn has_active_room_call(&self) -> bool {
606        self.inner.read().has_active_room_call()
607    }
608
609    /// Returns a Vec of userId's that participate in the room call.
610    ///
611    /// MatrixRTC memberships with application "m.call" and scope "m.room" are
612    /// considered. A user can occur twice if they join with two devices.
613    /// convert to a set depending if the different users are required or the
614    /// amount of sessions.
615    ///
616    /// The vector is ordered by oldest membership user to newest.
617    pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
618        self.inner.read().active_room_call_participants()
619    }
620
621    /// Calculate a room's display name, or return the cached value, taking into
622    /// account its name, aliases and members.
623    ///
624    /// The display name is calculated according to [this algorithm][spec].
625    ///
626    /// While the underlying computation can be slow, the result is cached and
627    /// returned on the following calls. The cache is also filled on every
628    /// successful sync, since a sync may cause a change in the display
629    /// name.
630    ///
631    /// If you need a variant that's sync (but with the drawback that it returns
632    /// an `Option`), consider using [`Room::cached_display_name`].
633    ///
634    /// [spec]: <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
635    pub async fn display_name(&self) -> StoreResult<RoomDisplayName> {
636        if let Some(name) = self.cached_display_name() {
637            Ok(name)
638        } else {
639            self.compute_display_name().await
640        }
641    }
642
643    /// Force recalculating a room's display name, taking into account its name,
644    /// aliases and members.
645    ///
646    /// The display name is calculated according to [this algorithm][spec].
647    ///
648    /// ⚠ This may be slowish to compute. As such, the result is cached and can
649    /// be retrieved via [`Room::cached_display_name`] (sync, returns an option)
650    /// or [`Room::display_name`] (async, always returns a value), which should
651    /// be preferred in general.
652    ///
653    /// [spec]: <https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room>
654    pub(crate) async fn compute_display_name(&self) -> StoreResult<RoomDisplayName> {
655        enum DisplayNameOrSummary {
656            Summary(RoomSummary),
657            DisplayName(RoomDisplayName),
658        }
659
660        let display_name_or_summary = {
661            let inner = self.inner.read();
662
663            match (inner.name(), inner.canonical_alias()) {
664                (Some(name), _) => {
665                    let name = RoomDisplayName::Named(name.trim().to_owned());
666                    DisplayNameOrSummary::DisplayName(name)
667                }
668                (None, Some(alias)) => {
669                    let name = RoomDisplayName::Aliased(alias.alias().trim().to_owned());
670                    DisplayNameOrSummary::DisplayName(name)
671                }
672                // We can't directly compute the display name from the summary here because Rust
673                // thinks that the `inner` lock is still held even if we explicitly call `drop()`
674                // on it. So we introduced the DisplayNameOrSummary type and do the computation in
675                // two steps.
676                (None, None) => DisplayNameOrSummary::Summary(inner.summary.clone()),
677            }
678        };
679
680        let display_name = match display_name_or_summary {
681            DisplayNameOrSummary::Summary(summary) => {
682                self.compute_display_name_from_summary(summary).await?
683            }
684            DisplayNameOrSummary::DisplayName(display_name) => display_name,
685        };
686
687        // Update the cached display name before we return the newly computed value.
688        self.inner.update_if(|info| {
689            if info.cached_display_name.as_ref() != Some(&display_name) {
690                info.cached_display_name = Some(display_name.clone());
691                true
692            } else {
693                false
694            }
695        });
696
697        Ok(display_name)
698    }
699
700    /// Compute a [`RoomDisplayName`] from the given [`RoomSummary`].
701    async fn compute_display_name_from_summary(
702        &self,
703        summary: RoomSummary,
704    ) -> StoreResult<RoomDisplayName> {
705        let computed_summary = if !summary.room_heroes.is_empty() {
706            self.extract_and_augment_summary(&summary).await?
707        } else {
708            self.compute_summary().await?
709        };
710
711        let ComputedSummary { heroes, num_service_members, num_joined_invited_guess } =
712            computed_summary;
713
714        let summary_member_count = (summary.joined_member_count + summary.invited_member_count)
715            .saturating_sub(num_service_members);
716
717        let num_joined_invited = if self.state() == RoomState::Invited {
718            // when we were invited we don't have a proper summary, we have to do best
719            // guessing
720            heroes.len() as u64 + 1
721        } else if summary_member_count == 0 {
722            num_joined_invited_guess
723        } else {
724            summary_member_count
725        };
726
727        debug!(
728            room_id = ?self.room_id(),
729            own_user = ?self.own_user_id,
730            num_joined_invited,
731            heroes = ?heroes,
732            "Calculating name for a room based on heroes",
733        );
734
735        let display_name = compute_display_name_from_heroes(
736            num_joined_invited,
737            heroes.iter().map(|hero| hero.as_str()).collect(),
738        );
739
740        Ok(display_name)
741    }
742
743    /// Extracts and enhances the [`RoomSummary`] provided by the homeserver.
744    ///
745    /// This method extracts the relevant data from the [`RoomSummary`] and
746    /// augments it with additional information that may not be included in
747    /// the initial response, such as details about service members in the
748    /// room.
749    ///
750    /// Returns a [`ComputedSummary`].
751    async fn extract_and_augment_summary(
752        &self,
753        summary: &RoomSummary,
754    ) -> StoreResult<ComputedSummary> {
755        let heroes = &summary.room_heroes;
756
757        let mut names = Vec::with_capacity(heroes.len());
758        let own_user_id = self.own_user_id();
759        let member_hints = self.get_member_hints().await?;
760
761        // If we have some service members in the heroes, that means that they are also
762        // part of the joined member counts. They shouldn't be so, otherwise
763        // we'll wrongly assume that there are more members in the room than
764        // they are for the "Bob and 2 others" case.
765        let num_service_members = heroes
766            .iter()
767            .filter(|hero| member_hints.service_members.contains(&hero.user_id))
768            .count() as u64;
769
770        // Construct a filter that is specific to this own user id, set of member hints,
771        // and accepts a `RoomHero` type.
772        let heroes_filter = heroes_filter(own_user_id, &member_hints);
773        let heroes_filter = |hero: &&RoomHero| heroes_filter(&hero.user_id);
774
775        for hero in heroes.iter().filter(heroes_filter) {
776            if let Some(display_name) = &hero.display_name {
777                names.push(display_name.clone());
778            } else {
779                match self.get_member(&hero.user_id).await {
780                    Ok(Some(member)) => {
781                        names.push(member.name().to_owned());
782                    }
783                    Ok(None) => {
784                        warn!("Ignoring hero, no member info for {}", hero.user_id);
785                    }
786                    Err(error) => {
787                        warn!("Ignoring hero, error getting member: {}", error);
788                    }
789                }
790            }
791        }
792
793        let num_joined_invited_guess = summary.joined_member_count + summary.invited_member_count;
794
795        // If the summary doesn't provide the number of joined/invited members, let's
796        // guess something.
797        let num_joined_invited_guess = if num_joined_invited_guess == 0 {
798            let guess = self
799                .store
800                .get_user_ids(self.room_id(), RoomMemberships::JOIN | RoomMemberships::INVITE)
801                .await?
802                .len() as u64;
803
804            guess.saturating_sub(num_service_members)
805        } else {
806            // Otherwise, accept the numbers provided by the summary as the guess.
807            num_joined_invited_guess
808        };
809
810        Ok(ComputedSummary { heroes: names, num_service_members, num_joined_invited_guess })
811    }
812
813    /// Compute the room summary with the data present in the store.
814    ///
815    /// The summary might be incorrect if the database info is outdated.
816    ///
817    /// Returns the [`ComputedSummary`].
818    async fn compute_summary(&self) -> StoreResult<ComputedSummary> {
819        let member_hints = self.get_member_hints().await?;
820
821        // Construct a filter that is specific to this own user id, set of member hints,
822        // and accepts a `RoomMember` type.
823        let heroes_filter = heroes_filter(&self.own_user_id, &member_hints);
824        let heroes_filter = |u: &RoomMember| heroes_filter(u.user_id());
825
826        let mut members = self.members(RoomMemberships::JOIN | RoomMemberships::INVITE).await?;
827
828        // If we have some service members, they shouldn't count to the number of
829        // joined/invited members, otherwise we'll wrongly assume that there are more
830        // members in the room than they are for the "Bob and 2 others" case.
831        let num_service_members = members
832            .iter()
833            .filter(|member| member_hints.service_members.contains(member.user_id()))
834            .count();
835
836        // We can make a good prediction of the total number of joined and invited
837        // members here. This might be incorrect if the database info is
838        // outdated.
839        //
840        // Note: Subtracting here is fine because `num_service_members` is a subset of
841        // `members.len()` due to the above filter operation.
842        let num_joined_invited = members.len() - num_service_members;
843
844        if num_joined_invited == 0
845            || (num_joined_invited == 1 && members[0].user_id() == self.own_user_id)
846        {
847            // No joined or invited members, heroes should be banned and left members.
848            members = self.members(RoomMemberships::LEAVE | RoomMemberships::BAN).await?;
849        }
850
851        // Make the ordering deterministic.
852        members.sort_unstable_by(|lhs, rhs| lhs.name().cmp(rhs.name()));
853
854        let heroes = members
855            .into_iter()
856            .filter(heroes_filter)
857            .take(NUM_HEROES)
858            .map(|u| u.name().to_owned())
859            .collect();
860
861        trace!(
862            ?heroes,
863            num_joined_invited,
864            num_service_members,
865            "Computed a room summary since we didn't receive one."
866        );
867
868        let num_service_members = num_service_members as u64;
869        let num_joined_invited_guess = num_joined_invited as u64;
870
871        Ok(ComputedSummary { heroes, num_service_members, num_joined_invited_guess })
872    }
873
874    async fn get_member_hints(&self) -> StoreResult<MemberHintsEventContent> {
875        Ok(self
876            .store
877            .get_state_event_static::<MemberHintsEventContent>(self.room_id())
878            .await?
879            .and_then(|event| {
880                event
881                    .deserialize()
882                    .inspect_err(|e| warn!("Couldn't deserialize the member hints event: {e}"))
883                    .ok()
884            })
885            .and_then(|event| as_variant!(event, SyncOrStrippedState::Sync(SyncStateEvent::Original(e)) => e.content))
886            .unwrap_or_default())
887    }
888
889    /// Returns the cached computed display name, if available.
890    ///
891    /// This cache is refilled every time we call [`Self::display_name`].
892    pub fn cached_display_name(&self) -> Option<RoomDisplayName> {
893        self.inner.read().cached_display_name.clone()
894    }
895
896    /// Update the cached user defined notification mode.
897    ///
898    /// This is automatically recomputed on every successful sync, and the
899    /// cached result can be retrieved in
900    /// [`Self::cached_user_defined_notification_mode`].
901    pub fn update_cached_user_defined_notification_mode(&self, mode: RoomNotificationMode) {
902        self.inner.update_if(|info| {
903            if info.cached_user_defined_notification_mode.as_ref() != Some(&mode) {
904                info.cached_user_defined_notification_mode = Some(mode);
905
906                true
907            } else {
908                false
909            }
910        });
911    }
912
913    /// Returns the cached user defined notification mode, if available.
914    ///
915    /// This cache is refilled every time we call
916    /// [`Self::update_cached_user_defined_notification_mode`].
917    pub fn cached_user_defined_notification_mode(&self) -> Option<RoomNotificationMode> {
918        self.inner.read().cached_user_defined_notification_mode
919    }
920
921    /// Return the last event in this room, if one has been cached during
922    /// sliding sync.
923    pub fn latest_event(&self) -> Option<LatestEvent> {
924        self.inner.read().latest_event.as_deref().cloned()
925    }
926
927    /// Return the most recent few encrypted events. When the keys come through
928    /// to decrypt these, the most recent relevant one will replace
929    /// latest_event. (We can't tell which one is relevant until
930    /// they are decrypted.)
931    #[cfg(feature = "e2e-encryption")]
932    pub(crate) fn latest_encrypted_events(&self) -> Vec<Raw<AnySyncTimelineEvent>> {
933        self.latest_encrypted_events.read().unwrap().iter().cloned().collect()
934    }
935
936    /// Replace our latest_event with the supplied event, and delete it and all
937    /// older encrypted events from latest_encrypted_events, given that the
938    /// new event was at the supplied index in the latest_encrypted_events
939    /// list.
940    ///
941    /// Panics if index is not a valid index in the latest_encrypted_events
942    /// list.
943    ///
944    /// It is the responsibility of the caller to apply the changes into the
945    /// state store after calling this function.
946    #[cfg(feature = "e2e-encryption")]
947    pub(crate) fn on_latest_event_decrypted(
948        &self,
949        latest_event: Box<LatestEvent>,
950        index: usize,
951        changes: &mut crate::StateChanges,
952        room_info_notable_updates: &mut BTreeMap<OwnedRoomId, RoomInfoNotableUpdateReasons>,
953    ) {
954        self.latest_encrypted_events.write().unwrap().drain(0..=index);
955
956        let room_info = changes
957            .room_infos
958            .entry(self.room_id().to_owned())
959            .or_insert_with(|| self.clone_info());
960
961        room_info.latest_event = Some(latest_event);
962
963        room_info_notable_updates
964            .entry(self.room_id().to_owned())
965            .or_default()
966            .insert(RoomInfoNotableUpdateReasons::LATEST_EVENT);
967    }
968
969    /// Get the list of users ids that are considered to be joined members of
970    /// this room.
971    pub async fn joined_user_ids(&self) -> StoreResult<Vec<OwnedUserId>> {
972        self.store.get_user_ids(self.room_id(), RoomMemberships::JOIN).await
973    }
974
975    /// Get the `RoomMember`s of this room that are known to the store, with the
976    /// given memberships.
977    pub async fn members(&self, memberships: RoomMemberships) -> StoreResult<Vec<RoomMember>> {
978        let user_ids = self.store.get_user_ids(self.room_id(), memberships).await?;
979
980        if user_ids.is_empty() {
981            return Ok(Vec::new());
982        }
983
984        let member_events = self
985            .store
986            .get_state_events_for_keys_static::<RoomMemberEventContent, _, _>(
987                self.room_id(),
988                &user_ids,
989            )
990            .await?
991            .into_iter()
992            .map(|raw_event| raw_event.deserialize())
993            .collect::<Result<Vec<_>, _>>()?;
994
995        let mut profiles = self.store.get_profiles(self.room_id(), &user_ids).await?;
996
997        let mut presences = self
998            .store
999            .get_presence_events(&user_ids)
1000            .await?
1001            .into_iter()
1002            .filter_map(|e| {
1003                e.deserialize().ok().map(|presence| (presence.sender.clone(), presence))
1004            })
1005            .collect::<BTreeMap<_, _>>();
1006
1007        let display_names = member_events.iter().map(|e| e.display_name()).collect::<Vec<_>>();
1008        let room_info = self.member_room_info(&display_names).await?;
1009
1010        let mut members = Vec::new();
1011
1012        for event in member_events {
1013            let profile = profiles.remove(event.user_id());
1014            let presence = presences.remove(event.user_id());
1015            members.push(RoomMember::from_parts(event, profile, presence, &room_info))
1016        }
1017
1018        Ok(members)
1019    }
1020
1021    /// Get the heroes for this room.
1022    pub fn heroes(&self) -> Vec<RoomHero> {
1023        self.inner.read().heroes().to_vec()
1024    }
1025
1026    /// Returns the number of members who have joined or been invited to the
1027    /// room.
1028    pub fn active_members_count(&self) -> u64 {
1029        self.inner.read().active_members_count()
1030    }
1031
1032    /// Returns the number of members who have been invited to the room.
1033    pub fn invited_members_count(&self) -> u64 {
1034        self.inner.read().invited_members_count()
1035    }
1036
1037    /// Returns the number of members who have joined the room.
1038    pub fn joined_members_count(&self) -> u64 {
1039        self.inner.read().joined_members_count()
1040    }
1041
1042    /// Subscribe to the inner `RoomInfo`.
1043    pub fn subscribe_info(&self) -> Subscriber<RoomInfo> {
1044        self.inner.subscribe()
1045    }
1046
1047    /// Clone the inner `RoomInfo`.
1048    pub fn clone_info(&self) -> RoomInfo {
1049        self.inner.get()
1050    }
1051
1052    /// Update the summary with given RoomInfo.
1053    pub fn set_room_info(
1054        &self,
1055        room_info: RoomInfo,
1056        room_info_notable_update_reasons: RoomInfoNotableUpdateReasons,
1057    ) {
1058        self.inner.set(room_info);
1059
1060        // Ignore error if no receiver exists.
1061        let _ = self.room_info_notable_update_sender.send(RoomInfoNotableUpdate {
1062            room_id: self.room_id.clone(),
1063            reasons: room_info_notable_update_reasons,
1064        });
1065    }
1066
1067    /// Get the `RoomMember` with the given `user_id`.
1068    ///
1069    /// Returns `None` if the member was never part of this room, otherwise
1070    /// return a `RoomMember` that can be in a joined, RoomState::Invited, left,
1071    /// banned state.
1072    ///
1073    /// Async because it can read from storage.
1074    pub async fn get_member(&self, user_id: &UserId) -> StoreResult<Option<RoomMember>> {
1075        let Some(raw_event) = self.store.get_member_event(self.room_id(), user_id).await? else {
1076            debug!(%user_id, "Member event not found in state store");
1077            return Ok(None);
1078        };
1079
1080        let event = raw_event.deserialize()?;
1081
1082        let presence =
1083            self.store.get_presence_event(user_id).await?.and_then(|e| e.deserialize().ok());
1084
1085        let profile = self.store.get_profile(self.room_id(), user_id).await?;
1086
1087        let display_names = [event.display_name()];
1088        let room_info = self.member_room_info(&display_names).await?;
1089
1090        Ok(Some(RoomMember::from_parts(event, profile, presence, &room_info)))
1091    }
1092
1093    /// The current `MemberRoomInfo` for this room.
1094    ///
1095    /// Async because it can read from storage.
1096    async fn member_room_info<'a>(
1097        &self,
1098        display_names: &'a [DisplayName],
1099    ) -> StoreResult<MemberRoomInfo<'a>> {
1100        let max_power_level = self.max_power_level();
1101        let room_creator = self.inner.read().creator().map(ToOwned::to_owned);
1102
1103        let power_levels = self
1104            .store
1105            .get_state_event_static(self.room_id())
1106            .await?
1107            .and_then(|e| e.deserialize().ok());
1108
1109        let users_display_names =
1110            self.store.get_users_with_display_names(self.room_id(), display_names).await?;
1111
1112        let ignored_users = self
1113            .store
1114            .get_account_data_event_static::<IgnoredUserListEventContent>()
1115            .await?
1116            .map(|c| c.deserialize())
1117            .transpose()?
1118            .map(|e| e.content.ignored_users.into_keys().collect());
1119
1120        Ok(MemberRoomInfo {
1121            power_levels: power_levels.into(),
1122            max_power_level,
1123            room_creator,
1124            users_display_names,
1125            ignored_users,
1126        })
1127    }
1128
1129    /// Get the `Tags` for this room.
1130    pub async fn tags(&self) -> StoreResult<Option<Tags>> {
1131        if let Some(AnyRoomAccountDataEvent::Tag(event)) = self
1132            .store
1133            .get_room_account_data_event(self.room_id(), RoomAccountDataEventType::Tag)
1134            .await?
1135            .and_then(|r| r.deserialize().ok())
1136        {
1137            Ok(Some(event.content.tags))
1138        } else {
1139            Ok(None)
1140        }
1141    }
1142
1143    /// Check whether the room is marked as favourite.
1144    ///
1145    /// A room is considered favourite if it has received the `m.favourite` tag.
1146    pub fn is_favourite(&self) -> bool {
1147        self.inner.read().base_info.notable_tags.contains(RoomNotableTags::FAVOURITE)
1148    }
1149
1150    /// Check whether the room is marked as low priority.
1151    ///
1152    /// A room is considered low priority if it has received the `m.lowpriority`
1153    /// tag.
1154    pub fn is_low_priority(&self) -> bool {
1155        self.inner.read().base_info.notable_tags.contains(RoomNotableTags::LOW_PRIORITY)
1156    }
1157
1158    /// Get the receipt as an `OwnedEventId` and `Receipt` tuple for the given
1159    /// `receipt_type`, `thread` and `user_id` in this room.
1160    pub async fn load_user_receipt(
1161        &self,
1162        receipt_type: ReceiptType,
1163        thread: ReceiptThread,
1164        user_id: &UserId,
1165    ) -> StoreResult<Option<(OwnedEventId, Receipt)>> {
1166        self.store.get_user_room_receipt_event(self.room_id(), receipt_type, thread, user_id).await
1167    }
1168
1169    /// Load from storage the receipts as a list of `OwnedUserId` and `Receipt`
1170    /// tuples for the given `receipt_type`, `thread` and `event_id` in this
1171    /// room.
1172    pub async fn load_event_receipts(
1173        &self,
1174        receipt_type: ReceiptType,
1175        thread: ReceiptThread,
1176        event_id: &EventId,
1177    ) -> StoreResult<Vec<(OwnedUserId, Receipt)>> {
1178        self.store
1179            .get_event_room_receipt_events(self.room_id(), receipt_type, thread, event_id)
1180            .await
1181    }
1182
1183    /// Returns a boolean indicating if this room has been manually marked as
1184    /// unread
1185    pub fn is_marked_unread(&self) -> bool {
1186        self.inner.read().base_info.is_marked_unread
1187    }
1188
1189    /// Returns the recency stamp of the room.
1190    ///
1191    /// Please read `RoomInfo::recency_stamp` to learn more.
1192    pub fn recency_stamp(&self) -> Option<u64> {
1193        self.inner.read().recency_stamp
1194    }
1195
1196    /// Get a `Stream` of loaded pinned events for this room.
1197    /// If no pinned events are found a single empty `Vec` will be returned.
1198    pub fn pinned_event_ids_stream(&self) -> impl Stream<Item = Vec<OwnedEventId>> {
1199        self.inner
1200            .subscribe()
1201            .map(|i| i.base_info.pinned_events.map(|c| c.pinned).unwrap_or_default())
1202    }
1203
1204    /// Returns the current pinned event ids for this room.
1205    pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
1206        self.inner.read().pinned_event_ids()
1207    }
1208
1209    /// Mark a list of requests to join the room as seen, given their state
1210    /// event ids.
1211    pub async fn mark_knock_requests_as_seen(&self, user_ids: &[OwnedUserId]) -> StoreResult<()> {
1212        let raw_user_ids: Vec<&str> = user_ids.iter().map(|id| id.as_str()).collect();
1213        let member_raw_events = self
1214            .store
1215            .get_state_events_for_keys(self.room_id(), StateEventType::RoomMember, &raw_user_ids)
1216            .await?;
1217        let mut event_to_user_ids = Vec::with_capacity(member_raw_events.len());
1218
1219        // Map the list of events ids to their user ids, if they are event ids for knock
1220        // membership events. Log an error and continue otherwise.
1221        for raw_event in member_raw_events {
1222            let event = raw_event.cast::<RoomMemberEventContent>().deserialize()?;
1223            match event {
1224                SyncOrStrippedState::Sync(SyncStateEvent::Original(event)) => {
1225                    if event.content.membership == MembershipState::Knock {
1226                        event_to_user_ids.push((event.event_id, event.state_key))
1227                    } else {
1228                        warn!("Could not mark knock event as seen: event {} for user {} is not in Knock membership state.", event.event_id, event.state_key);
1229                    }
1230                }
1231                _ => warn!(
1232                    "Could not mark knock event as seen: event for user {} is not valid.",
1233                    event.state_key()
1234                ),
1235            }
1236        }
1237
1238        let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?;
1239        let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default();
1240
1241        current_seen_events.extend(event_to_user_ids);
1242
1243        self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?;
1244
1245        Ok(())
1246    }
1247
1248    /// Removes the seen knock request ids that are no longer valid given the
1249    /// current room members.
1250    pub async fn remove_outdated_seen_knock_requests_ids(&self) -> StoreResult<()> {
1251        let current_seen_events_guard = self.get_write_guarded_current_knock_request_ids().await?;
1252        let mut current_seen_events = current_seen_events_guard.clone().unwrap_or_default();
1253
1254        // Get and deserialize the member events for the seen knock requests
1255        let keys: Vec<OwnedUserId> = current_seen_events.values().map(|id| id.to_owned()).collect();
1256        let raw_member_events: Vec<RawMemberEvent> =
1257            self.store.get_state_events_for_keys_static(self.room_id(), &keys).await?;
1258        let member_events = raw_member_events
1259            .into_iter()
1260            .map(|raw| raw.deserialize())
1261            .collect::<Result<Vec<MemberEvent>, _>>()?;
1262
1263        let mut ids_to_remove = Vec::new();
1264
1265        for (event_id, user_id) in current_seen_events.iter() {
1266            // Check the seen knock request ids against the current room member events for
1267            // the room members associated to them
1268            let matching_member = member_events.iter().find(|event| event.user_id() == user_id);
1269
1270            if let Some(member) = matching_member {
1271                let member_event_id = member.event_id();
1272                // If the member event is not a knock or it's different knock, it's outdated
1273                if *member.membership() != MembershipState::Knock
1274                    || member_event_id.is_some_and(|id| id != event_id)
1275                {
1276                    ids_to_remove.push(event_id.to_owned());
1277                }
1278            } else {
1279                ids_to_remove.push(event_id.to_owned());
1280            }
1281        }
1282
1283        // If there are no ids to remove, do nothing
1284        if ids_to_remove.is_empty() {
1285            return Ok(());
1286        }
1287
1288        for event_id in ids_to_remove {
1289            current_seen_events.remove(&event_id);
1290        }
1291
1292        self.update_seen_knock_request_ids(current_seen_events_guard, current_seen_events).await?;
1293
1294        Ok(())
1295    }
1296
1297    /// Get the list of seen knock request event ids in this room.
1298    pub async fn get_seen_knock_request_ids(
1299        &self,
1300    ) -> Result<BTreeMap<OwnedEventId, OwnedUserId>, StoreError> {
1301        Ok(self.get_write_guarded_current_knock_request_ids().await?.clone().unwrap_or_default())
1302    }
1303
1304    async fn get_write_guarded_current_knock_request_ids(
1305        &self,
1306    ) -> StoreResult<ObservableWriteGuard<'_, Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>>
1307    {
1308        let mut guard = self.seen_knock_request_ids_map.write().await;
1309        // If there are no loaded request ids yet
1310        if guard.is_none() {
1311            // Load the values from the store and update the shared observable contents
1312            let updated_seen_ids = self
1313                .store
1314                .get_kv_data(StateStoreDataKey::SeenKnockRequests(self.room_id()))
1315                .await?
1316                .and_then(|v| v.into_seen_knock_requests())
1317                .unwrap_or_default();
1318
1319            ObservableWriteGuard::set(&mut guard, Some(updated_seen_ids));
1320        }
1321        Ok(guard)
1322    }
1323
1324    async fn update_seen_knock_request_ids(
1325        &self,
1326        mut guard: ObservableWriteGuard<'_, Option<BTreeMap<OwnedEventId, OwnedUserId>>, AsyncLock>,
1327        new_value: BTreeMap<OwnedEventId, OwnedUserId>,
1328    ) -> StoreResult<()> {
1329        // Save the new values to the shared observable
1330        ObservableWriteGuard::set(&mut guard, Some(new_value.clone()));
1331
1332        // Save them into the store too
1333        self.store
1334            .set_kv_data(
1335                StateStoreDataKey::SeenKnockRequests(self.room_id()),
1336                StateStoreDataValue::SeenKnockRequests(new_value),
1337            )
1338            .await?;
1339
1340        Ok(())
1341    }
1342}
1343
1344// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
1345#[cfg(not(feature = "test-send-sync"))]
1346unsafe impl Send for Room {}
1347
1348// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
1349#[cfg(not(feature = "test-send-sync"))]
1350unsafe impl Sync for Room {}
1351
1352#[cfg(feature = "test-send-sync")]
1353#[test]
1354// See https://github.com/matrix-org/matrix-rust-sdk/pull/3749#issuecomment-2312939823.
1355fn test_send_sync_for_room() {
1356    fn assert_send_sync<T: Send + Sync>() {}
1357
1358    assert_send_sync::<Room>();
1359}
1360
1361/// The underlying pure data structure for joined and left rooms.
1362///
1363/// Holds all the info needed to persist a room into the state store.
1364#[derive(Clone, Debug, Serialize, Deserialize)]
1365pub struct RoomInfo {
1366    /// The version of the room info.
1367    #[serde(default)]
1368    pub(crate) version: u8,
1369
1370    /// The unique room id of the room.
1371    pub(crate) room_id: OwnedRoomId,
1372
1373    /// The state of the room.
1374    pub(crate) room_state: RoomState,
1375
1376    /// The previous state of the room, if any.
1377    pub(crate) prev_room_state: Option<RoomState>,
1378
1379    /// The unread notifications counts, as returned by the server.
1380    ///
1381    /// These might be incorrect for encrypted rooms, since the server doesn't
1382    /// have access to the content of the encrypted events.
1383    pub(crate) notification_counts: UnreadNotificationsCount,
1384
1385    /// The summary of this room.
1386    pub(crate) summary: RoomSummary,
1387
1388    /// Flag remembering if the room members are synced.
1389    pub(crate) members_synced: bool,
1390
1391    /// The prev batch of this room we received during the last sync.
1392    pub(crate) last_prev_batch: Option<String>,
1393
1394    /// How much we know about this room.
1395    pub(crate) sync_info: SyncInfo,
1396
1397    /// Whether or not the encryption info was been synced.
1398    pub(crate) encryption_state_synced: bool,
1399
1400    /// The last event send by sliding sync
1401    pub(crate) latest_event: Option<Box<LatestEvent>>,
1402
1403    /// Information about read receipts for this room.
1404    #[serde(default)]
1405    pub(crate) read_receipts: RoomReadReceipts,
1406
1407    /// Base room info which holds some basic event contents important for the
1408    /// room state.
1409    pub(crate) base_info: Box<BaseRoomInfo>,
1410
1411    /// Did we already warn about an unknown room version in
1412    /// [`RoomInfo::room_version_or_default`]? This is done to avoid
1413    /// spamming about unknown room versions in the log for the same room.
1414    #[serde(skip)]
1415    pub(crate) warned_about_unknown_room_version: Arc<AtomicBool>,
1416
1417    /// Cached display name, useful for sync access.
1418    ///
1419    /// Filled by calling [`Room::compute_display_name`]. It's automatically
1420    /// filled at start when creating a room, or on every successful sync.
1421    #[serde(default, skip_serializing_if = "Option::is_none")]
1422    pub(crate) cached_display_name: Option<RoomDisplayName>,
1423
1424    /// Cached user defined notification mode.
1425    #[serde(default, skip_serializing_if = "Option::is_none")]
1426    pub(crate) cached_user_defined_notification_mode: Option<RoomNotificationMode>,
1427
1428    /// The recency stamp of this room.
1429    ///
1430    /// It's not to be confused with `origin_server_ts` of the latest event.
1431    /// Sliding Sync might "ignore” some events when computing the recency
1432    /// stamp of the room. Thus, using this `recency_stamp` value is
1433    /// more accurate than relying on the latest event.
1434    #[serde(default)]
1435    pub(crate) recency_stamp: Option<u64>,
1436}
1437
1438#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
1439pub(crate) enum SyncInfo {
1440    /// We only know the room exists and whether it is in invite / joined / left
1441    /// state.
1442    ///
1443    /// This is the case when we have a limited sync or only seen the room
1444    /// because of a request we've done, like a room creation event.
1445    NoState,
1446
1447    /// Some states have been synced, but they might have been filtered or is
1448    /// stale, as it is from a room we've left.
1449    PartiallySynced,
1450
1451    /// We have all the latest state events.
1452    FullySynced,
1453}
1454
1455impl RoomInfo {
1456    #[doc(hidden)] // used by store tests, otherwise it would be pub(crate)
1457    pub fn new(room_id: &RoomId, room_state: RoomState) -> Self {
1458        Self {
1459            version: 1,
1460            room_id: room_id.into(),
1461            room_state,
1462            prev_room_state: None,
1463            notification_counts: Default::default(),
1464            summary: Default::default(),
1465            members_synced: false,
1466            last_prev_batch: None,
1467            sync_info: SyncInfo::NoState,
1468            encryption_state_synced: false,
1469            latest_event: None,
1470            read_receipts: Default::default(),
1471            base_info: Box::new(BaseRoomInfo::new()),
1472            warned_about_unknown_room_version: Arc::new(false.into()),
1473            cached_display_name: None,
1474            cached_user_defined_notification_mode: None,
1475            recency_stamp: None,
1476        }
1477    }
1478
1479    /// Mark this Room as joined.
1480    pub fn mark_as_joined(&mut self) {
1481        self.set_state(RoomState::Joined);
1482    }
1483
1484    /// Mark this Room as left.
1485    pub fn mark_as_left(&mut self) {
1486        self.set_state(RoomState::Left);
1487    }
1488
1489    /// Mark this Room as invited.
1490    pub fn mark_as_invited(&mut self) {
1491        self.set_state(RoomState::Invited);
1492    }
1493
1494    /// Mark this Room as knocked.
1495    pub fn mark_as_knocked(&mut self) {
1496        self.set_state(RoomState::Knocked);
1497    }
1498
1499    /// Mark this Room as banned.
1500    pub fn mark_as_banned(&mut self) {
1501        self.set_state(RoomState::Banned);
1502    }
1503
1504    /// Set the membership RoomState of this Room
1505    pub fn set_state(&mut self, room_state: RoomState) {
1506        if room_state != self.room_state {
1507            self.prev_room_state = Some(self.room_state);
1508            self.room_state = room_state;
1509        }
1510    }
1511
1512    /// Mark this Room as having all the members synced.
1513    pub fn mark_members_synced(&mut self) {
1514        self.members_synced = true;
1515    }
1516
1517    /// Mark this Room as still missing member information.
1518    pub fn mark_members_missing(&mut self) {
1519        self.members_synced = false;
1520    }
1521
1522    /// Returns whether the room members are synced.
1523    pub fn are_members_synced(&self) -> bool {
1524        self.members_synced
1525    }
1526
1527    /// Mark this Room as still missing some state information.
1528    pub fn mark_state_partially_synced(&mut self) {
1529        self.sync_info = SyncInfo::PartiallySynced;
1530    }
1531
1532    /// Mark this Room as still having all state synced.
1533    pub fn mark_state_fully_synced(&mut self) {
1534        self.sync_info = SyncInfo::FullySynced;
1535    }
1536
1537    /// Mark this Room as still having no state synced.
1538    pub fn mark_state_not_synced(&mut self) {
1539        self.sync_info = SyncInfo::NoState;
1540    }
1541
1542    /// Mark this Room as having the encryption state synced.
1543    pub fn mark_encryption_state_synced(&mut self) {
1544        self.encryption_state_synced = true;
1545    }
1546
1547    /// Mark this Room as still missing encryption state information.
1548    pub fn mark_encryption_state_missing(&mut self) {
1549        self.encryption_state_synced = false;
1550    }
1551
1552    /// Set the `prev_batch`-token.
1553    /// Returns whether the token has differed and thus has been upgraded:
1554    /// `false` means no update was applied as the were the same
1555    pub fn set_prev_batch(&mut self, prev_batch: Option<&str>) -> bool {
1556        if self.last_prev_batch.as_deref() != prev_batch {
1557            self.last_prev_batch = prev_batch.map(|p| p.to_owned());
1558            true
1559        } else {
1560            false
1561        }
1562    }
1563
1564    /// Returns the state this room is in.
1565    pub fn state(&self) -> RoomState {
1566        self.room_state
1567    }
1568
1569    /// Returns the encryption state of this room.
1570    pub fn encryption_state(&self) -> EncryptionState {
1571        if !self.encryption_state_synced {
1572            EncryptionState::Unknown
1573        } else if self.base_info.encryption.is_some() {
1574            EncryptionState::Encrypted
1575        } else {
1576            EncryptionState::NotEncrypted
1577        }
1578    }
1579
1580    /// Set the encryption event content in this room.
1581    pub fn set_encryption_event(&mut self, event: Option<RoomEncryptionEventContent>) {
1582        self.base_info.encryption = event;
1583    }
1584
1585    /// Handle the encryption state.
1586    pub fn handle_encryption_state(
1587        &mut self,
1588        requested_required_states: &[(StateEventType, String)],
1589    ) {
1590        if requested_required_states
1591            .iter()
1592            .any(|(state_event, _)| state_event == &StateEventType::RoomEncryption)
1593        {
1594            // The `m.room.encryption` event was requested during the sync. Whether we have
1595            // received a `m.room.encryption` event in return doesn't matter: we must mark
1596            // the encryption state as synced; if the event is present, it means the room
1597            // _is_ encrypted, otherwise it means the room _is not_ encrypted.
1598
1599            self.mark_encryption_state_synced();
1600        }
1601    }
1602
1603    /// Handle the given state event.
1604    ///
1605    /// Returns true if the event modified the info, false otherwise.
1606    pub fn handle_state_event(&mut self, event: &AnySyncStateEvent) -> bool {
1607        // Store the state event in the `BaseRoomInfo` first.
1608        let base_info_has_been_modified = self.base_info.handle_state_event(event);
1609
1610        if let AnySyncStateEvent::RoomEncryption(_) = event {
1611            // The `m.room.encryption` event was or wasn't explicitly requested, we don't
1612            // know here (see `Self::handle_encryption_state`) but we got one in
1613            // return! In this case, we can deduce the room _is_ encrypted, but we cannot
1614            // know if it _is not_ encrypted.
1615
1616            self.mark_encryption_state_synced();
1617        }
1618
1619        base_info_has_been_modified
1620    }
1621
1622    /// Handle the given stripped state event.
1623    ///
1624    /// Returns true if the event modified the info, false otherwise.
1625    pub fn handle_stripped_state_event(&mut self, event: &AnyStrippedStateEvent) -> bool {
1626        self.base_info.handle_stripped_state_event(event)
1627    }
1628
1629    /// Handle the given redaction.
1630    #[instrument(skip_all, fields(redacts))]
1631    pub fn handle_redaction(
1632        &mut self,
1633        event: &SyncRoomRedactionEvent,
1634        _raw: &Raw<SyncRoomRedactionEvent>,
1635    ) {
1636        let room_version = self.base_info.room_version().unwrap_or(&RoomVersionId::V1);
1637
1638        let Some(redacts) = event.redacts(room_version) else {
1639            info!("Can't apply redaction, redacts field is missing");
1640            return;
1641        };
1642        tracing::Span::current().record("redacts", debug(redacts));
1643
1644        if let Some(latest_event) = &mut self.latest_event {
1645            tracing::trace!("Checking if redaction applies to latest event");
1646            if latest_event.event_id().as_deref() == Some(redacts) {
1647                match apply_redaction(latest_event.event().raw(), _raw, room_version) {
1648                    Some(redacted) => {
1649                        // Even if the original event was encrypted, redaction removes all its
1650                        // fields so it cannot possibly be successfully decrypted after redaction.
1651                        latest_event.event_mut().kind =
1652                            TimelineEventKind::PlainText { event: redacted };
1653                        debug!("Redacted latest event");
1654                    }
1655                    None => {
1656                        self.latest_event = None;
1657                        debug!("Removed latest event");
1658                    }
1659                }
1660            }
1661        }
1662
1663        self.base_info.handle_redaction(redacts);
1664    }
1665
1666    /// Returns the current room avatar.
1667    pub fn avatar_url(&self) -> Option<&MxcUri> {
1668        self.base_info
1669            .avatar
1670            .as_ref()
1671            .and_then(|e| e.as_original().and_then(|e| e.content.url.as_deref()))
1672    }
1673
1674    /// Update the room avatar.
1675    pub fn update_avatar(&mut self, url: Option<OwnedMxcUri>) {
1676        self.base_info.avatar = url.map(|url| {
1677            let mut content = RoomAvatarEventContent::new();
1678            content.url = Some(url);
1679
1680            MinimalStateEvent::Original(OriginalMinimalStateEvent { content, event_id: None })
1681        });
1682    }
1683
1684    /// Returns information about the current room avatar.
1685    pub fn avatar_info(&self) -> Option<&avatar::ImageInfo> {
1686        self.base_info
1687            .avatar
1688            .as_ref()
1689            .and_then(|e| e.as_original().and_then(|e| e.content.info.as_deref()))
1690    }
1691
1692    /// Update the notifications count.
1693    pub fn update_notification_count(&mut self, notification_counts: UnreadNotificationsCount) {
1694        self.notification_counts = notification_counts;
1695    }
1696
1697    /// Update the RoomSummary from a Ruma `RoomSummary`.
1698    ///
1699    /// Returns true if any field has been updated, false otherwise.
1700    pub fn update_from_ruma_summary(&mut self, summary: &RumaSummary) -> bool {
1701        let mut changed = false;
1702
1703        if !summary.is_empty() {
1704            if !summary.heroes.is_empty() {
1705                self.summary.room_heroes = summary
1706                    .heroes
1707                    .iter()
1708                    .map(|hero_id| RoomHero {
1709                        user_id: hero_id.to_owned(),
1710                        display_name: None,
1711                        avatar_url: None,
1712                    })
1713                    .collect();
1714
1715                changed = true;
1716            }
1717
1718            if let Some(joined) = summary.joined_member_count {
1719                self.summary.joined_member_count = joined.into();
1720                changed = true;
1721            }
1722
1723            if let Some(invited) = summary.invited_member_count {
1724                self.summary.invited_member_count = invited.into();
1725                changed = true;
1726            }
1727        }
1728
1729        changed
1730    }
1731
1732    /// Updates the joined member count.
1733    pub(crate) fn update_joined_member_count(&mut self, count: u64) {
1734        self.summary.joined_member_count = count;
1735    }
1736
1737    /// Updates the invited member count.
1738    pub(crate) fn update_invited_member_count(&mut self, count: u64) {
1739        self.summary.invited_member_count = count;
1740    }
1741
1742    /// Updates the room heroes.
1743    pub(crate) fn update_heroes(&mut self, heroes: Vec<RoomHero>) {
1744        self.summary.room_heroes = heroes;
1745    }
1746
1747    /// The heroes for this room.
1748    pub fn heroes(&self) -> &[RoomHero] {
1749        &self.summary.room_heroes
1750    }
1751
1752    /// The number of active members (invited + joined) in the room.
1753    ///
1754    /// The return value is saturated at `u64::MAX`.
1755    pub fn active_members_count(&self) -> u64 {
1756        self.summary.joined_member_count.saturating_add(self.summary.invited_member_count)
1757    }
1758
1759    /// The number of invited members in the room
1760    pub fn invited_members_count(&self) -> u64 {
1761        self.summary.invited_member_count
1762    }
1763
1764    /// The number of joined members in the room
1765    pub fn joined_members_count(&self) -> u64 {
1766        self.summary.joined_member_count
1767    }
1768
1769    /// Get the canonical alias of this room.
1770    pub fn canonical_alias(&self) -> Option<&RoomAliasId> {
1771        self.base_info.canonical_alias.as_ref()?.as_original()?.content.alias.as_deref()
1772    }
1773
1774    /// Get the alternative aliases of this room.
1775    pub fn alt_aliases(&self) -> &[OwnedRoomAliasId] {
1776        self.base_info
1777            .canonical_alias
1778            .as_ref()
1779            .and_then(|ev| ev.as_original())
1780            .map(|ev| ev.content.alt_aliases.as_ref())
1781            .unwrap_or_default()
1782    }
1783
1784    /// Get the room ID of this room.
1785    pub fn room_id(&self) -> &RoomId {
1786        &self.room_id
1787    }
1788
1789    /// Get the room version of this room.
1790    pub fn room_version(&self) -> Option<&RoomVersionId> {
1791        self.base_info.room_version()
1792    }
1793
1794    /// Get the room version of this room, or a sensible default.
1795    ///
1796    /// Will warn (at most once) if the room creation event is missing from this
1797    /// [`RoomInfo`].
1798    pub fn room_version_or_default(&self) -> RoomVersionId {
1799        use std::sync::atomic::Ordering;
1800
1801        self.base_info.room_version().cloned().unwrap_or_else(|| {
1802            if self
1803                .warned_about_unknown_room_version
1804                .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed)
1805                .is_ok()
1806            {
1807                warn!("Unknown room version, falling back to v10");
1808            }
1809
1810            RoomVersionId::V10
1811        })
1812    }
1813
1814    /// Get the room type of this room.
1815    pub fn room_type(&self) -> Option<&RoomType> {
1816        match self.base_info.create.as_ref()? {
1817            MinimalStateEvent::Original(ev) => ev.content.room_type.as_ref(),
1818            MinimalStateEvent::Redacted(ev) => ev.content.room_type.as_ref(),
1819        }
1820    }
1821
1822    /// Get the creator of this room.
1823    pub fn creator(&self) -> Option<&UserId> {
1824        match self.base_info.create.as_ref()? {
1825            MinimalStateEvent::Original(ev) => Some(&ev.content.creator),
1826            MinimalStateEvent::Redacted(ev) => Some(&ev.content.creator),
1827        }
1828    }
1829
1830    fn guest_access(&self) -> &GuestAccess {
1831        match &self.base_info.guest_access {
1832            Some(MinimalStateEvent::Original(ev)) => &ev.content.guest_access,
1833            _ => &GuestAccess::Forbidden,
1834        }
1835    }
1836
1837    /// Returns the history visibility for this room.
1838    ///
1839    /// Returns None if the event was never seen during sync.
1840    pub fn history_visibility(&self) -> Option<&HistoryVisibility> {
1841        match &self.base_info.history_visibility {
1842            Some(MinimalStateEvent::Original(ev)) => Some(&ev.content.history_visibility),
1843            _ => None,
1844        }
1845    }
1846
1847    /// Returns the history visibility for this room, or a sensible default.
1848    ///
1849    /// Returns `Shared`, the default specified by the [spec], when the event is
1850    /// missing.
1851    ///
1852    /// [spec]: https://spec.matrix.org/latest/client-server-api/#server-behaviour-7
1853    pub fn history_visibility_or_default(&self) -> &HistoryVisibility {
1854        match &self.base_info.history_visibility {
1855            Some(MinimalStateEvent::Original(ev)) => &ev.content.history_visibility,
1856            _ => &HistoryVisibility::Shared,
1857        }
1858    }
1859
1860    /// Returns the join rule for this room.
1861    ///
1862    /// Defaults to `Public`, if missing.
1863    pub fn join_rule(&self) -> &JoinRule {
1864        match &self.base_info.join_rules {
1865            Some(MinimalStateEvent::Original(ev)) => &ev.content.join_rule,
1866            _ => &JoinRule::Public,
1867        }
1868    }
1869
1870    /// Get the name of this room.
1871    pub fn name(&self) -> Option<&str> {
1872        let name = &self.base_info.name.as_ref()?.as_original()?.content.name;
1873        (!name.is_empty()).then_some(name)
1874    }
1875
1876    fn tombstone(&self) -> Option<&RoomTombstoneEventContent> {
1877        Some(&self.base_info.tombstone.as_ref()?.as_original()?.content)
1878    }
1879
1880    /// Returns the topic for this room, if set.
1881    pub fn topic(&self) -> Option<&str> {
1882        Some(&self.base_info.topic.as_ref()?.as_original()?.content.topic)
1883    }
1884
1885    /// Get a list of all the valid (non expired) matrixRTC memberships and
1886    /// associated UserId's in this room.
1887    ///
1888    /// The vector is ordered by oldest membership to newest.
1889    fn active_matrix_rtc_memberships(&self) -> Vec<(CallMemberStateKey, MembershipData<'_>)> {
1890        let mut v = self
1891            .base_info
1892            .rtc_member_events
1893            .iter()
1894            .filter_map(|(user_id, ev)| {
1895                ev.as_original().map(|ev| {
1896                    ev.content
1897                        .active_memberships(None)
1898                        .into_iter()
1899                        .map(move |m| (user_id.clone(), m))
1900                })
1901            })
1902            .flatten()
1903            .collect::<Vec<_>>();
1904        v.sort_by_key(|(_, m)| m.created_ts());
1905        v
1906    }
1907
1908    /// Similar to
1909    /// [`matrix_rtc_memberships`](Self::active_matrix_rtc_memberships) but only
1910    /// returns Memberships with application "m.call" and scope "m.room".
1911    ///
1912    /// The vector is ordered by oldest membership user to newest.
1913    fn active_room_call_memberships(&self) -> Vec<(CallMemberStateKey, MembershipData<'_>)> {
1914        self.active_matrix_rtc_memberships()
1915            .into_iter()
1916            .filter(|(_user_id, m)| m.is_room_call())
1917            .collect()
1918    }
1919
1920    /// Is there a non expired membership with application "m.call" and scope
1921    /// "m.room" in this room.
1922    pub fn has_active_room_call(&self) -> bool {
1923        !self.active_room_call_memberships().is_empty()
1924    }
1925
1926    /// Returns a Vec of userId's that participate in the room call.
1927    ///
1928    /// matrix_rtc memberships with application "m.call" and scope "m.room" are
1929    /// considered. A user can occur twice if they join with two devices.
1930    /// convert to a set depending if the different users are required or the
1931    /// amount of sessions.
1932    ///
1933    /// The vector is ordered by oldest membership user to newest.
1934    pub fn active_room_call_participants(&self) -> Vec<OwnedUserId> {
1935        self.active_room_call_memberships()
1936            .iter()
1937            .map(|(call_member_state_key, _)| call_member_state_key.user_id().to_owned())
1938            .collect()
1939    }
1940
1941    /// Returns the latest (decrypted) event recorded for this room.
1942    pub fn latest_event(&self) -> Option<&LatestEvent> {
1943        self.latest_event.as_deref()
1944    }
1945
1946    /// Updates the recency stamp of this room.
1947    ///
1948    /// Please read [`Self::recency_stamp`] to learn more.
1949    pub(crate) fn update_recency_stamp(&mut self, stamp: u64) {
1950        self.recency_stamp = Some(stamp);
1951    }
1952
1953    /// Returns the current pinned event ids for this room.
1954    pub fn pinned_event_ids(&self) -> Option<Vec<OwnedEventId>> {
1955        self.base_info.pinned_events.clone().map(|c| c.pinned)
1956    }
1957
1958    /// Checks if an `EventId` is currently pinned.
1959    /// It avoids having to clone the whole list of event ids to check a single
1960    /// value.
1961    ///
1962    /// Returns `true` if the provided `event_id` is pinned, `false` otherwise.
1963    pub fn is_pinned_event(&self, event_id: &EventId) -> bool {
1964        self.base_info
1965            .pinned_events
1966            .as_ref()
1967            .map(|p| p.pinned.contains(&event_id.to_owned()))
1968            .unwrap_or_default()
1969    }
1970
1971    /// Apply migrations to this `RoomInfo` if needed.
1972    ///
1973    /// This should be used to populate new fields with data from the state
1974    /// store.
1975    ///
1976    /// Returns `true` if migrations were applied and this `RoomInfo` needs to
1977    /// be persisted to the state store.
1978    #[instrument(skip_all, fields(room_id = ?self.room_id))]
1979    pub(crate) async fn apply_migrations(&mut self, store: Arc<DynStateStore>) -> bool {
1980        let mut migrated = false;
1981
1982        if self.version < 1 {
1983            info!("Migrating room info to version 1");
1984
1985            // notable_tags
1986            match store.get_room_account_data_event_static::<TagEventContent>(&self.room_id).await {
1987                // Pinned events are never in stripped state.
1988                Ok(Some(raw_event)) => match raw_event.deserialize() {
1989                    Ok(event) => {
1990                        self.base_info.handle_notable_tags(&event.content.tags);
1991                    }
1992                    Err(error) => {
1993                        warn!("Failed to deserialize room tags: {error}");
1994                    }
1995                },
1996                Ok(_) => {
1997                    // Nothing to do.
1998                }
1999                Err(error) => {
2000                    warn!("Failed to load room tags: {error}");
2001                }
2002            }
2003
2004            // pinned_events
2005            match store.get_state_event_static::<RoomPinnedEventsEventContent>(&self.room_id).await
2006            {
2007                // Pinned events are never in stripped state.
2008                Ok(Some(RawSyncOrStrippedState::Sync(raw_event))) => {
2009                    match raw_event.deserialize() {
2010                        Ok(event) => {
2011                            self.handle_state_event(&event.into());
2012                        }
2013                        Err(error) => {
2014                            warn!("Failed to deserialize room pinned events: {error}");
2015                        }
2016                    }
2017                }
2018                Ok(_) => {
2019                    // Nothing to do.
2020                }
2021                Err(error) => {
2022                    warn!("Failed to load room pinned events: {error}");
2023                }
2024            }
2025
2026            self.version = 1;
2027            migrated = true;
2028        }
2029
2030        migrated
2031    }
2032}
2033
2034/// Apply a redaction to the given target `event`, given the raw redaction event
2035/// and the room version.
2036pub fn apply_redaction(
2037    event: &Raw<AnySyncTimelineEvent>,
2038    raw_redaction: &Raw<SyncRoomRedactionEvent>,
2039    room_version: &RoomVersionId,
2040) -> Option<Raw<AnySyncTimelineEvent>> {
2041    use ruma::canonical_json::{redact_in_place, RedactedBecause};
2042
2043    let mut event_json = match event.deserialize_as() {
2044        Ok(json) => json,
2045        Err(e) => {
2046            warn!("Failed to deserialize latest event: {e}");
2047            return None;
2048        }
2049    };
2050
2051    let redacted_because = match RedactedBecause::from_raw_event(raw_redaction) {
2052        Ok(rb) => rb,
2053        Err(e) => {
2054            warn!("Redaction event is not valid canonical JSON: {e}");
2055            return None;
2056        }
2057    };
2058
2059    let redact_result = redact_in_place(&mut event_json, room_version, Some(redacted_because));
2060
2061    if let Err(e) = redact_result {
2062        warn!("Failed to redact event: {e}");
2063        return None;
2064    }
2065
2066    let raw = Raw::new(&event_json).expect("CanonicalJsonObject must be serializable");
2067    Some(raw.cast())
2068}
2069
2070bitflags! {
2071    /// Room state filter as a bitset.
2072    ///
2073    /// Note that [`RoomStateFilter::empty()`] doesn't filter the results and
2074    /// is equivalent to [`RoomStateFilter::all()`].
2075    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
2076    pub struct RoomStateFilter: u16 {
2077        /// The room is in a joined state.
2078        const JOINED   = 0b00000001;
2079        /// The room is in an invited state.
2080        const INVITED  = 0b00000010;
2081        /// The room is in a left state.
2082        const LEFT     = 0b00000100;
2083        /// The room is in a knocked state.
2084        const KNOCKED  = 0b00001000;
2085        /// The room is in a banned state.
2086        const BANNED   = 0b00010000;
2087    }
2088}
2089
2090impl RoomStateFilter {
2091    /// Whether the given room state matches this `RoomStateFilter`.
2092    pub fn matches(&self, state: RoomState) -> bool {
2093        if self.is_empty() {
2094            return true;
2095        }
2096
2097        let bit_state = match state {
2098            RoomState::Joined => Self::JOINED,
2099            RoomState::Left => Self::LEFT,
2100            RoomState::Invited => Self::INVITED,
2101            RoomState::Knocked => Self::KNOCKED,
2102            RoomState::Banned => Self::BANNED,
2103        };
2104
2105        self.contains(bit_state)
2106    }
2107
2108    /// Get this `RoomStateFilter` as a list of matching [`RoomState`]s.
2109    pub fn as_vec(&self) -> Vec<RoomState> {
2110        let mut states = Vec::new();
2111
2112        if self.contains(Self::JOINED) {
2113            states.push(RoomState::Joined);
2114        }
2115        if self.contains(Self::LEFT) {
2116            states.push(RoomState::Left);
2117        }
2118        if self.contains(Self::INVITED) {
2119            states.push(RoomState::Invited);
2120        }
2121        if self.contains(Self::KNOCKED) {
2122            states.push(RoomState::Knocked);
2123        }
2124        if self.contains(Self::BANNED) {
2125            states.push(RoomState::Banned);
2126        }
2127
2128        states
2129    }
2130}
2131
2132/// Calculate room name according to step 3 of the [naming algorithm].
2133///
2134/// [naming algorithm]: https://spec.matrix.org/latest/client-server-api/#calculating-the-display-name-for-a-room
2135fn compute_display_name_from_heroes(
2136    num_joined_invited: u64,
2137    mut heroes: Vec<&str>,
2138) -> RoomDisplayName {
2139    let num_heroes = heroes.len() as u64;
2140    let num_joined_invited_except_self = num_joined_invited.saturating_sub(1);
2141
2142    // Stabilize ordering.
2143    heroes.sort_unstable();
2144
2145    let names = if num_heroes == 0 && num_joined_invited > 1 {
2146        format!("{} people", num_joined_invited)
2147    } else if num_heroes >= num_joined_invited_except_self {
2148        heroes.join(", ")
2149    } else if num_heroes < num_joined_invited_except_self && num_joined_invited > 1 {
2150        // TODO: What length does the spec want us to use here and in
2151        // the `else`?
2152        format!("{}, and {} others", heroes.join(", "), (num_joined_invited - num_heroes))
2153    } else {
2154        "".to_owned()
2155    };
2156
2157    // User is alone.
2158    if num_joined_invited <= 1 {
2159        if names.is_empty() {
2160            RoomDisplayName::Empty
2161        } else {
2162            RoomDisplayName::EmptyWas(names)
2163        }
2164    } else {
2165        RoomDisplayName::Calculated(names)
2166    }
2167}
2168
2169/// Represents the state of a room encryption.
2170#[derive(Debug)]
2171#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
2172pub enum EncryptionState {
2173    /// The room is encrypted.
2174    Encrypted,
2175
2176    /// The room is not encrypted.
2177    NotEncrypted,
2178
2179    /// The state of the room encryption is unknown, probably because the
2180    /// `/sync` did not provide all data needed to decide.
2181    Unknown,
2182}
2183
2184impl EncryptionState {
2185    /// Check whether `EncryptionState` is [`Encrypted`][Self::Encrypted].
2186    pub fn is_encrypted(&self) -> bool {
2187        matches!(self, Self::Encrypted)
2188    }
2189
2190    /// Check whether `EncryptionState` is [`Unknown`][Self::Unknown].
2191    pub fn is_unknown(&self) -> bool {
2192        matches!(self, Self::Unknown)
2193    }
2194}
2195
2196#[cfg(test)]
2197mod tests {
2198    use std::{
2199        collections::BTreeSet,
2200        ops::{Not, Sub},
2201        str::FromStr,
2202        sync::Arc,
2203        time::Duration,
2204    };
2205
2206    use assert_matches::assert_matches;
2207    use assign::assign;
2208    use matrix_sdk_common::deserialized_responses::TimelineEvent;
2209    use matrix_sdk_test::{
2210        async_test,
2211        event_factory::EventFactory,
2212        test_json::{sync_events::PINNED_EVENTS, TAG},
2213        ALICE, BOB, CAROL,
2214    };
2215    use ruma::{
2216        api::client::sync::sync_events::v3::RoomSummary as RumaSummary,
2217        device_id, event_id,
2218        events::{
2219            call::member::{
2220                ActiveFocus, ActiveLivekitFocus, Application, CallApplicationContent,
2221                CallMemberEventContent, CallMemberStateKey, Focus, LegacyMembershipData,
2222                LegacyMembershipDataInit, LivekitFocus, OriginalSyncCallMemberEvent,
2223            },
2224            room::{
2225                canonical_alias::RoomCanonicalAliasEventContent,
2226                encryption::{OriginalSyncRoomEncryptionEvent, RoomEncryptionEventContent},
2227                member::{MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent},
2228                name::RoomNameEventContent,
2229                pinned_events::RoomPinnedEventsEventContent,
2230            },
2231            AnySyncStateEvent, EmptyStateKey, StateEventType, StateUnsigned, SyncStateEvent,
2232        },
2233        owned_event_id, owned_room_id, owned_user_id, room_alias_id, room_id,
2234        serde::Raw,
2235        time::SystemTime,
2236        user_id, DeviceId, EventEncryptionAlgorithm, EventId, MilliSecondsSinceUnixEpoch,
2237        OwnedEventId, OwnedUserId, UserId,
2238    };
2239    use serde_json::json;
2240    use similar_asserts::assert_eq;
2241    use stream_assert::{assert_pending, assert_ready};
2242
2243    use super::{
2244        compute_display_name_from_heroes, EncryptionState, Room, RoomHero, RoomInfo, RoomState,
2245        SyncInfo,
2246    };
2247    use crate::{
2248        latest_event::LatestEvent,
2249        rooms::RoomNotableTags,
2250        store::{
2251            IntoStateStore, MemoryStore, RoomLoadSettings, StateChanges, StateStore, StoreConfig,
2252        },
2253        test_utils::logged_in_base_client,
2254        BaseClient, MinimalStateEvent, OriginalMinimalStateEvent, RoomDisplayName,
2255        RoomInfoNotableUpdateReasons, RoomStateFilter, SessionMeta,
2256    };
2257
2258    #[test]
2259    fn test_room_info_serialization() {
2260        // This test exists to make sure we don't accidentally change the
2261        // serialized format for `RoomInfo`.
2262
2263        use ruma::owned_user_id;
2264
2265        use super::RoomSummary;
2266        use crate::{rooms::BaseRoomInfo, sync::UnreadNotificationsCount};
2267
2268        let info = RoomInfo {
2269            version: 1,
2270            room_id: room_id!("!gda78o:server.tld").into(),
2271            room_state: RoomState::Invited,
2272            prev_room_state: None,
2273            notification_counts: UnreadNotificationsCount {
2274                highlight_count: 1,
2275                notification_count: 2,
2276            },
2277            summary: RoomSummary {
2278                room_heroes: vec![RoomHero {
2279                    user_id: owned_user_id!("@somebody:example.org"),
2280                    display_name: None,
2281                    avatar_url: None,
2282                }],
2283                joined_member_count: 5,
2284                invited_member_count: 0,
2285            },
2286            members_synced: true,
2287            last_prev_batch: Some("pb".to_owned()),
2288            sync_info: SyncInfo::FullySynced,
2289            encryption_state_synced: true,
2290            latest_event: Some(Box::new(LatestEvent::new(TimelineEvent::new(
2291                Raw::from_json_string(json!({"sender": "@u:i.uk"}).to_string()).unwrap(),
2292            )))),
2293            base_info: Box::new(
2294                assign!(BaseRoomInfo::new(), { pinned_events: Some(RoomPinnedEventsEventContent::new(vec![owned_event_id!("$a")])) }),
2295            ),
2296            read_receipts: Default::default(),
2297            warned_about_unknown_room_version: Arc::new(false.into()),
2298            cached_display_name: None,
2299            cached_user_defined_notification_mode: None,
2300            recency_stamp: Some(42),
2301        };
2302
2303        let info_json = json!({
2304            "version": 1,
2305            "room_id": "!gda78o:server.tld",
2306            "room_state": "Invited",
2307            "prev_room_state": null,
2308            "notification_counts": {
2309                "highlight_count": 1,
2310                "notification_count": 2,
2311            },
2312            "summary": {
2313                "room_heroes": [{
2314                    "user_id": "@somebody:example.org",
2315                    "display_name": null,
2316                    "avatar_url": null
2317                }],
2318                "joined_member_count": 5,
2319                "invited_member_count": 0,
2320            },
2321            "members_synced": true,
2322            "last_prev_batch": "pb",
2323            "sync_info": "FullySynced",
2324            "encryption_state_synced": true,
2325            "latest_event": {
2326                "event": {
2327                    "kind": {"PlainText": {"event": {"sender": "@u:i.uk"}}},
2328                },
2329            },
2330            "base_info": {
2331                "avatar": null,
2332                "canonical_alias": null,
2333                "create": null,
2334                "dm_targets": [],
2335                "encryption": null,
2336                "guest_access": null,
2337                "history_visibility": null,
2338                "is_marked_unread": false,
2339                "join_rules": null,
2340                "max_power_level": 100,
2341                "name": null,
2342                "tombstone": null,
2343                "topic": null,
2344                "pinned_events": {
2345                    "pinned": ["$a"]
2346                },
2347            },
2348            "read_receipts": {
2349                "num_unread": 0,
2350                "num_mentions": 0,
2351                "num_notifications": 0,
2352                "latest_active": null,
2353                "pending": []
2354            },
2355            "recency_stamp": 42,
2356        });
2357
2358        assert_eq!(serde_json::to_value(info).unwrap(), info_json);
2359    }
2360
2361    // Ensure we can still deserialize RoomInfos before we added things to its
2362    // schema
2363    //
2364    // In an ideal world, we must not change this test. Please see
2365    // [`test_room_info_serialization`] if you want to test a “recent” `RoomInfo`
2366    // deserialization.
2367    #[test]
2368    fn test_room_info_deserialization_without_optional_items() {
2369        use ruma::{owned_mxc_uri, owned_user_id};
2370
2371        // The following JSON should never change if we want to be able to read in old
2372        // cached state
2373        let info_json = json!({
2374            "room_id": "!gda78o:server.tld",
2375            "room_state": "Invited",
2376            "prev_room_state": null,
2377            "notification_counts": {
2378                "highlight_count": 1,
2379                "notification_count": 2,
2380            },
2381            "summary": {
2382                "room_heroes": [{
2383                    "user_id": "@somebody:example.org",
2384                    "display_name": "Somebody",
2385                    "avatar_url": "mxc://example.org/abc"
2386                }],
2387                "joined_member_count": 5,
2388                "invited_member_count": 0,
2389            },
2390            "members_synced": true,
2391            "last_prev_batch": "pb",
2392            "sync_info": "FullySynced",
2393            "encryption_state_synced": true,
2394            "base_info": {
2395                "avatar": null,
2396                "canonical_alias": null,
2397                "create": null,
2398                "dm_targets": [],
2399                "encryption": null,
2400                "guest_access": null,
2401                "history_visibility": null,
2402                "join_rules": null,
2403                "max_power_level": 100,
2404                "name": null,
2405                "tombstone": null,
2406                "topic": null,
2407            },
2408        });
2409
2410        let info: RoomInfo = serde_json::from_value(info_json).unwrap();
2411
2412        assert_eq!(info.room_id, room_id!("!gda78o:server.tld"));
2413        assert_eq!(info.room_state, RoomState::Invited);
2414        assert_eq!(info.notification_counts.highlight_count, 1);
2415        assert_eq!(info.notification_counts.notification_count, 2);
2416        assert_eq!(
2417            info.summary.room_heroes,
2418            vec![RoomHero {
2419                user_id: owned_user_id!("@somebody:example.org"),
2420                display_name: Some("Somebody".to_owned()),
2421                avatar_url: Some(owned_mxc_uri!("mxc://example.org/abc")),
2422            }]
2423        );
2424        assert_eq!(info.summary.joined_member_count, 5);
2425        assert_eq!(info.summary.invited_member_count, 0);
2426        assert!(info.members_synced);
2427        assert_eq!(info.last_prev_batch, Some("pb".to_owned()));
2428        assert_eq!(info.sync_info, SyncInfo::FullySynced);
2429        assert!(info.encryption_state_synced);
2430        assert!(info.base_info.avatar.is_none());
2431        assert!(info.base_info.canonical_alias.is_none());
2432        assert!(info.base_info.create.is_none());
2433        assert_eq!(info.base_info.dm_targets.len(), 0);
2434        assert!(info.base_info.encryption.is_none());
2435        assert!(info.base_info.guest_access.is_none());
2436        assert!(info.base_info.history_visibility.is_none());
2437        assert!(info.base_info.join_rules.is_none());
2438        assert_eq!(info.base_info.max_power_level, 100);
2439        assert!(info.base_info.name.is_none());
2440        assert!(info.base_info.tombstone.is_none());
2441        assert!(info.base_info.topic.is_none());
2442    }
2443
2444    #[test]
2445    fn test_room_info_deserialization() {
2446        use ruma::{owned_mxc_uri, owned_user_id};
2447
2448        use crate::notification_settings::RoomNotificationMode;
2449
2450        let info_json = json!({
2451            "room_id": "!gda78o:server.tld",
2452            "room_state": "Joined",
2453            "prev_room_state": "Invited",
2454            "notification_counts": {
2455                "highlight_count": 1,
2456                "notification_count": 2,
2457            },
2458            "summary": {
2459                "room_heroes": [{
2460                    "user_id": "@somebody:example.org",
2461                    "display_name": "Somebody",
2462                    "avatar_url": "mxc://example.org/abc"
2463                }],
2464                "joined_member_count": 5,
2465                "invited_member_count": 0,
2466            },
2467            "members_synced": true,
2468            "last_prev_batch": "pb",
2469            "sync_info": "FullySynced",
2470            "encryption_state_synced": true,
2471            "base_info": {
2472                "avatar": null,
2473                "canonical_alias": null,
2474                "create": null,
2475                "dm_targets": [],
2476                "encryption": null,
2477                "guest_access": null,
2478                "history_visibility": null,
2479                "join_rules": null,
2480                "max_power_level": 100,
2481                "name": null,
2482                "tombstone": null,
2483                "topic": null,
2484            },
2485            "cached_display_name": { "Calculated": "lol" },
2486            "cached_user_defined_notification_mode": "Mute",
2487            "recency_stamp": 42,
2488        });
2489
2490        let info: RoomInfo = serde_json::from_value(info_json).unwrap();
2491
2492        assert_eq!(info.room_id, room_id!("!gda78o:server.tld"));
2493        assert_eq!(info.room_state, RoomState::Joined);
2494        assert_eq!(info.prev_room_state, Some(RoomState::Invited));
2495        assert_eq!(info.notification_counts.highlight_count, 1);
2496        assert_eq!(info.notification_counts.notification_count, 2);
2497        assert_eq!(
2498            info.summary.room_heroes,
2499            vec![RoomHero {
2500                user_id: owned_user_id!("@somebody:example.org"),
2501                display_name: Some("Somebody".to_owned()),
2502                avatar_url: Some(owned_mxc_uri!("mxc://example.org/abc")),
2503            }]
2504        );
2505        assert_eq!(info.summary.joined_member_count, 5);
2506        assert_eq!(info.summary.invited_member_count, 0);
2507        assert!(info.members_synced);
2508        assert_eq!(info.last_prev_batch, Some("pb".to_owned()));
2509        assert_eq!(info.sync_info, SyncInfo::FullySynced);
2510        assert!(info.encryption_state_synced);
2511        assert!(info.latest_event.is_none());
2512        assert!(info.base_info.avatar.is_none());
2513        assert!(info.base_info.canonical_alias.is_none());
2514        assert!(info.base_info.create.is_none());
2515        assert_eq!(info.base_info.dm_targets.len(), 0);
2516        assert!(info.base_info.encryption.is_none());
2517        assert!(info.base_info.guest_access.is_none());
2518        assert!(info.base_info.history_visibility.is_none());
2519        assert!(info.base_info.join_rules.is_none());
2520        assert_eq!(info.base_info.max_power_level, 100);
2521        assert!(info.base_info.name.is_none());
2522        assert!(info.base_info.tombstone.is_none());
2523        assert!(info.base_info.topic.is_none());
2524
2525        assert_eq!(
2526            info.cached_display_name.as_ref(),
2527            Some(&RoomDisplayName::Calculated("lol".to_owned())),
2528        );
2529        assert_eq!(
2530            info.cached_user_defined_notification_mode.as_ref(),
2531            Some(&RoomNotificationMode::Mute)
2532        );
2533        assert_eq!(info.recency_stamp.as_ref(), Some(&42));
2534    }
2535
2536    #[async_test]
2537    async fn test_is_favourite() {
2538        // Given a room,
2539        let client =
2540            BaseClient::new(StoreConfig::new("cross-process-store-locks-holder-name".to_owned()));
2541
2542        client
2543            .activate(
2544                SessionMeta {
2545                    user_id: user_id!("@alice:example.org").into(),
2546                    device_id: ruma::device_id!("AYEAYEAYE").into(),
2547                },
2548                RoomLoadSettings::default(),
2549                #[cfg(feature = "e2e-encryption")]
2550                None,
2551            )
2552            .await
2553            .unwrap();
2554
2555        let room_id = room_id!("!test:localhost");
2556        let room = client.get_or_create_room(room_id, RoomState::Joined);
2557
2558        // Sanity checks to ensure the room isn't marked as favourite.
2559        assert!(room.is_favourite().not());
2560
2561        // Subscribe to the `RoomInfo`.
2562        let mut room_info_subscriber = room.subscribe_info();
2563
2564        assert_pending!(room_info_subscriber);
2565
2566        // Create the tag.
2567        let tag_raw = Raw::new(&json!({
2568            "content": {
2569                "tags": {
2570                    "m.favourite": {
2571                        "order": 0.0
2572                    },
2573                },
2574            },
2575            "type": "m.tag",
2576        }))
2577        .unwrap()
2578        .cast();
2579
2580        // When the new tag is handled and applied.
2581        let mut changes = StateChanges::default();
2582        client
2583            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2584            .await;
2585        client.apply_changes(&changes, Default::default(), None);
2586
2587        // The `RoomInfo` is getting notified.
2588        assert_ready!(room_info_subscriber);
2589        assert_pending!(room_info_subscriber);
2590
2591        // The room is now marked as favourite.
2592        assert!(room.is_favourite());
2593
2594        // Now, let's remove the tag.
2595        let tag_raw = Raw::new(&json!({
2596            "content": {
2597                "tags": {},
2598            },
2599            "type": "m.tag"
2600        }))
2601        .unwrap()
2602        .cast();
2603        client
2604            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2605            .await;
2606        client.apply_changes(&changes, Default::default(), None);
2607
2608        // The `RoomInfo` is getting notified.
2609        assert_ready!(room_info_subscriber);
2610        assert_pending!(room_info_subscriber);
2611
2612        // The room is now marked as _not_ favourite.
2613        assert!(room.is_favourite().not());
2614    }
2615
2616    #[async_test]
2617    async fn test_is_low_priority() {
2618        // Given a room,
2619        let client =
2620            BaseClient::new(StoreConfig::new("cross-process-store-locks-holder-name".to_owned()));
2621
2622        client
2623            .activate(
2624                SessionMeta {
2625                    user_id: user_id!("@alice:example.org").into(),
2626                    device_id: ruma::device_id!("AYEAYEAYE").into(),
2627                },
2628                RoomLoadSettings::default(),
2629                #[cfg(feature = "e2e-encryption")]
2630                None,
2631            )
2632            .await
2633            .unwrap();
2634
2635        let room_id = room_id!("!test:localhost");
2636        let room = client.get_or_create_room(room_id, RoomState::Joined);
2637
2638        // Sanity checks to ensure the room isn't marked as low priority.
2639        assert!(!room.is_low_priority());
2640
2641        // Subscribe to the `RoomInfo`.
2642        let mut room_info_subscriber = room.subscribe_info();
2643
2644        assert_pending!(room_info_subscriber);
2645
2646        // Create the tag.
2647        let tag_raw = Raw::new(&json!({
2648            "content": {
2649                "tags": {
2650                    "m.lowpriority": {
2651                        "order": 0.0
2652                    },
2653                }
2654            },
2655            "type": "m.tag"
2656        }))
2657        .unwrap()
2658        .cast();
2659
2660        // When the new tag is handled and applied.
2661        let mut changes = StateChanges::default();
2662        client
2663            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2664            .await;
2665        client.apply_changes(&changes, Default::default(), None);
2666
2667        // The `RoomInfo` is getting notified.
2668        assert_ready!(room_info_subscriber);
2669        assert_pending!(room_info_subscriber);
2670
2671        // The room is now marked as low priority.
2672        assert!(room.is_low_priority());
2673
2674        // Now, let's remove the tag.
2675        let tag_raw = Raw::new(&json!({
2676            "content": {
2677                "tags": {},
2678            },
2679            "type": "m.tag"
2680        }))
2681        .unwrap()
2682        .cast();
2683        client
2684            .handle_room_account_data(room_id, &[tag_raw], &mut changes, &mut Default::default())
2685            .await;
2686        client.apply_changes(&changes, Default::default(), None);
2687
2688        // The `RoomInfo` is getting notified.
2689        assert_ready!(room_info_subscriber);
2690        assert_pending!(room_info_subscriber);
2691
2692        // The room is now marked as _not_ low priority.
2693        assert!(room.is_low_priority().not());
2694    }
2695
2696    fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
2697        let store = Arc::new(MemoryStore::new());
2698        let user_id = user_id!("@me:example.org");
2699        let room_id = room_id!("!test:localhost");
2700        let (sender, _receiver) = tokio::sync::broadcast::channel(1);
2701
2702        (store.clone(), Room::new(user_id, store, room_id, room_type, sender))
2703    }
2704
2705    fn make_stripped_member_event(user_id: &UserId, name: &str) -> Raw<StrippedRoomMemberEvent> {
2706        let ev_json = json!({
2707            "type": "m.room.member",
2708            "content": assign!(RoomMemberEventContent::new(MembershipState::Join), {
2709                displayname: Some(name.to_owned())
2710            }),
2711            "sender": user_id,
2712            "state_key": user_id,
2713        });
2714
2715        Raw::new(&ev_json).unwrap().cast()
2716    }
2717
2718    #[async_test]
2719    async fn test_display_name_for_joined_room_is_empty_if_no_info() {
2720        let (_, room) = make_room_test_helper(RoomState::Joined);
2721        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2722    }
2723
2724    #[async_test]
2725    async fn test_display_name_for_joined_room_uses_canonical_alias_if_available() {
2726        let (_, room) = make_room_test_helper(RoomState::Joined);
2727        room.inner
2728            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2729        assert_eq!(
2730            room.compute_display_name().await.unwrap(),
2731            RoomDisplayName::Aliased("test".to_owned())
2732        );
2733    }
2734
2735    #[async_test]
2736    async fn test_display_name_for_joined_room_prefers_name_over_alias() {
2737        let (_, room) = make_room_test_helper(RoomState::Joined);
2738        room.inner
2739            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2740        assert_eq!(
2741            room.compute_display_name().await.unwrap(),
2742            RoomDisplayName::Aliased("test".to_owned())
2743        );
2744        room.inner.update(|info| info.base_info.name = Some(make_name_event()));
2745        // Display name wasn't cached when we asked for it above, and name overrides
2746        assert_eq!(
2747            room.compute_display_name().await.unwrap(),
2748            RoomDisplayName::Named("Test Room".to_owned())
2749        );
2750    }
2751
2752    #[async_test]
2753    async fn test_display_name_for_invited_room_is_empty_if_no_info() {
2754        let (_, room) = make_room_test_helper(RoomState::Invited);
2755        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2756    }
2757
2758    #[async_test]
2759    async fn test_display_name_for_invited_room_is_empty_if_room_name_empty() {
2760        let (_, room) = make_room_test_helper(RoomState::Invited);
2761
2762        let room_name = MinimalStateEvent::Original(OriginalMinimalStateEvent {
2763            content: RoomNameEventContent::new(String::new()),
2764            event_id: None,
2765        });
2766        room.inner.update(|info| info.base_info.name = Some(room_name));
2767
2768        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2769    }
2770
2771    #[async_test]
2772    async fn test_display_name_for_invited_room_uses_canonical_alias_if_available() {
2773        let (_, room) = make_room_test_helper(RoomState::Invited);
2774        room.inner
2775            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2776        assert_eq!(
2777            room.compute_display_name().await.unwrap(),
2778            RoomDisplayName::Aliased("test".to_owned())
2779        );
2780    }
2781
2782    #[async_test]
2783    async fn test_display_name_for_invited_room_prefers_name_over_alias() {
2784        let (_, room) = make_room_test_helper(RoomState::Invited);
2785        room.inner
2786            .update(|info| info.base_info.canonical_alias = Some(make_canonical_alias_event()));
2787        assert_eq!(
2788            room.compute_display_name().await.unwrap(),
2789            RoomDisplayName::Aliased("test".to_owned())
2790        );
2791        room.inner.update(|info| info.base_info.name = Some(make_name_event()));
2792        // Display name wasn't cached when we asked for it above, and name overrides
2793        assert_eq!(
2794            room.compute_display_name().await.unwrap(),
2795            RoomDisplayName::Named("Test Room".to_owned())
2796        );
2797    }
2798
2799    fn make_canonical_alias_event() -> MinimalStateEvent<RoomCanonicalAliasEventContent> {
2800        MinimalStateEvent::Original(OriginalMinimalStateEvent {
2801            content: assign!(RoomCanonicalAliasEventContent::new(), {
2802                alias: Some(room_alias_id!("#test:example.com").to_owned()),
2803            }),
2804            event_id: None,
2805        })
2806    }
2807
2808    fn make_name_event() -> MinimalStateEvent<RoomNameEventContent> {
2809        MinimalStateEvent::Original(OriginalMinimalStateEvent {
2810            content: RoomNameEventContent::new("Test Room".to_owned()),
2811            event_id: None,
2812        })
2813    }
2814
2815    #[async_test]
2816    async fn test_display_name_dm_invited() {
2817        let (store, room) = make_room_test_helper(RoomState::Invited);
2818        let room_id = room_id!("!test:localhost");
2819        let matthew = user_id!("@matthew:example.org");
2820        let me = user_id!("@me:example.org");
2821        let mut changes = StateChanges::new("".to_owned());
2822        let summary = assign!(RumaSummary::new(), {
2823            heroes: vec![me.to_owned(), matthew.to_owned()],
2824        });
2825
2826        changes.add_stripped_member(
2827            room_id,
2828            matthew,
2829            make_stripped_member_event(matthew, "Matthew"),
2830        );
2831        changes.add_stripped_member(room_id, me, make_stripped_member_event(me, "Me"));
2832        store.save_changes(&changes).await.unwrap();
2833
2834        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2835        assert_eq!(
2836            room.compute_display_name().await.unwrap(),
2837            RoomDisplayName::Calculated("Matthew".to_owned())
2838        );
2839    }
2840
2841    #[async_test]
2842    async fn test_display_name_dm_invited_no_heroes() {
2843        let (store, room) = make_room_test_helper(RoomState::Invited);
2844        let room_id = room_id!("!test:localhost");
2845        let matthew = user_id!("@matthew:example.org");
2846        let me = user_id!("@me:example.org");
2847        let mut changes = StateChanges::new("".to_owned());
2848
2849        changes.add_stripped_member(
2850            room_id,
2851            matthew,
2852            make_stripped_member_event(matthew, "Matthew"),
2853        );
2854        changes.add_stripped_member(room_id, me, make_stripped_member_event(me, "Me"));
2855        store.save_changes(&changes).await.unwrap();
2856
2857        assert_eq!(
2858            room.compute_display_name().await.unwrap(),
2859            RoomDisplayName::Calculated("Matthew".to_owned())
2860        );
2861    }
2862
2863    #[async_test]
2864    async fn test_display_name_dm_joined() {
2865        let (store, room) = make_room_test_helper(RoomState::Joined);
2866        let room_id = room_id!("!test:localhost");
2867        let matthew = user_id!("@matthew:example.org");
2868        let me = user_id!("@me:example.org");
2869
2870        let mut changes = StateChanges::new("".to_owned());
2871        let summary = assign!(RumaSummary::new(), {
2872            joined_member_count: Some(2u32.into()),
2873            heroes: vec![me.to_owned(), matthew.to_owned()],
2874        });
2875
2876        let f = EventFactory::new().room(room_id!("!test:localhost"));
2877
2878        let members = changes
2879            .state
2880            .entry(room_id.to_owned())
2881            .or_default()
2882            .entry(StateEventType::RoomMember)
2883            .or_default();
2884        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2885        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2886
2887        store.save_changes(&changes).await.unwrap();
2888
2889        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2890        assert_eq!(
2891            room.compute_display_name().await.unwrap(),
2892            RoomDisplayName::Calculated("Matthew".to_owned())
2893        );
2894    }
2895
2896    #[async_test]
2897    async fn test_display_name_dm_joined_service_members() {
2898        let (store, room) = make_room_test_helper(RoomState::Joined);
2899        let room_id = room_id!("!test:localhost");
2900
2901        let matthew = user_id!("@sahasrhala:example.org");
2902        let me = user_id!("@me:example.org");
2903        let bot = user_id!("@bot:example.org");
2904
2905        let mut changes = StateChanges::new("".to_owned());
2906        let summary = assign!(RumaSummary::new(), {
2907            joined_member_count: Some(3u32.into()),
2908            heroes: vec![me.to_owned(), matthew.to_owned(), bot.to_owned()],
2909        });
2910
2911        let f = EventFactory::new().room(room_id!("!test:localhost"));
2912
2913        let members = changes
2914            .state
2915            .entry(room_id.to_owned())
2916            .or_default()
2917            .entry(StateEventType::RoomMember)
2918            .or_default();
2919        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
2920        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2921        members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
2922
2923        let member_hints_content =
2924            f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
2925        changes
2926            .state
2927            .entry(room_id.to_owned())
2928            .or_default()
2929            .entry(StateEventType::MemberHints)
2930            .or_default()
2931            .insert("".to_owned(), member_hints_content);
2932
2933        store.save_changes(&changes).await.unwrap();
2934
2935        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2936        // Bot should not contribute to the display name.
2937        assert_eq!(
2938            room.compute_display_name().await.unwrap(),
2939            RoomDisplayName::Calculated("Matthew".to_owned())
2940        );
2941    }
2942
2943    #[async_test]
2944    async fn test_display_name_dm_joined_alone_with_service_members() {
2945        let (store, room) = make_room_test_helper(RoomState::Joined);
2946        let room_id = room_id!("!test:localhost");
2947
2948        let me = user_id!("@me:example.org");
2949        let bot = user_id!("@bot:example.org");
2950
2951        let mut changes = StateChanges::new("".to_owned());
2952        let summary = assign!(RumaSummary::new(), {
2953            joined_member_count: Some(2u32.into()),
2954            heroes: vec![me.to_owned(), bot.to_owned()],
2955        });
2956
2957        let f = EventFactory::new().room(room_id!("!test:localhost"));
2958
2959        let members = changes
2960            .state
2961            .entry(room_id.to_owned())
2962            .or_default()
2963            .entry(StateEventType::RoomMember)
2964            .or_default();
2965        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
2966        members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
2967
2968        let member_hints_content =
2969            f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
2970        changes
2971            .state
2972            .entry(room_id.to_owned())
2973            .or_default()
2974            .entry(StateEventType::MemberHints)
2975            .or_default()
2976            .insert("".to_owned(), member_hints_content);
2977
2978        store.save_changes(&changes).await.unwrap();
2979
2980        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
2981        // Bot should not contribute to the display name.
2982        assert_eq!(room.compute_display_name().await.unwrap(), RoomDisplayName::Empty);
2983    }
2984
2985    #[async_test]
2986    async fn test_display_name_dm_joined_no_heroes() {
2987        let (store, room) = make_room_test_helper(RoomState::Joined);
2988        let room_id = room_id!("!test:localhost");
2989        let matthew = user_id!("@matthew:example.org");
2990        let me = user_id!("@me:example.org");
2991        let mut changes = StateChanges::new("".to_owned());
2992
2993        let f = EventFactory::new().room(room_id!("!test:localhost"));
2994
2995        let members = changes
2996            .state
2997            .entry(room_id.to_owned())
2998            .or_default()
2999            .entry(StateEventType::RoomMember)
3000            .or_default();
3001        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
3002        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3003
3004        store.save_changes(&changes).await.unwrap();
3005
3006        assert_eq!(
3007            room.compute_display_name().await.unwrap(),
3008            RoomDisplayName::Calculated("Matthew".to_owned())
3009        );
3010    }
3011
3012    #[async_test]
3013    async fn test_display_name_dm_joined_no_heroes_service_members() {
3014        let (store, room) = make_room_test_helper(RoomState::Joined);
3015        let room_id = room_id!("!test:localhost");
3016
3017        let matthew = user_id!("@matthew:example.org");
3018        let me = user_id!("@me:example.org");
3019        let bot = user_id!("@bot:example.org");
3020
3021        let mut changes = StateChanges::new("".to_owned());
3022
3023        let f = EventFactory::new().room(room_id!("!test:localhost"));
3024
3025        let members = changes
3026            .state
3027            .entry(room_id.to_owned())
3028            .or_default()
3029            .entry(StateEventType::RoomMember)
3030            .or_default();
3031        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
3032        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3033        members.insert(bot.into(), f.member(bot).display_name("Bot").into_raw());
3034
3035        let member_hints_content =
3036            f.member_hints(BTreeSet::from([bot.to_owned()])).sender(me).into_raw();
3037        changes
3038            .state
3039            .entry(room_id.to_owned())
3040            .or_default()
3041            .entry(StateEventType::MemberHints)
3042            .or_default()
3043            .insert("".to_owned(), member_hints_content);
3044
3045        store.save_changes(&changes).await.unwrap();
3046
3047        assert_eq!(
3048            room.compute_display_name().await.unwrap(),
3049            RoomDisplayName::Calculated("Matthew".to_owned())
3050        );
3051    }
3052
3053    #[async_test]
3054    async fn test_display_name_deterministic() {
3055        let (store, room) = make_room_test_helper(RoomState::Joined);
3056
3057        let alice = user_id!("@alice:example.org");
3058        let bob = user_id!("@bob:example.org");
3059        let carol = user_id!("@carol:example.org");
3060        let denis = user_id!("@denis:example.org");
3061        let erica = user_id!("@erica:example.org");
3062        let fred = user_id!("@fred:example.org");
3063        let me = user_id!("@me:example.org");
3064
3065        let mut changes = StateChanges::new("".to_owned());
3066
3067        let f = EventFactory::new().room(room_id!("!test:localhost"));
3068
3069        // Save members in two batches, so that there's no implied ordering in the
3070        // store.
3071        {
3072            let members = changes
3073                .state
3074                .entry(room.room_id().to_owned())
3075                .or_default()
3076                .entry(StateEventType::RoomMember)
3077                .or_default();
3078            members.insert(carol.into(), f.member(carol).display_name("Carol").into_raw());
3079            members.insert(bob.into(), f.member(bob).display_name("Bob").into_raw());
3080            members.insert(fred.into(), f.member(fred).display_name("Fred").into_raw());
3081            members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3082            store.save_changes(&changes).await.unwrap();
3083        }
3084
3085        {
3086            let members = changes
3087                .state
3088                .entry(room.room_id().to_owned())
3089                .or_default()
3090                .entry(StateEventType::RoomMember)
3091                .or_default();
3092            members.insert(alice.into(), f.member(alice).display_name("Alice").into_raw());
3093            members.insert(erica.into(), f.member(erica).display_name("Erica").into_raw());
3094            members.insert(denis.into(), f.member(denis).display_name("Denis").into_raw());
3095            store.save_changes(&changes).await.unwrap();
3096        }
3097
3098        let summary = assign!(RumaSummary::new(), {
3099            joined_member_count: Some(7u32.into()),
3100            heroes: vec![denis.to_owned(), carol.to_owned(), bob.to_owned(), erica.to_owned()],
3101        });
3102        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
3103
3104        assert_eq!(
3105            room.compute_display_name().await.unwrap(),
3106            RoomDisplayName::Calculated("Bob, Carol, Denis, Erica, and 3 others".to_owned())
3107        );
3108    }
3109
3110    #[async_test]
3111    async fn test_display_name_deterministic_no_heroes() {
3112        let (store, room) = make_room_test_helper(RoomState::Joined);
3113
3114        let alice = user_id!("@alice:example.org");
3115        let bob = user_id!("@bob:example.org");
3116        let carol = user_id!("@carol:example.org");
3117        let denis = user_id!("@denis:example.org");
3118        let erica = user_id!("@erica:example.org");
3119        let fred = user_id!("@fred:example.org");
3120        let me = user_id!("@me:example.org");
3121
3122        let f = EventFactory::new().room(room_id!("!test:localhost"));
3123
3124        let mut changes = StateChanges::new("".to_owned());
3125
3126        // Save members in two batches, so that there's no implied ordering in the
3127        // store.
3128        {
3129            let members = changes
3130                .state
3131                .entry(room.room_id().to_owned())
3132                .or_default()
3133                .entry(StateEventType::RoomMember)
3134                .or_default();
3135            members.insert(carol.into(), f.member(carol).display_name("Carol").into_raw());
3136            members.insert(bob.into(), f.member(bob).display_name("Bob").into_raw());
3137            members.insert(fred.into(), f.member(fred).display_name("Fred").into_raw());
3138            members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3139
3140            store.save_changes(&changes).await.unwrap();
3141        }
3142
3143        {
3144            let members = changes
3145                .state
3146                .entry(room.room_id().to_owned())
3147                .or_default()
3148                .entry(StateEventType::RoomMember)
3149                .or_default();
3150            members.insert(alice.into(), f.member(alice).display_name("Alice").into_raw());
3151            members.insert(erica.into(), f.member(erica).display_name("Erica").into_raw());
3152            members.insert(denis.into(), f.member(denis).display_name("Denis").into_raw());
3153            store.save_changes(&changes).await.unwrap();
3154        }
3155
3156        assert_eq!(
3157            room.compute_display_name().await.unwrap(),
3158            RoomDisplayName::Calculated("Alice, Bob, Carol, Denis, Erica, and 2 others".to_owned())
3159        );
3160    }
3161
3162    #[async_test]
3163    async fn test_display_name_dm_alone() {
3164        let (store, room) = make_room_test_helper(RoomState::Joined);
3165        let room_id = room_id!("!test:localhost");
3166        let matthew = user_id!("@matthew:example.org");
3167        let me = user_id!("@me:example.org");
3168        let mut changes = StateChanges::new("".to_owned());
3169        let summary = assign!(RumaSummary::new(), {
3170            joined_member_count: Some(1u32.into()),
3171            heroes: vec![me.to_owned(), matthew.to_owned()],
3172        });
3173
3174        let f = EventFactory::new().room(room_id!("!test:localhost"));
3175
3176        let members = changes
3177            .state
3178            .entry(room_id.to_owned())
3179            .or_default()
3180            .entry(StateEventType::RoomMember)
3181            .or_default();
3182        members.insert(matthew.into(), f.member(matthew).display_name("Matthew").into_raw());
3183        members.insert(me.into(), f.member(me).display_name("Me").into_raw());
3184
3185        store.save_changes(&changes).await.unwrap();
3186
3187        room.inner.update_if(|info| info.update_from_ruma_summary(&summary));
3188        assert_eq!(
3189            room.compute_display_name().await.unwrap(),
3190            RoomDisplayName::EmptyWas("Matthew".to_owned())
3191        );
3192    }
3193
3194    #[cfg(feature = "e2e-encryption")]
3195    #[async_test]
3196    async fn test_setting_the_latest_event_doesnt_cause_a_room_info_notable_update() {
3197        use std::collections::BTreeMap;
3198
3199        use assert_matches::assert_matches;
3200
3201        use crate::{RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons};
3202
3203        // Given a room,
3204        let client =
3205            BaseClient::new(StoreConfig::new("cross-process-store-locks-holder-name".to_owned()));
3206
3207        client
3208            .activate(
3209                SessionMeta {
3210                    user_id: user_id!("@alice:example.org").into(),
3211                    device_id: ruma::device_id!("AYEAYEAYE").into(),
3212                },
3213                RoomLoadSettings::default(),
3214                None,
3215            )
3216            .await
3217            .unwrap();
3218
3219        let room_id = room_id!("!test:localhost");
3220        let room = client.get_or_create_room(room_id, RoomState::Joined);
3221
3222        // That has an encrypted event,
3223        add_encrypted_event(&room, "$A");
3224        // Sanity: it has no latest_event
3225        assert!(room.latest_event().is_none());
3226
3227        // When I set up an observer on the latest_event,
3228        let mut room_info_notable_update = client.room_info_notable_update_receiver();
3229
3230        // And I provide a decrypted event to replace the encrypted one,
3231        let event = make_latest_event("$A");
3232
3233        let mut changes = StateChanges::default();
3234        let mut room_info_notable_updates = BTreeMap::new();
3235        room.on_latest_event_decrypted(
3236            event.clone(),
3237            0,
3238            &mut changes,
3239            &mut room_info_notable_updates,
3240        );
3241
3242        assert!(room_info_notable_updates.contains_key(room_id));
3243
3244        // The subscriber isn't notified at this point.
3245        assert!(room_info_notable_update.try_recv().is_err());
3246
3247        // Then updating the room info will store the event,
3248        client.apply_changes(&changes, room_info_notable_updates, None);
3249        assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
3250
3251        // And wake up the subscriber.
3252        assert_matches!(
3253            room_info_notable_update.recv().await,
3254            Ok(RoomInfoNotableUpdate { room_id: received_room_id, reasons }) => {
3255                assert_eq!(received_room_id, room_id);
3256                assert!(reasons.contains(RoomInfoNotableUpdateReasons::LATEST_EVENT));
3257            }
3258        );
3259    }
3260
3261    #[cfg(feature = "e2e-encryption")]
3262    #[async_test]
3263    async fn test_when_we_provide_a_newly_decrypted_event_it_replaces_latest_event() {
3264        use std::collections::BTreeMap;
3265
3266        // Given a room with an encrypted event
3267        let (_store, room) = make_room_test_helper(RoomState::Joined);
3268        add_encrypted_event(&room, "$A");
3269        // Sanity: it has no latest_event
3270        assert!(room.latest_event().is_none());
3271
3272        // When I provide a decrypted event to replace the encrypted one
3273        let event = make_latest_event("$A");
3274        let mut changes = StateChanges::default();
3275        let mut room_info_notable_updates = BTreeMap::new();
3276        room.on_latest_event_decrypted(
3277            event.clone(),
3278            0,
3279            &mut changes,
3280            &mut room_info_notable_updates,
3281        );
3282        room.set_room_info(
3283            changes.room_infos.get(room.room_id()).cloned().unwrap(),
3284            room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3285        );
3286
3287        // Then is it stored
3288        assert_eq!(room.latest_event().unwrap().event_id(), event.event_id());
3289    }
3290
3291    #[cfg(feature = "e2e-encryption")]
3292    #[async_test]
3293    async fn test_when_a_newly_decrypted_event_appears_we_delete_all_older_encrypted_events() {
3294        use std::collections::BTreeMap;
3295
3296        // Given a room with some encrypted events and a latest event
3297        let (_store, room) = make_room_test_helper(RoomState::Joined);
3298        room.inner.update(|info| info.latest_event = Some(make_latest_event("$A")));
3299        add_encrypted_event(&room, "$0");
3300        add_encrypted_event(&room, "$1");
3301        add_encrypted_event(&room, "$2");
3302        add_encrypted_event(&room, "$3");
3303
3304        // When I provide a latest event
3305        let new_event = make_latest_event("$1");
3306        let new_event_index = 1;
3307        let mut changes = StateChanges::default();
3308        let mut room_info_notable_updates = BTreeMap::new();
3309        room.on_latest_event_decrypted(
3310            new_event.clone(),
3311            new_event_index,
3312            &mut changes,
3313            &mut room_info_notable_updates,
3314        );
3315        room.set_room_info(
3316            changes.room_infos.get(room.room_id()).cloned().unwrap(),
3317            room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3318        );
3319
3320        // Then the encrypted events list is shortened to only newer events
3321        let enc_evs = room.latest_encrypted_events();
3322        assert_eq!(enc_evs.len(), 2);
3323        assert_eq!(enc_evs[0].get_field::<&str>("event_id").unwrap().unwrap(), "$2");
3324        assert_eq!(enc_evs[1].get_field::<&str>("event_id").unwrap().unwrap(), "$3");
3325
3326        // And the event is stored
3327        assert_eq!(room.latest_event().unwrap().event_id(), new_event.event_id());
3328    }
3329
3330    #[cfg(feature = "e2e-encryption")]
3331    #[async_test]
3332    async fn test_replacing_the_newest_event_leaves_none_left() {
3333        use std::collections::BTreeMap;
3334
3335        // Given a room with some encrypted events
3336        let (_store, room) = make_room_test_helper(RoomState::Joined);
3337        add_encrypted_event(&room, "$0");
3338        add_encrypted_event(&room, "$1");
3339        add_encrypted_event(&room, "$2");
3340        add_encrypted_event(&room, "$3");
3341
3342        // When I provide a latest event and say it was the very latest
3343        let new_event = make_latest_event("$3");
3344        let new_event_index = 3;
3345        let mut changes = StateChanges::default();
3346        let mut room_info_notable_updates = BTreeMap::new();
3347        room.on_latest_event_decrypted(
3348            new_event,
3349            new_event_index,
3350            &mut changes,
3351            &mut room_info_notable_updates,
3352        );
3353        room.set_room_info(
3354            changes.room_infos.get(room.room_id()).cloned().unwrap(),
3355            room_info_notable_updates.get(room.room_id()).copied().unwrap(),
3356        );
3357
3358        // Then the encrypted events list ie empty
3359        let enc_evs = room.latest_encrypted_events();
3360        assert_eq!(enc_evs.len(), 0);
3361    }
3362
3363    #[cfg(feature = "e2e-encryption")]
3364    fn add_encrypted_event(room: &Room, event_id: &str) {
3365        room.latest_encrypted_events
3366            .write()
3367            .unwrap()
3368            .push(Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap());
3369    }
3370
3371    #[cfg(feature = "e2e-encryption")]
3372    fn make_latest_event(event_id: &str) -> Box<LatestEvent> {
3373        Box::new(LatestEvent::new(TimelineEvent::new(
3374            Raw::from_json_string(json!({ "event_id": event_id }).to_string()).unwrap(),
3375        )))
3376    }
3377
3378    fn timestamp(minutes_ago: u32) -> MilliSecondsSinceUnixEpoch {
3379        MilliSecondsSinceUnixEpoch::from_system_time(
3380            SystemTime::now().sub(Duration::from_secs((60 * minutes_ago).into())),
3381        )
3382        .expect("date out of range")
3383    }
3384
3385    fn legacy_membership_for_my_call(
3386        device_id: &DeviceId,
3387        membership_id: &str,
3388        minutes_ago: u32,
3389    ) -> LegacyMembershipData {
3390        let (application, foci) = foci_and_application();
3391        assign!(
3392            LegacyMembershipData::from(LegacyMembershipDataInit {
3393                application,
3394                device_id: device_id.to_owned(),
3395                expires: Duration::from_millis(3_600_000),
3396                foci_active: foci,
3397                membership_id: membership_id.to_owned(),
3398            }),
3399            { created_ts: Some(timestamp(minutes_ago)) }
3400        )
3401    }
3402
3403    fn legacy_member_state_event(
3404        memberships: Vec<LegacyMembershipData>,
3405        ev_id: &EventId,
3406        user_id: &UserId,
3407    ) -> AnySyncStateEvent {
3408        let content = CallMemberEventContent::new_legacy(memberships);
3409
3410        AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
3411            content,
3412            event_id: ev_id.to_owned(),
3413            sender: user_id.to_owned(),
3414            // we can simply use now here since this will be dropped when using a MinimalStateEvent
3415            // in the roomInfo
3416            origin_server_ts: timestamp(0),
3417            state_key: CallMemberStateKey::new(user_id.to_owned(), None, false),
3418            unsigned: StateUnsigned::new(),
3419        }))
3420    }
3421
3422    struct InitData<'a> {
3423        device_id: &'a DeviceId,
3424        minutes_ago: u32,
3425    }
3426
3427    fn session_member_state_event(
3428        ev_id: &EventId,
3429        user_id: &UserId,
3430        init_data: Option<InitData<'_>>,
3431    ) -> AnySyncStateEvent {
3432        let application = Application::Call(CallApplicationContent::new(
3433            "my_call_id_1".to_owned(),
3434            ruma::events::call::member::CallScope::Room,
3435        ));
3436        let foci_preferred = vec![Focus::Livekit(LivekitFocus::new(
3437            "my_call_foci_alias".to_owned(),
3438            "https://lk.org".to_owned(),
3439        ))];
3440        let focus_active = ActiveFocus::Livekit(ActiveLivekitFocus::new());
3441        let (content, state_key) = match init_data {
3442            Some(InitData { device_id, minutes_ago }) => (
3443                CallMemberEventContent::new(
3444                    application,
3445                    device_id.to_owned(),
3446                    focus_active,
3447                    foci_preferred,
3448                    Some(timestamp(minutes_ago)),
3449                ),
3450                CallMemberStateKey::new(user_id.to_owned(), Some(device_id.to_owned()), false),
3451            ),
3452            None => (
3453                CallMemberEventContent::new_empty(None),
3454                CallMemberStateKey::new(user_id.to_owned(), None, false),
3455            ),
3456        };
3457
3458        AnySyncStateEvent::CallMember(SyncStateEvent::Original(OriginalSyncCallMemberEvent {
3459            content,
3460            event_id: ev_id.to_owned(),
3461            sender: user_id.to_owned(),
3462            // we can simply use now here since this will be dropped when using a MinimalStateEvent
3463            // in the roomInfo
3464            origin_server_ts: timestamp(0),
3465            state_key,
3466            unsigned: StateUnsigned::new(),
3467        }))
3468    }
3469
3470    fn foci_and_application() -> (Application, Vec<Focus>) {
3471        (
3472            Application::Call(CallApplicationContent::new(
3473                "my_call_id_1".to_owned(),
3474                ruma::events::call::member::CallScope::Room,
3475            )),
3476            vec![Focus::Livekit(LivekitFocus::new(
3477                "my_call_foci_alias".to_owned(),
3478                "https://lk.org".to_owned(),
3479            ))],
3480        )
3481    }
3482
3483    fn receive_state_events(room: &Room, events: Vec<&AnySyncStateEvent>) {
3484        room.inner.update_if(|info| {
3485            let mut res = false;
3486            for ev in events {
3487                res |= info.handle_state_event(ev);
3488            }
3489            res
3490        });
3491    }
3492
3493    /// `user_a`: empty memberships
3494    /// `user_b`: one membership
3495    /// `user_c`: two memberships (two devices)
3496    fn legacy_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
3497        let (_, room) = make_room_test_helper(RoomState::Joined);
3498
3499        let a_empty = legacy_member_state_event(Vec::new(), event_id!("$1234"), a);
3500
3501        // make b 10min old
3502        let m_init_b = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 1);
3503        let b_one = legacy_member_state_event(vec![m_init_b], event_id!("$12345"), b);
3504
3505        // c1 1min old
3506        let m_init_c1 = legacy_membership_for_my_call(device_id!("DEVICE_0"), "0", 10);
3507        // c2 20min old
3508        let m_init_c2 = legacy_membership_for_my_call(device_id!("DEVICE_1"), "0", 20);
3509        let c_two = legacy_member_state_event(vec![m_init_c1, m_init_c2], event_id!("$123456"), c);
3510
3511        // Intentionally use a non time sorted receive order.
3512        receive_state_events(&room, vec![&c_two, &a_empty, &b_one]);
3513
3514        room
3515    }
3516
3517    /// `user_a`: empty memberships
3518    /// `user_b`: one membership
3519    /// `user_c`: two memberships (two devices)
3520    fn session_create_call_with_member_events_for_user(a: &UserId, b: &UserId, c: &UserId) -> Room {
3521        let (_, room) = make_room_test_helper(RoomState::Joined);
3522
3523        let a_empty = session_member_state_event(event_id!("$1234"), a, None);
3524
3525        // make b 10min old
3526        let b_one = session_member_state_event(
3527            event_id!("$12345"),
3528            b,
3529            Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 1 }),
3530        );
3531
3532        let m_c1 = session_member_state_event(
3533            event_id!("$123456_0"),
3534            c,
3535            Some(InitData { device_id: "DEVICE_0".into(), minutes_ago: 10 }),
3536        );
3537        let m_c2 = session_member_state_event(
3538            event_id!("$123456_1"),
3539            c,
3540            Some(InitData { device_id: "DEVICE_1".into(), minutes_ago: 20 }),
3541        );
3542        // Intentionally use a non time sorted receive order1
3543        receive_state_events(&room, vec![&m_c1, &m_c2, &a_empty, &b_one]);
3544
3545        room
3546    }
3547
3548    #[test]
3549    fn test_show_correct_active_call_state() {
3550        let room_legacy = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3551
3552        // This check also tests the ordering.
3553        // We want older events to be in the front.
3554        // user_b (Bob) is 1min old, c1 (CAROL) 10min old, c2 (CAROL) 20min old
3555        assert_eq!(
3556            vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
3557            room_legacy.active_room_call_participants()
3558        );
3559        assert!(room_legacy.has_active_room_call());
3560
3561        let room_session = session_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3562        assert_eq!(
3563            vec![CAROL.to_owned(), CAROL.to_owned(), BOB.to_owned()],
3564            room_session.active_room_call_participants()
3565        );
3566        assert!(room_session.has_active_room_call());
3567    }
3568
3569    #[test]
3570    fn test_active_call_is_false_when_everyone_left() {
3571        let room = legacy_create_call_with_member_events_for_user(&ALICE, &BOB, &CAROL);
3572
3573        let b_empty_membership = legacy_member_state_event(Vec::new(), event_id!("$1234_1"), &BOB);
3574        let c_empty_membership =
3575            legacy_member_state_event(Vec::new(), event_id!("$12345_1"), &CAROL);
3576
3577        receive_state_events(&room, vec![&b_empty_membership, &c_empty_membership]);
3578
3579        // We have no active call anymore after emptying the memberships
3580        assert_eq!(Vec::<OwnedUserId>::new(), room.active_room_call_participants());
3581        assert!(!room.has_active_room_call());
3582    }
3583
3584    #[test]
3585    fn test_calculate_room_name() {
3586        let mut actual = compute_display_name_from_heroes(2, vec!["a"]);
3587        assert_eq!(RoomDisplayName::Calculated("a".to_owned()), actual);
3588
3589        actual = compute_display_name_from_heroes(3, vec!["a", "b"]);
3590        assert_eq!(RoomDisplayName::Calculated("a, b".to_owned()), actual);
3591
3592        actual = compute_display_name_from_heroes(4, vec!["a", "b", "c"]);
3593        assert_eq!(RoomDisplayName::Calculated("a, b, c".to_owned()), actual);
3594
3595        actual = compute_display_name_from_heroes(5, vec!["a", "b", "c"]);
3596        assert_eq!(RoomDisplayName::Calculated("a, b, c, and 2 others".to_owned()), actual);
3597
3598        actual = compute_display_name_from_heroes(5, vec![]);
3599        assert_eq!(RoomDisplayName::Calculated("5 people".to_owned()), actual);
3600
3601        actual = compute_display_name_from_heroes(0, vec![]);
3602        assert_eq!(RoomDisplayName::Empty, actual);
3603
3604        actual = compute_display_name_from_heroes(1, vec![]);
3605        assert_eq!(RoomDisplayName::Empty, actual);
3606
3607        actual = compute_display_name_from_heroes(1, vec!["a"]);
3608        assert_eq!(RoomDisplayName::EmptyWas("a".to_owned()), actual);
3609
3610        actual = compute_display_name_from_heroes(1, vec!["a", "b"]);
3611        assert_eq!(RoomDisplayName::EmptyWas("a, b".to_owned()), actual);
3612
3613        actual = compute_display_name_from_heroes(1, vec!["a", "b", "c"]);
3614        assert_eq!(RoomDisplayName::EmptyWas("a, b, c".to_owned()), actual);
3615    }
3616
3617    #[test]
3618    fn test_encryption_is_set_when_encryption_event_is_received_encrypted() {
3619        let (_store, room) = make_room_test_helper(RoomState::Joined);
3620
3621        assert_matches!(room.encryption_state(), EncryptionState::Unknown);
3622
3623        let encryption_content =
3624            RoomEncryptionEventContent::new(EventEncryptionAlgorithm::MegolmV1AesSha2);
3625        let encryption_event = AnySyncStateEvent::RoomEncryption(SyncStateEvent::Original(
3626            OriginalSyncRoomEncryptionEvent {
3627                content: encryption_content,
3628                event_id: OwnedEventId::from_str("$1234_1").unwrap(),
3629                sender: ALICE.to_owned(),
3630                // we can simply use now here since this will be dropped when using a
3631                // MinimalStateEvent in the roomInfo
3632                origin_server_ts: timestamp(0),
3633                state_key: EmptyStateKey,
3634                unsigned: StateUnsigned::new(),
3635            },
3636        ));
3637        receive_state_events(&room, vec![&encryption_event]);
3638
3639        assert_matches!(room.encryption_state(), EncryptionState::Encrypted);
3640    }
3641
3642    #[test]
3643    fn test_encryption_is_set_when_encryption_event_is_received_not_encrypted() {
3644        let (_store, room) = make_room_test_helper(RoomState::Joined);
3645
3646        assert_matches!(room.encryption_state(), EncryptionState::Unknown);
3647        room.inner.update_if(|info| {
3648            info.mark_encryption_state_synced();
3649
3650            false
3651        });
3652
3653        assert_matches!(room.encryption_state(), EncryptionState::NotEncrypted);
3654    }
3655
3656    #[test]
3657    fn test_encryption_state() {
3658        assert!(EncryptionState::Unknown.is_unknown());
3659        assert!(EncryptionState::Encrypted.is_unknown().not());
3660        assert!(EncryptionState::NotEncrypted.is_unknown().not());
3661
3662        assert!(EncryptionState::Unknown.is_encrypted().not());
3663        assert!(EncryptionState::Encrypted.is_encrypted());
3664        assert!(EncryptionState::NotEncrypted.is_encrypted().not());
3665    }
3666
3667    #[async_test]
3668    async fn test_room_info_migration_v1() {
3669        let store = MemoryStore::new().into_state_store();
3670
3671        let room_info_json = json!({
3672            "room_id": "!gda78o:server.tld",
3673            "room_state": "Joined",
3674            "notification_counts": {
3675                "highlight_count": 1,
3676                "notification_count": 2,
3677            },
3678            "summary": {
3679                "room_heroes": [{
3680                    "user_id": "@somebody:example.org",
3681                    "display_name": null,
3682                    "avatar_url": null
3683                }],
3684                "joined_member_count": 5,
3685                "invited_member_count": 0,
3686            },
3687            "members_synced": true,
3688            "last_prev_batch": "pb",
3689            "sync_info": "FullySynced",
3690            "encryption_state_synced": true,
3691            "latest_event": {
3692                "event": {
3693                    "encryption_info": null,
3694                    "event": {
3695                        "sender": "@u:i.uk",
3696                    },
3697                },
3698            },
3699            "base_info": {
3700                "avatar": null,
3701                "canonical_alias": null,
3702                "create": null,
3703                "dm_targets": [],
3704                "encryption": null,
3705                "guest_access": null,
3706                "history_visibility": null,
3707                "join_rules": null,
3708                "max_power_level": 100,
3709                "name": null,
3710                "tombstone": null,
3711                "topic": null,
3712            },
3713            "read_receipts": {
3714                "num_unread": 0,
3715                "num_mentions": 0,
3716                "num_notifications": 0,
3717                "latest_active": null,
3718                "pending": []
3719            },
3720            "recency_stamp": 42,
3721        });
3722        let mut room_info: RoomInfo = serde_json::from_value(room_info_json).unwrap();
3723
3724        assert_eq!(room_info.version, 0);
3725        assert!(room_info.base_info.notable_tags.is_empty());
3726        assert!(room_info.base_info.pinned_events.is_none());
3727
3728        // Apply migrations with an empty store.
3729        assert!(room_info.apply_migrations(store.clone()).await);
3730
3731        assert_eq!(room_info.version, 1);
3732        assert!(room_info.base_info.notable_tags.is_empty());
3733        assert!(room_info.base_info.pinned_events.is_none());
3734
3735        // Applying migrations again has no effect.
3736        assert!(!room_info.apply_migrations(store.clone()).await);
3737
3738        assert_eq!(room_info.version, 1);
3739        assert!(room_info.base_info.notable_tags.is_empty());
3740        assert!(room_info.base_info.pinned_events.is_none());
3741
3742        // Add events to the store.
3743        let mut changes = StateChanges::default();
3744
3745        let raw_tag_event = Raw::new(&*TAG).unwrap().cast();
3746        let tag_event = raw_tag_event.deserialize().unwrap();
3747        changes.add_room_account_data(&room_info.room_id, tag_event, raw_tag_event);
3748
3749        let raw_pinned_events_event = Raw::new(&*PINNED_EVENTS).unwrap().cast();
3750        let pinned_events_event = raw_pinned_events_event.deserialize().unwrap();
3751        changes.add_state_event(&room_info.room_id, pinned_events_event, raw_pinned_events_event);
3752
3753        store.save_changes(&changes).await.unwrap();
3754
3755        // Reset to version 0 and reapply migrations.
3756        room_info.version = 0;
3757        assert!(room_info.apply_migrations(store.clone()).await);
3758
3759        assert_eq!(room_info.version, 1);
3760        assert!(room_info.base_info.notable_tags.contains(RoomNotableTags::FAVOURITE));
3761        assert!(room_info.base_info.pinned_events.is_some());
3762
3763        // Creating a new room info initializes it to version 1.
3764        let new_room_info = RoomInfo::new(room_id!("!new_room:localhost"), RoomState::Joined);
3765        assert_eq!(new_room_info.version, 1);
3766    }
3767
3768    #[async_test]
3769    async fn test_prev_room_state_is_updated() {
3770        let (_store, room) = make_room_test_helper(RoomState::Invited);
3771        assert_eq!(room.prev_state(), None);
3772        assert_eq!(room.state(), RoomState::Invited);
3773
3774        // Invited -> Joined
3775        let mut room_info = room.clone_info();
3776        room_info.mark_as_joined();
3777        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3778        assert_eq!(room.prev_state(), Some(RoomState::Invited));
3779        assert_eq!(room.state(), RoomState::Joined);
3780
3781        // No change when the same state is used
3782        let mut room_info = room.clone_info();
3783        room_info.mark_as_joined();
3784        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3785        assert_eq!(room.prev_state(), Some(RoomState::Invited));
3786        assert_eq!(room.state(), RoomState::Joined);
3787
3788        // Joined -> Left
3789        let mut room_info = room.clone_info();
3790        room_info.mark_as_left();
3791        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3792        assert_eq!(room.prev_state(), Some(RoomState::Joined));
3793        assert_eq!(room.state(), RoomState::Left);
3794
3795        // Left -> Banned
3796        let mut room_info = room.clone_info();
3797        room_info.mark_as_banned();
3798        room.set_room_info(room_info, RoomInfoNotableUpdateReasons::MEMBERSHIP);
3799        assert_eq!(room.prev_state(), Some(RoomState::Left));
3800        assert_eq!(room.state(), RoomState::Banned);
3801    }
3802
3803    #[async_test]
3804    async fn test_room_state_filters() {
3805        let client = logged_in_base_client(None).await;
3806
3807        let joined_room_id = owned_room_id!("!joined:example.org");
3808        client.get_or_create_room(&joined_room_id, RoomState::Joined);
3809
3810        let invited_room_id = owned_room_id!("!invited:example.org");
3811        client.get_or_create_room(&invited_room_id, RoomState::Invited);
3812
3813        let left_room_id = owned_room_id!("!left:example.org");
3814        client.get_or_create_room(&left_room_id, RoomState::Left);
3815
3816        let knocked_room_id = owned_room_id!("!knocked:example.org");
3817        client.get_or_create_room(&knocked_room_id, RoomState::Knocked);
3818
3819        let banned_room_id = owned_room_id!("!banned:example.org");
3820        client.get_or_create_room(&banned_room_id, RoomState::Banned);
3821
3822        let joined_rooms = client.rooms_filtered(RoomStateFilter::JOINED);
3823        assert_eq!(joined_rooms.len(), 1);
3824        assert_eq!(joined_rooms[0].state(), RoomState::Joined);
3825        assert_eq!(joined_rooms[0].room_id, joined_room_id);
3826
3827        let invited_rooms = client.rooms_filtered(RoomStateFilter::INVITED);
3828        assert_eq!(invited_rooms.len(), 1);
3829        assert_eq!(invited_rooms[0].state(), RoomState::Invited);
3830        assert_eq!(invited_rooms[0].room_id, invited_room_id);
3831
3832        let left_rooms = client.rooms_filtered(RoomStateFilter::LEFT);
3833        assert_eq!(left_rooms.len(), 1);
3834        assert_eq!(left_rooms[0].state(), RoomState::Left);
3835        assert_eq!(left_rooms[0].room_id, left_room_id);
3836
3837        let knocked_rooms = client.rooms_filtered(RoomStateFilter::KNOCKED);
3838        assert_eq!(knocked_rooms.len(), 1);
3839        assert_eq!(knocked_rooms[0].state(), RoomState::Knocked);
3840        assert_eq!(knocked_rooms[0].room_id, knocked_room_id);
3841
3842        let banned_rooms = client.rooms_filtered(RoomStateFilter::BANNED);
3843        assert_eq!(banned_rooms.len(), 1);
3844        assert_eq!(banned_rooms[0].state(), RoomState::Banned);
3845        assert_eq!(banned_rooms[0].room_id, banned_room_id);
3846    }
3847
3848    #[test]
3849    fn test_room_state_filters_as_vec() {
3850        assert_eq!(RoomStateFilter::JOINED.as_vec(), vec![RoomState::Joined]);
3851        assert_eq!(RoomStateFilter::LEFT.as_vec(), vec![RoomState::Left]);
3852        assert_eq!(RoomStateFilter::INVITED.as_vec(), vec![RoomState::Invited]);
3853        assert_eq!(RoomStateFilter::KNOCKED.as_vec(), vec![RoomState::Knocked]);
3854        assert_eq!(RoomStateFilter::BANNED.as_vec(), vec![RoomState::Banned]);
3855
3856        // Check all filters are taken into account
3857        assert_eq!(
3858            RoomStateFilter::all().as_vec(),
3859            vec![
3860                RoomState::Joined,
3861                RoomState::Left,
3862                RoomState::Invited,
3863                RoomState::Knocked,
3864                RoomState::Banned
3865            ]
3866        );
3867    }
3868}