1#[cfg(feature = "e2e-encryption")]
17use std::sync::Arc;
18#[cfg(all(feature = "e2e-encryption", feature = "unstable-msc4354"))]
19use std::sync::Mutex as StdMutex;
20use std::{
21 collections::{BTreeMap, BTreeSet, HashMap},
22 fmt,
23 ops::Deref,
24};
25
26use eyeball::{SharedObservable, Subscriber};
27use eyeball_im::{Vector, VectorDiff};
28use futures_util::Stream;
29#[cfg(all(feature = "e2e-encryption", feature = "unstable-msc4354"))]
30use matrix_sdk_common::executor::AbortOnDrop;
31use matrix_sdk_common::{cross_process_lock::CrossProcessLockConfig, timer};
32#[cfg(feature = "experimental-x509-identity-verification")]
33use matrix_sdk_crypto::x509::{RawX509Signer, RawX509Verifier};
34#[cfg(feature = "e2e-encryption")]
35use matrix_sdk_crypto::{
36 CollectStrategy, DecryptionSettings, EncryptionSettings, OlmError, OlmMachine,
37 OlmMachineBuilder, TrustRequirement, store::DynCryptoStore,
38 store::types::RoomPendingKeyBundleDetails, types::requests::ToDeviceRequest,
39};
40#[cfg(doc)]
41use ruma::DeviceId;
42#[cfg(feature = "unstable-msc4536")]
43use ruma::MilliSecondsSinceUnixEpoch;
44#[cfg(feature = "e2e-encryption")]
45use ruma::events::room::{history_visibility::HistoryVisibility, member::MembershipState};
46use ruma::{
47 OwnedRoomId, OwnedUserId, RoomId, UserId,
48 api::client::{self as api, sync::sync_events::msc4186},
49 events::{
50 StateEvent, StateEventType,
51 ignored_user_list::IgnoredUserListEventContent,
52 push_rules::{PushRulesEvent, PushRulesEventContent},
53 room::member::SyncRoomMemberEvent,
54 },
55 profile::UserProfileUpdate,
56 push::Ruleset,
57};
58use tokio::sync::{Mutex, MutexGuard, broadcast};
59#[cfg(feature = "e2e-encryption")]
60use tokio::sync::{RwLock, RwLockReadGuard};
61use tracing::{Level, debug, info, instrument, warn};
62
63#[cfg(feature = "e2e-encryption")]
64use crate::RoomMemberships;
65use crate::{
66 RoomStateFilter, SessionMeta, StateStore,
67 deserialized_responses::DisplayName,
68 error::{Error, Result},
69 event_cache::store::EventCacheStoreLock,
70 media::store::MediaStoreLock,
71 response_processors::{self as processors, Context},
72 room::{
73 Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState,
74 },
75 store::{
76 AvatarCache, BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult,
77 RoomLoadSettings, StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt,
78 StoreConfig,
79 ambiguity_map::{AmbiguityCache, is_member_active},
80 },
81 sync::{RoomUpdates, SyncResponse},
82};
83
84#[derive(Clone)]
105pub struct BaseClient {
106 pub(crate) state_store: BaseStateStore,
108
109 event_cache_store: EventCacheStoreLock,
111
112 media_store: MediaStoreLock,
114
115 #[cfg(feature = "e2e-encryption")]
120 crypto_store: Arc<DynCryptoStore>,
121
122 #[cfg(feature = "e2e-encryption")]
126 olm_machine: Arc<RwLock<Option<OlmMachine>>>,
127
128 pub(crate) ignore_user_list_changes: SharedObservable<Vec<OwnedUserId>>,
130
131 pub(crate) global_profile_updates_sender: broadcast::Sender<BTreeSet<OwnedUserId>>,
134
135 #[cfg(feature = "e2e-encryption")]
138 pub room_key_recipient_strategy: CollectStrategy,
139
140 #[cfg(feature = "e2e-encryption")]
142 pub decryption_settings: DecryptionSettings,
143
144 #[cfg(feature = "e2e-encryption")]
146 pub handle_verification_events: bool,
147
148 #[cfg(all(feature = "e2e-encryption", feature = "unstable-msc4354"))]
152 sticky_redecryptor: Arc<StdMutex<Option<AbortOnDrop<()>>>>,
153
154 pub threading_support: ThreadingSupport,
156
157 #[cfg(feature = "experimental-x509-identity-verification")]
160 x509_signer: Option<Arc<dyn RawX509Signer>>,
161
162 #[cfg(feature = "experimental-x509-identity-verification")]
165 x509_verifier: Option<Arc<dyn RawX509Verifier>>,
166
167 pub dm_room_definition: DmRoomDefinition,
169}
170
171#[cfg(not(tarpaulin_include))]
172impl fmt::Debug for BaseClient {
173 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
174 f.debug_struct("BaseClient")
175 .field("session_meta", &self.state_store.session_meta())
176 .field("sync_token", &self.state_store.sync_token)
177 .finish_non_exhaustive()
178 }
179}
180
181#[derive(Clone, Copy, Debug)]
193pub enum ThreadingSupport {
194 Enabled {
196 with_subscriptions: bool,
202 },
203 Disabled,
205}
206
207impl BaseClient {
208 pub fn new(
215 config: StoreConfig,
216 threading_support: ThreadingSupport,
217 dm_room_definition: DmRoomDefinition,
218 ) -> Self {
219 let store = BaseStateStore::new(config.state_store);
220
221 BaseClient {
222 state_store: store,
223 event_cache_store: config.event_cache_store,
224 media_store: config.media_store,
225 #[cfg(feature = "e2e-encryption")]
226 crypto_store: config.crypto_store,
227 #[cfg(feature = "e2e-encryption")]
228 olm_machine: Default::default(),
229 ignore_user_list_changes: Default::default(),
230 global_profile_updates_sender: broadcast::Sender::new(16),
231 #[cfg(feature = "e2e-encryption")]
232 room_key_recipient_strategy: Default::default(),
233 #[cfg(feature = "e2e-encryption")]
234 decryption_settings: DecryptionSettings {
235 sender_device_trust_requirement: TrustRequirement::Untrusted,
236 },
237 #[cfg(feature = "e2e-encryption")]
238 handle_verification_events: true,
239 #[cfg(all(feature = "e2e-encryption", feature = "unstable-msc4354"))]
240 sticky_redecryptor: Default::default(),
241 threading_support,
242 #[cfg(feature = "experimental-x509-identity-verification")]
243 x509_signer: None,
244 #[cfg(feature = "experimental-x509-identity-verification")]
245 x509_verifier: None,
246 dm_room_definition,
247 }
248 }
249
250 #[cfg(feature = "e2e-encryption")]
253 pub async fn clone_with_in_memory_state_store(
254 &self,
255 cross_process_mode: CrossProcessLockConfig,
256 handle_verification_events: bool,
257 ) -> Result<Self> {
258 let config = StoreConfig::new(cross_process_mode).state_store(MemoryStore::new());
259 let config = config.crypto_store(self.crypto_store.clone());
260
261 let copy = Self {
262 state_store: BaseStateStore::new(config.state_store),
263 event_cache_store: config.event_cache_store,
264 media_store: config.media_store,
265 crypto_store: self.crypto_store.clone(),
274 olm_machine: self.olm_machine.clone(),
275 ignore_user_list_changes: Default::default(),
276 global_profile_updates_sender: broadcast::Sender::new(16),
277 room_key_recipient_strategy: self.room_key_recipient_strategy.clone(),
278 decryption_settings: self.decryption_settings.clone(),
279 handle_verification_events,
280 #[cfg(feature = "unstable-msc4354")]
281 sticky_redecryptor: Default::default(),
282 threading_support: self.threading_support,
283 #[cfg(feature = "experimental-x509-identity-verification")]
284 x509_signer: self.x509_signer.clone(),
285 #[cfg(feature = "experimental-x509-identity-verification")]
286 x509_verifier: self.x509_verifier.clone(),
287 dm_room_definition: self.dm_room_definition.clone(),
288 };
289
290 copy.state_store.derive_from_other(&self.state_store).await?;
291
292 Ok(copy)
293 }
294
295 #[cfg(feature = "experimental-x509-identity-verification")]
298 pub fn set_x509_signer(&mut self, x509_signer: Option<Arc<dyn RawX509Signer>>) {
299 self.x509_signer = x509_signer;
300 }
301
302 #[cfg(feature = "experimental-x509-identity-verification")]
305 pub fn set_x509_verifier(&mut self, x509_verifier: Option<Arc<dyn RawX509Verifier>>) {
306 self.x509_verifier = x509_verifier
307 }
308
309 #[cfg(not(feature = "e2e-encryption"))]
312 #[allow(clippy::unused_async)]
313 pub async fn clone_with_in_memory_state_store(
314 &self,
315 cross_process_store_config: CrossProcessLockConfig,
316 _handle_verification_events: bool,
317 ) -> Result<Self> {
318 let config = StoreConfig::new(cross_process_store_config).state_store(MemoryStore::new());
319 Ok(Self::new(config, ThreadingSupport::Disabled, DmRoomDefinition::default()))
320 }
321
322 pub fn session_meta(&self) -> Option<&SessionMeta> {
328 self.state_store.session_meta()
329 }
330
331 pub fn rooms(&self) -> Vec<Room> {
333 self.state_store.rooms()
334 }
335
336 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
338 self.state_store.rooms_filtered(filter)
339 }
340
341 pub fn rooms_stream(
344 &self,
345 ) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + use<>) {
346 self.state_store.rooms_stream()
347 }
348
349 pub fn get_or_create_room(&self, room_id: &RoomId, room_state: RoomState) -> Room {
352 self.state_store.get_or_create_room(room_id, room_state)
353 }
354
355 pub fn state_store(&self) -> &DynStateStore {
357 self.state_store.deref()
358 }
359
360 pub fn event_cache_store(&self) -> &EventCacheStoreLock {
362 &self.event_cache_store
363 }
364
365 pub fn media_store(&self) -> &MediaStoreLock {
367 &self.media_store
368 }
369
370 pub fn is_active(&self) -> bool {
374 self.state_store.session_meta().is_some()
375 }
376
377 pub async fn activate(
407 &self,
408 session_meta: SessionMeta,
409 room_load_settings: RoomLoadSettings,
410 #[cfg(feature = "e2e-encryption")] custom_account: Option<
411 crate::crypto::vodozemac::olm::Account,
412 >,
413 ) -> Result<()> {
414 debug!(user_id = ?session_meta.user_id, device_id = ?session_meta.device_id, "Activating the client");
415
416 self.state_store.load_rooms(&session_meta.user_id, room_load_settings).await?;
417 self.state_store.load_sync_token().await?;
418 self.state_store.set_session_meta(session_meta);
419
420 let ignore_user_list = self.get_ignore_user_list().await?;
424 self.ignore_user_list_changes.set(ignore_user_list);
425
426 #[cfg(feature = "e2e-encryption")]
427 self.regenerate_olm(custom_account).await?;
428
429 Ok(())
430 }
431
432 #[cfg(feature = "e2e-encryption")]
436 pub async fn regenerate_olm(
437 &self,
438 custom_account: Option<crate::crypto::vodozemac::olm::Account>,
439 ) -> Result<()> {
440 tracing::debug!("regenerating OlmMachine");
441 let session_meta = self.session_meta().ok_or(Error::OlmError(OlmError::MissingSession))?;
442
443 let builder = OlmMachineBuilder::new(&session_meta.user_id, &session_meta.device_id)
446 .with_crypto_store(self.crypto_store.clone())
447 .with_custom_account(custom_account);
448
449 #[cfg(feature = "experimental-x509-identity-verification")]
450 let builder = builder
451 .with_x509_verifier(self.x509_verifier.clone())
452 .with_x509_signer(self.x509_signer.clone());
453
454 let olm_machine = builder.build().await.map_err(OlmError::from)?;
455
456 #[cfg(feature = "unstable-msc4354")]
459 let room_keys_stream = olm_machine.store().room_keys_received_stream();
460
461 *self.olm_machine.write().await = Some(olm_machine);
462
463 #[cfg(feature = "unstable-msc4354")]
466 {
467 let redecryptor = crate::sticky::spawn_redecryptor(
468 room_keys_stream,
469 self.olm_machine.clone(),
470 self.decryption_settings.clone(),
471 self.state_store.clone(),
472 );
473 *self.sticky_redecryptor.lock().unwrap() = Some(redecryptor);
474 }
475
476 Ok(())
477 }
478
479 pub async fn sync_token(&self) -> Option<String> {
482 self.state_store.sync_token.read().await.clone()
483 }
484
485 pub async fn room_knocked(&self, room_id: &RoomId) -> Result<Room> {
489 let room = self.state_store.get_or_create_room(room_id, RoomState::Knocked);
490
491 if room.state() != RoomState::Knocked {
492 let store_guard = self.state_store.lock().lock().await;
493
494 #[cfg(feature = "e2e-encryption")]
497 if let Some(olm_machine) = self.olm_machine().await.as_ref() {
498 olm_machine.store().clear_room_pending_key_bundle(room_id).await?
499 }
500
501 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
502 info.mark_as_knocked();
503 info.mark_state_partially_synced();
504 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
506 })
507 .await?;
508 }
509
510 Ok(room)
511 }
512
513 pub async fn room_joined(
557 &self,
558 room_id: &RoomId,
559 inviter: Option<OwnedUserId>,
560 ) -> Result<Room> {
561 let room = self.state_store.get_or_create_room(room_id, RoomState::Joined);
562
563 if room.state() != RoomState::Joined {
566 let store_guard = self.state_store_lock().lock().await;
567
568 #[cfg(feature = "e2e-encryption")]
569 {
570 let previous_state = room.state();
583 if previous_state == RoomState::Invited
584 && let Some(inviter) = inviter
585 && let Some(olm_machine) = self.olm_machine().await.as_ref()
586 {
587 olm_machine.store().store_room_pending_key_bundle(room_id, &inviter).await?
588 }
589 }
590 #[cfg(not(feature = "e2e-encryption"))]
591 {
592 let _ = inviter;
594 }
595
596 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
597 info.mark_as_joined();
598 info.mark_state_partially_synced();
599 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
601 })
602 .await?;
603 }
604
605 Ok(room)
606 }
607
608 pub async fn room_left(&self, room_id: &RoomId) -> Result<()> {
612 let room = self.state_store.get_or_create_room(room_id, RoomState::Left);
613
614 if room.state() != RoomState::Left {
615 let store_guard = self.state_store.lock().lock().await;
616
617 #[cfg(feature = "e2e-encryption")]
620 if let Some(olm_machine) = self.olm_machine().await.as_ref() {
621 olm_machine.store().clear_room_pending_key_bundle(room_id).await?
622 }
623
624 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
625 info.mark_as_left();
626 info.mark_state_partially_synced();
627 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
629 })
630 .await?;
631 }
632
633 Ok(())
634 }
635
636 pub fn state_store_lock(&self) -> &Mutex<()> {
641 self.state_store.lock()
642 }
643
644 #[instrument(skip_all)]
650 pub async fn receive_sync_response(
651 &self,
652 response: api::sync::sync_events::v3::Response,
653 ) -> Result<SyncResponse> {
654 self.receive_sync_response_with_requested_required_states(
655 response,
656 &RequestedRequiredStates::default(),
657 )
658 .await
659 }
660
661 pub async fn receive_sync_response_with_requested_required_states(
669 &self,
670 response: api::sync::sync_events::v3::Response,
671 requested_required_states: &RequestedRequiredStates,
672 ) -> Result<SyncResponse> {
673 if self.state_store.sync_token.read().await.as_ref() == Some(&response.next_batch) {
677 info!("Got the same sync response twice");
678 return Ok(SyncResponse::default());
679 }
680
681 let _time = timer!(Level::INFO, "Processing a sync response");
682
683 let state_store_guard = self.state_store_lock().lock().await;
686
687 let user_id = self
688 .session_meta()
689 .expect("Sync shouldn't run without an authenticated user")
690 .user_id
691 .to_owned();
692
693 #[cfg(feature = "e2e-encryption")]
694 let olm_machine = self.olm_machine().await;
695
696 let mut context = Context::new(StateChanges::new(response.next_batch.clone()));
697
698 #[cfg(feature = "e2e-encryption")]
699 let processors::e2ee::to_device::Output { processed_to_device_events: to_device } =
700 processors::e2ee::to_device::from_sync_v2(
701 &response,
702 olm_machine.as_ref(),
703 &self.decryption_settings,
704 )
705 .await?;
706
707 #[cfg(not(feature = "e2e-encryption"))]
708 let to_device = response
709 .to_device
710 .events
711 .into_iter()
712 .map(|raw| {
713 use matrix_sdk_common::deserialized_responses::{
714 ProcessedToDeviceEvent, ToDeviceUnableToDecryptInfo,
715 ToDeviceUnableToDecryptReason,
716 };
717 use ruma::events::ToDeviceEventType;
718
719 if let Ok(Some(event_type)) = raw.get_field::<ToDeviceEventType>("type") {
720 if event_type == ToDeviceEventType::RoomEncrypted {
721 ProcessedToDeviceEvent::UnableToDecrypt {
722 encrypted_event: raw,
723 utd_info: ToDeviceUnableToDecryptInfo {
724 reason: ToDeviceUnableToDecryptReason::EncryptionIsDisabled,
725 },
726 }
727 } else {
728 ProcessedToDeviceEvent::PlainText(raw)
729 }
730 } else {
731 ProcessedToDeviceEvent::Invalid(raw)
733 }
734 })
735 .collect();
736
737 let mut ambiguity_cache = AmbiguityCache::new(self.state_store.inner.clone());
738 let mut avatar_cache = AvatarCache::new(self.state_store.inner.clone());
739
740 let global_account_data_processor =
741 processors::account_data::global(&response.account_data.events);
742
743 let push_rules = self.get_push_rules(&global_account_data_processor).await?;
744
745 let mut room_updates = RoomUpdates::default();
746 let mut notifications = Default::default();
747
748 let mut updated_members_in_room: BTreeMap<OwnedRoomId, BTreeSet<OwnedUserId>> =
749 BTreeMap::new();
750
751 #[cfg(feature = "e2e-encryption")]
752 let e2ee_context = processors::e2ee::E2EE::new(
753 olm_machine.as_ref(),
754 &self.decryption_settings,
755 self.handle_verification_events,
756 );
757
758 for (room_id, joined_room) in response.rooms.join {
759 let joined_room_update = processors::room::sync_v2::update_joined_room(
760 &mut context,
761 processors::room::RoomCreationData::new(
762 &room_id,
763 requested_required_states,
764 &mut ambiguity_cache,
765 &mut avatar_cache,
766 ),
767 joined_room,
768 &mut updated_members_in_room,
769 processors::notification::Notification::new(
770 &push_rules,
771 &mut notifications,
772 &self.state_store,
773 ),
774 #[cfg(feature = "e2e-encryption")]
775 &e2ee_context,
776 )
777 .await?;
778
779 room_updates.joined.insert(room_id, joined_room_update);
780 }
781
782 for (room_id, left_room) in response.rooms.leave {
783 let left_room_update = processors::room::sync_v2::update_left_room(
784 &mut context,
785 processors::room::RoomCreationData::new(
786 &room_id,
787 requested_required_states,
788 &mut ambiguity_cache,
789 &mut avatar_cache,
790 ),
791 left_room,
792 processors::notification::Notification::new(
793 &push_rules,
794 &mut notifications,
795 &self.state_store,
796 ),
797 #[cfg(feature = "e2e-encryption")]
798 &e2ee_context,
799 )
800 .await?;
801
802 room_updates.left.insert(room_id, left_room_update);
803 }
804
805 for (room_id, invited_room) in response.rooms.invite {
806 let invited_room_update = processors::room::sync_v2::update_invited_room(
807 &mut context,
808 &room_id,
809 &user_id,
810 invited_room,
811 processors::notification::Notification::new(
812 &push_rules,
813 &mut notifications,
814 &self.state_store,
815 ),
816 #[cfg(feature = "e2e-encryption")]
817 &e2ee_context,
818 )
819 .await?;
820
821 room_updates.invited.insert(room_id, invited_room_update);
822 }
823
824 for (room_id, knocked_room) in response.rooms.knock {
825 let knocked_room_update = processors::room::sync_v2::update_knocked_room(
826 &mut context,
827 &room_id,
828 &user_id,
829 knocked_room,
830 processors::notification::Notification::new(
831 &push_rules,
832 &mut notifications,
833 &self.state_store,
834 ),
835 #[cfg(feature = "e2e-encryption")]
836 &e2ee_context,
837 )
838 .await?;
839
840 room_updates.knocked.insert(room_id, knocked_room_update);
841 }
842
843 global_account_data_processor.apply(&mut context, &self.state_store).await;
844
845 context.state_changes.presence = response
846 .presence
847 .events
848 .iter()
849 .filter_map(|e| {
850 let event = e.deserialize().ok()?;
851 Some((event.sender, e.clone()))
852 })
853 .collect();
854
855 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
856
857 processors::changes::save_and_apply(
858 context,
859 &self.state_store,
860 &state_store_guard,
861 &self.ignore_user_list_changes,
862 Some(response.next_batch.clone()),
863 )
864 .await?;
865
866 let mut context = Context::default();
867
868 processors::room::display_name::update_for_rooms(
872 &mut context,
873 &room_updates,
874 &self.state_store,
875 )
876 .await;
877
878 processors::changes::save_only(context, &self.state_store, &state_store_guard).await?;
880
881 for (room_id, member_ids) in updated_members_in_room {
882 if let Some(room) = self.get_room(&room_id) {
883 let _ =
884 room.room_member_updates_sender.send(RoomMembersUpdate::Partial(member_ids));
885 }
886 }
887
888 drop(state_store_guard);
890
891 let response = SyncResponse {
892 rooms: room_updates,
893 presence: response.presence.events,
894 account_data: response.account_data.events,
895 to_device,
896 notifications,
897 };
898
899 Ok(response)
900 }
901
902 #[instrument(skip_all, fields(?room_id))]
913 pub async fn receive_all_members(
914 &self,
915 room_id: &RoomId,
916 request: &api::membership::get_member_events::v3::Request,
917 response: &api::membership::get_member_events::v3::Response,
918 ) -> Result<()> {
919 if request.membership.is_some() || request.not_membership.is_some() || request.at.is_some()
920 {
921 return Err(Error::InvalidReceiveMembersParameters);
926 }
927
928 let Some(room) = self.state_store.room(room_id) else {
929 return Ok(());
931 };
932
933 let mut chunk = Vec::with_capacity(response.chunk.len());
934 let mut context = Context::default();
935
936 #[cfg(feature = "e2e-encryption")]
937 let mut user_ids = BTreeSet::new();
938
939 let mut ambiguity_map: HashMap<DisplayName, BTreeSet<OwnedUserId>> = Default::default();
940
941 for raw_event in &response.chunk {
942 let member = match raw_event.deserialize() {
943 Ok(ev) => ev,
944 Err(e) => {
945 let event_id: Option<String> = raw_event.get_field("event_id").ok().flatten();
946 debug!(event_id, "Failed to deserialize member event: {e}");
947 continue;
948 }
949 };
950
951 #[cfg(feature = "e2e-encryption")]
962 match member.membership() {
963 MembershipState::Join | MembershipState::Invite => {
964 user_ids.insert(member.state_key().to_owned());
965 }
966 _ => (),
967 }
968
969 if let StateEvent::Original(e) = &member
970 && is_member_active(&e.content.membership)
971 && let Some(d) = &e.content.displayname
972 {
973 let display_name = DisplayName::new(d);
974 ambiguity_map.entry(display_name).or_default().insert(member.state_key().clone());
975 }
976
977 let sync_member: SyncRoomMemberEvent = member.clone().into();
978 processors::profiles::upsert_or_delete(&mut context, room_id, &sync_member);
979
980 context
981 .state_changes
982 .state
983 .entry(room_id.to_owned())
984 .or_default()
985 .entry(member.event_type())
986 .or_default()
987 .insert(member.state_key().to_string(), raw_event.clone().cast());
988 chunk.push(member);
989 }
990
991 #[cfg(feature = "e2e-encryption")]
992 processors::e2ee::tracked_users::update(
993 self.olm_machine().await.as_ref(),
994 room.encryption_state(),
995 &user_ids,
996 )
997 .await?;
998
999 context.state_changes.ambiguity_maps.insert(room_id.to_owned(), ambiguity_map);
1000
1001 {
1002 let state_store_guard = self.state_store_lock().lock().await;
1003
1004 let mut room_info = room.clone_info();
1005 room_info.mark_members_synced();
1006 context.state_changes.add_room(room_info);
1007
1008 processors::changes::save_and_apply(
1009 context,
1010 &self.state_store,
1011 &state_store_guard,
1012 &self.ignore_user_list_changes,
1013 None,
1014 )
1015 .await?;
1016 }
1017
1018 let _ = room.room_member_updates_sender.send(RoomMembersUpdate::FullReload);
1019
1020 #[cfg(feature = "e2e-encryption")]
1021 if let Some(olm) = self.olm_machine().await.as_ref() {
1022 tracing::debug!("Rotating room key due to full member list reload");
1028 if let Err(e) = olm.discard_room_key(room_id).await {
1029 tracing::warn!("Error discarding room key: {e:?}");
1030 }
1031 }
1032
1033 Ok(())
1034 }
1035
1036 pub async fn receive_filter_upload(
1051 &self,
1052 filter_name: &str,
1053 response: &api::filter::create_filter::v3::Response,
1054 ) -> Result<()> {
1055 Ok(self
1056 .state_store
1057 .set_kv_data(
1058 StateStoreDataKey::Filter(filter_name),
1059 StateStoreDataValue::Filter(response.filter_id.clone()),
1060 )
1061 .await?)
1062 }
1063
1064 pub async fn get_filter(&self, filter_name: &str) -> StoreResult<Option<String>> {
1076 let filter = self
1077 .state_store
1078 .get_kv_data(StateStoreDataKey::Filter(filter_name))
1079 .await?
1080 .map(|d| d.into_filter().expect("State store data not a filter"));
1081
1082 Ok(filter)
1083 }
1084
1085 #[cfg(feature = "e2e-encryption")]
1087 pub async fn share_room_key(&self, room_id: &RoomId) -> Result<Vec<Arc<ToDeviceRequest>>> {
1088 match self.olm_machine().await.as_ref() {
1089 Some(o) => {
1090 let Some(room) = self.get_room(room_id) else {
1091 return Err(Error::InsufficientData);
1092 };
1093
1094 let history_visibility = room.history_visibility_or_default();
1095 let Some(room_encryption_event) = room.encryption_settings() else {
1096 return Err(Error::EncryptionNotEnabled);
1097 };
1098
1099 let filter = if history_visibility == HistoryVisibility::Joined {
1102 RoomMemberships::JOIN
1103 } else {
1104 RoomMemberships::ACTIVE
1105 };
1106
1107 let members = self.state_store.get_user_ids(room_id, filter).await?;
1108
1109 let Some(settings) = EncryptionSettings::from_possibly_redacted(
1110 room_encryption_event,
1111 history_visibility,
1112 self.room_key_recipient_strategy.clone(),
1113 ) else {
1114 return Err(Error::EncryptionNotEnabled);
1115 };
1116
1117 Ok(o.share_room_key(room_id, members.iter().map(Deref::deref), settings).await?)
1118 }
1119 None => panic!("Olm machine wasn't started"),
1120 }
1121 }
1122
1123 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1129 self.state_store.room(room_id)
1130 }
1131
1132 pub async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
1140 self.state_store.forget_room(room_id).await?;
1142
1143 Ok(())
1144 }
1145
1146 #[cfg(feature = "e2e-encryption")]
1148 pub async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1149 self.olm_machine.read().await
1150 }
1151
1152 pub(crate) async fn get_push_rules(
1158 &self,
1159 global_account_data_processor: &processors::account_data::Global,
1160 ) -> Result<Ruleset> {
1161 let _timer = timer!(Level::TRACE, "get_push_rules");
1162 if let Some(event) = global_account_data_processor
1163 .push_rules()
1164 .and_then(|ev| ev.deserialize_as_unchecked::<PushRulesEvent>().ok())
1165 {
1166 Ok(event.content.global)
1167 } else if let Some(event) = self
1168 .state_store
1169 .get_account_data_event_static::<PushRulesEventContent>()
1170 .await?
1171 .and_then(|ev| ev.deserialize().ok())
1172 {
1173 Ok(event.content.global)
1174 } else if let Some(session_meta) = self.state_store.session_meta() {
1175 Ok(Ruleset::server_default(&session_meta.user_id))
1176 } else {
1177 Ok(Ruleset::new())
1178 }
1179 }
1180
1181 async fn get_ignore_user_list(&self) -> Result<Vec<OwnedUserId>> {
1186 let Some(raw) =
1187 self.state_store.get_account_data_event_static::<IgnoredUserListEventContent>().await?
1188 else {
1189 return Ok(Vec::new());
1190 };
1191
1192 let event = raw.deserialize()?;
1193
1194 Ok(event.content.ignored_users.into_keys().collect())
1195 }
1196
1197 pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<OwnedUserId>> {
1200 self.ignore_user_list_changes.subscribe()
1201 }
1202
1203 pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
1207 self.state_store.room_info_notable_update_sender.subscribe()
1208 }
1209
1210 pub fn subscribe_to_global_profile_updates(
1216 &self,
1217 ) -> broadcast::Receiver<BTreeSet<OwnedUserId>> {
1218 self.global_profile_updates_sender.subscribe()
1219 }
1220
1221 pub async fn own_profile_updated(&self, update: UserProfileUpdate) -> Result<()> {
1229 let own_user_id = self.session_meta().ok_or(Error::InsufficientData)?.user_id.clone();
1230 let state_store_guard = self.state_store_lock().lock().await;
1231
1232 let mut changes = StateChanges::default();
1233 changes.global_profiles.insert(own_user_id.clone(), update);
1234 self.state_store.save_changes_with_guard(&state_store_guard, &changes).await?;
1235
1236 self.notify_global_profile_updates(BTreeSet::from([own_user_id]), &state_store_guard)
1237 }
1238
1239 #[cfg(feature = "unstable-msc4536")]
1249 pub async fn receive_fetched_global_profiles(
1250 &self,
1251 profiles: BTreeMap<OwnedUserId, UserProfileUpdate>,
1252 unavailable_profiles: BTreeMap<OwnedUserId, MilliSecondsSinceUnixEpoch>,
1253 ) -> Result<()> {
1254 if profiles.is_empty() && unavailable_profiles.is_empty() {
1255 return Ok(());
1256 }
1257
1258 let state_store_guard = self.state_store_lock().lock().await;
1259 let user_ids = profiles.keys().cloned().collect();
1260
1261 let changes = StateChanges {
1262 global_profiles: profiles,
1263 unavailable_global_profiles: unavailable_profiles,
1264 ..Default::default()
1265 };
1266 self.state_store.save_changes_with_guard(&state_store_guard, &changes).await?;
1267
1268 self.notify_global_profile_updates(user_ids, &state_store_guard)
1269 }
1270
1271 pub(crate) fn notify_global_profile_updates(
1277 &self,
1278 user_ids: BTreeSet<OwnedUserId>,
1279 #[cfg_attr(not(feature = "unstable-msc4426"), allow(unused_variables))]
1280 state_store_guard: &MutexGuard<'_, ()>,
1281 ) -> Result<()> {
1282 if user_ids.is_empty() {
1283 return Ok(());
1284 }
1285
1286 #[cfg(feature = "unstable-msc4426")]
1288 for room in self.state_store.rooms() {
1289 if room.hero_user_ids().iter().any(|hero| user_ids.contains(hero)) {
1290 room.update_room_info_with_store_guard(state_store_guard, |room_info| {
1291 (room_info, RoomInfoNotableUpdateReasons::HEROES)
1292 })
1293 .map_err(crate::StoreError::from)?;
1294 }
1295 }
1296
1297 let _ = self.global_profile_updates_sender.send(user_ids);
1298
1299 Ok(())
1300 }
1301
1302 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
1304 match self.state_store.get_account_data_event_static::<IgnoredUserListEventContent>().await
1305 {
1306 Ok(Some(raw_ignored_user_list)) => match raw_ignored_user_list.deserialize() {
1307 Ok(current_ignored_user_list) => {
1308 current_ignored_user_list.content.ignored_users.contains_key(user_id)
1309 }
1310 Err(error) => {
1311 warn!(?error, "Failed to deserialize the ignored user list event");
1312 false
1313 }
1314 },
1315 Ok(None) => false,
1316 Err(error) => {
1317 warn!(?error, "Could not get the ignored user list from the state store");
1318 false
1319 }
1320 }
1321 }
1322
1323 #[cfg(feature = "e2e-encryption")]
1328 pub async fn get_pending_key_bundle_details_for_room(
1329 &self,
1330 room_id: &RoomId,
1331 ) -> Result<Option<RoomPendingKeyBundleDetails>> {
1332 let result = match self.olm_machine().await.as_ref() {
1333 Some(machine) => {
1334 machine.store().get_pending_key_bundle_details_for_room(room_id).await?
1335 }
1336 None => None,
1337 };
1338 Ok(result)
1339 }
1340
1341 pub async fn close_stores(&self) -> Result<()> {
1345 self.state_store.close().await?;
1346 self.event_cache_store.close().await.map_err(Error::EventCacheStore)?;
1347 self.media_store.close().await.map_err(Error::MediaStore)?;
1348
1349 #[cfg(feature = "e2e-encryption")]
1350 self.crypto_store.close().await.map_err(Error::CryptoStore)?;
1351
1352 Ok(())
1353 }
1354
1355 pub async fn reopen_stores(&self) -> Result<()> {
1357 #[cfg(feature = "e2e-encryption")]
1358 self.crypto_store.reopen().await.map_err(Error::CryptoStore)?;
1359
1360 self.media_store.reopen().await.map_err(Error::MediaStore)?;
1361 self.event_cache_store.reopen().await.map_err(Error::EventCacheStore)?;
1362 self.state_store.reopen().await?;
1363
1364 Ok(())
1365 }
1366}
1367
1368#[derive(Debug, Default)]
1380pub struct RequestedRequiredStates {
1381 default: Vec<(StateEventType, String)>,
1382 for_rooms: HashMap<OwnedRoomId, Vec<(StateEventType, String)>>,
1383}
1384
1385impl RequestedRequiredStates {
1386 pub fn new(
1391 default: Vec<(StateEventType, String)>,
1392 for_rooms: HashMap<OwnedRoomId, Vec<(StateEventType, String)>>,
1393 ) -> Self {
1394 Self { default, for_rooms }
1395 }
1396
1397 pub fn for_room(&self, room_id: &RoomId) -> &[(StateEventType, String)] {
1399 self.for_rooms.get(room_id).unwrap_or(&self.default)
1400 }
1401}
1402
1403impl From<&msc4186::Request> for RequestedRequiredStates {
1404 fn from(request: &msc4186::Request) -> Self {
1405 let mut default = BTreeSet::new();
1413
1414 for list in request.lists.values() {
1415 default.extend(BTreeSet::from_iter(list.room_details.required_state.iter().cloned()));
1416 }
1417
1418 for room_subscription in request.room_subscriptions.values() {
1419 default.extend(BTreeSet::from_iter(room_subscription.required_state.iter().cloned()));
1420 }
1421
1422 Self { default: default.into_iter().collect(), for_rooms: HashMap::new() }
1423 }
1424}
1425
1426#[derive(Debug, Clone, Default)]
1428#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1429pub enum DmRoomDefinition {
1430 #[default]
1433 MatrixSpec,
1434 TwoMembers,
1437}
1438
1439#[cfg(test)]
1440mod tests {
1441 use std::collections::HashMap;
1442
1443 use futures_util::FutureExt as _;
1444 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
1445 use matrix_sdk_test::{
1446 InvitedRoomBuilder, LeftRoomBuilder, SyncResponseBuilder, User, async_test,
1447 event_factory::EventFactory, ruma_response_from_json,
1448 };
1449 #[cfg(feature = "unstable-msc4426")]
1450 use ruma::profile::{
1451 ProfileFieldValue, StatusProfileField, UserProfileChanges, UserProfileUpdate,
1452 };
1453 use ruma::{
1454 RoomId,
1455 api::client::{self as api, sync::sync_events::msc4186},
1456 event_id,
1457 events::{StateEventType, room::member::MembershipState},
1458 room_id,
1459 serde::Raw,
1460 user_id,
1461 };
1462 use serde_json::{json, value::to_raw_value};
1463 use strass::assert_let;
1464
1465 use super::{BaseClient, RequestedRequiredStates};
1466 use crate::{
1467 DmRoomDefinition, RoomDisplayName, RoomState, SessionMeta,
1468 client::ThreadingSupport,
1469 store::{RoomLoadSettings, StateStoreExt, StoreConfig},
1470 test_utils::logged_in_base_client,
1471 };
1472 #[cfg(feature = "unstable-msc4426")]
1473 use crate::{RoomMemberships, store::StateChanges};
1474
1475 #[test]
1476 fn test_requested_required_states() {
1477 let room_id_0 = room_id!("!r0");
1478 let room_id_1 = room_id!("!r1");
1479
1480 let requested_required_states = RequestedRequiredStates::new(
1481 vec![(StateEventType::RoomAvatar, "".to_owned())],
1482 HashMap::from([(
1483 room_id_0.to_owned(),
1484 vec![
1485 (StateEventType::RoomMember, "foo".to_owned()),
1486 (StateEventType::RoomEncryption, "".to_owned()),
1487 ],
1488 )]),
1489 );
1490
1491 assert_eq!(
1493 requested_required_states.for_room(room_id_0),
1494 &[
1495 (StateEventType::RoomMember, "foo".to_owned()),
1496 (StateEventType::RoomEncryption, "".to_owned()),
1497 ]
1498 );
1499
1500 assert_eq!(
1502 requested_required_states.for_room(room_id_1),
1503 &[(StateEventType::RoomAvatar, "".to_owned()),]
1504 );
1505 }
1506
1507 #[test]
1508 fn test_requested_required_states_from_sync_v5_request() {
1509 let room_id_0 = room_id!("!r0");
1510 let room_id_1 = room_id!("!r1");
1511
1512 let mut request = msc4186::Request::new();
1514
1515 {
1516 let requested_required_states = RequestedRequiredStates::from(&request);
1517
1518 assert!(requested_required_states.default.is_empty());
1519 assert!(requested_required_states.for_rooms.is_empty());
1520 }
1521
1522 request.lists.insert("foo".to_owned(), {
1524 let mut list = msc4186::request::List::default();
1525 list.room_details.required_state = vec![
1526 (StateEventType::RoomAvatar, "".to_owned()),
1527 (StateEventType::RoomEncryption, "".to_owned()),
1528 ];
1529
1530 list
1531 });
1532
1533 {
1534 let requested_required_states = RequestedRequiredStates::from(&request);
1535
1536 assert_eq!(
1537 requested_required_states.default,
1538 &[
1539 (StateEventType::RoomAvatar, "".to_owned()),
1540 (StateEventType::RoomEncryption, "".to_owned())
1541 ]
1542 );
1543 assert!(requested_required_states.for_rooms.is_empty());
1544 }
1545
1546 request.lists.insert("bar".to_owned(), {
1548 let mut list = msc4186::request::List::default();
1549 list.room_details.required_state = vec![
1550 (StateEventType::RoomEncryption, "".to_owned()),
1551 (StateEventType::RoomName, "".to_owned()),
1552 ];
1553
1554 list
1555 });
1556
1557 {
1558 let requested_required_states = RequestedRequiredStates::from(&request);
1559
1560 assert_eq!(
1562 requested_required_states.default,
1563 &[
1564 (StateEventType::RoomAvatar, "".to_owned()),
1565 (StateEventType::RoomEncryption, "".to_owned()),
1566 (StateEventType::RoomName, "".to_owned()),
1567 ]
1568 );
1569 assert!(requested_required_states.for_rooms.is_empty());
1570 }
1571
1572 request.room_subscriptions.insert(room_id_0.to_owned(), {
1574 let mut room_subscription = msc4186::request::RoomSubscription::default();
1575
1576 room_subscription.required_state = vec![
1577 (StateEventType::RoomJoinRules, "".to_owned()),
1578 (StateEventType::RoomEncryption, "".to_owned()),
1579 ];
1580
1581 room_subscription
1582 });
1583
1584 {
1585 let requested_required_states = RequestedRequiredStates::from(&request);
1586
1587 assert_eq!(
1590 requested_required_states.default,
1591 &[
1592 (StateEventType::RoomAvatar, "".to_owned()),
1593 (StateEventType::RoomEncryption, "".to_owned()),
1594 (StateEventType::RoomJoinRules, "".to_owned()),
1595 (StateEventType::RoomName, "".to_owned()),
1596 ]
1597 );
1598 assert!(requested_required_states.for_rooms.is_empty());
1599 }
1600
1601 request.room_subscriptions.insert(room_id_1.to_owned(), {
1603 let mut room_subscription = msc4186::request::RoomSubscription::default();
1604
1605 room_subscription.required_state = vec![
1606 (StateEventType::RoomName, "".to_owned()),
1607 (StateEventType::RoomTopic, "".to_owned()),
1608 ];
1609
1610 room_subscription
1611 });
1612
1613 {
1614 let requested_required_states = RequestedRequiredStates::from(&request);
1615
1616 assert_eq!(
1619 requested_required_states.default,
1620 &[
1621 (StateEventType::RoomAvatar, "".to_owned()),
1622 (StateEventType::RoomEncryption, "".to_owned()),
1623 (StateEventType::RoomJoinRules, "".to_owned()),
1624 (StateEventType::RoomName, "".to_owned()),
1625 (StateEventType::RoomTopic, "".to_owned()),
1626 ]
1627 );
1628 }
1629 }
1630
1631 #[async_test]
1632 async fn test_invite_after_leaving() {
1633 let user_id = user_id!("@alice:example.org");
1634 let room_id = room_id!("!test:example.org");
1635
1636 let client = logged_in_base_client(Some(user_id)).await;
1637 let f = EventFactory::new();
1638
1639 let mut sync_builder = SyncResponseBuilder::new();
1640
1641 let response = sync_builder
1642 .add_left_room(
1643 LeftRoomBuilder::new(room_id).add_timeline_event(
1644 EventFactory::new()
1645 .member(user_id)
1646 .membership(MembershipState::Leave)
1647 .display_name("Alice")
1648 .event_id(event_id!("$994173582443PhrSn:example.org")),
1649 ),
1650 )
1651 .build_sync_response();
1652 client.receive_sync_response(response).await.unwrap();
1653 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1654
1655 let response = sync_builder
1656 .add_invited_room(
1657 InvitedRoomBuilder::new(room_id).add_state_event(
1658 f.member(user_id)
1659 .sender(user_id!("@example:example.org"))
1660 .membership(MembershipState::Invite)
1661 .display_name("Alice"),
1662 ),
1663 )
1664 .build_sync_response();
1665 client.receive_sync_response(response).await.unwrap();
1666 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1667 }
1668
1669 #[async_test]
1670 async fn test_invite_displayname() {
1671 let user_id = user_id!("@alice:example.org");
1672 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1673
1674 let client = logged_in_base_client(Some(user_id)).await;
1675
1676 let response = ruma_response_from_json(&json!({
1677 "next_batch": "asdkl;fjasdkl;fj;asdkl;f",
1678 "device_one_time_keys_count": {
1679 "signed_curve25519": 50u64
1680 },
1681 "device_unused_fallback_key_types": [
1682 "signed_curve25519"
1683 ],
1684 "rooms": {
1685 "invite": {
1686 "!ithpyNKDtmhneaTQja:example.org": {
1687 "invite_state": {
1688 "events": [
1689 {
1690 "content": {
1691 "creator": "@test:example.org",
1692 "room_version": "9"
1693 },
1694 "sender": "@test:example.org",
1695 "state_key": "",
1696 "type": "m.room.create"
1697 },
1698 {
1699 "content": {
1700 "join_rule": "invite"
1701 },
1702 "sender": "@test:example.org",
1703 "state_key": "",
1704 "type": "m.room.join_rules"
1705 },
1706 {
1707 "content": {
1708 "algorithm": "m.megolm.v1.aes-sha2"
1709 },
1710 "sender": "@test:example.org",
1711 "state_key": "",
1712 "type": "m.room.encryption"
1713 },
1714 {
1715 "content": {
1716 "avatar_url": "mxc://example.org/dcBBDwuWEUrjfrOchvkirUST",
1717 "displayname": "Kyra",
1718 "membership": "join"
1719 },
1720 "sender": "@test:example.org",
1721 "state_key": "@test:example.org",
1722 "type": "m.room.member"
1723 },
1724 {
1725 "content": {
1726 "avatar_url": "mxc://example.org/ABFEXSDrESxovWwEnCYdNcHT",
1727 "displayname": "alice",
1728 "is_direct": true,
1729 "membership": "invite"
1730 },
1731 "origin_server_ts": 1650878657984u64,
1732 "sender": "@test:example.org",
1733 "state_key": "@alice:example.org",
1734 "type": "m.room.member",
1735 "unsigned": {
1736 "age": 14u64
1737 },
1738 "event_id": "$fLDqltg9Puj-kWItLSFVHPGN4YkgpYQf2qImPzdmgrE"
1739 }
1740 ]
1741 }
1742 }
1743 }
1744 }
1745 }));
1746
1747 client.receive_sync_response(response).await.unwrap();
1748
1749 let room = client.get_room(room_id).expect("Room not found");
1750 assert_eq!(room.state(), RoomState::Invited);
1751 assert_eq!(
1752 room.compute_display_name().await.expect("fetching display name failed").into_inner(),
1753 RoomDisplayName::Calculated("Kyra".to_owned())
1754 );
1755 }
1756
1757 #[async_test]
1758 async fn test_deserialization_failure() {
1759 let user_id = user_id!("@alice:example.org");
1760 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1761
1762 let client = BaseClient::new(
1763 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1764 ThreadingSupport::Disabled,
1765 DmRoomDefinition::default(),
1766 );
1767 client
1768 .activate(
1769 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1770 RoomLoadSettings::default(),
1771 #[cfg(feature = "e2e-encryption")]
1772 None,
1773 )
1774 .await
1775 .unwrap();
1776
1777 let response = ruma_response_from_json(&json!({
1778 "next_batch": "asdkl;fjasdkl;fj;asdkl;f",
1779 "rooms": {
1780 "join": {
1781 "!ithpyNKDtmhneaTQja:example.org": {
1782 "state": {
1783 "events": [
1784 {
1785 "invalid": "invalid",
1786 },
1787 {
1788 "content": {
1789 "name": "The room name"
1790 },
1791 "event_id": "$143273582443PhrSn:example.org",
1792 "origin_server_ts": 1432735824653u64,
1793 "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
1794 "sender": "@example:example.org",
1795 "state_key": "",
1796 "type": "m.room.name",
1797 "unsigned": {
1798 "age": 1234
1799 }
1800 },
1801 ]
1802 }
1803 }
1804 }
1805 }
1806 }));
1807
1808 client.receive_sync_response(response).await.unwrap();
1809 client
1810 .state_store()
1811 .get_state_event_static::<ruma::events::room::name::RoomNameEventContent>(room_id)
1812 .await
1813 .expect("Failed to fetch state event")
1814 .expect("State event not found")
1815 .deserialize()
1816 .expect("Failed to deserialize state event");
1817 }
1818
1819 #[async_test]
1820 async fn test_invited_members_arent_ignored() {
1821 let user_id = user_id!("@alice:example.org");
1822 let inviter_user_id = user_id!("@bob:example.org");
1823 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1824
1825 let client = BaseClient::new(
1826 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1827 ThreadingSupport::Disabled,
1828 DmRoomDefinition::default(),
1829 );
1830 client
1831 .activate(
1832 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1833 RoomLoadSettings::default(),
1834 #[cfg(feature = "e2e-encryption")]
1835 None,
1836 )
1837 .await
1838 .unwrap();
1839
1840 let mut sync_builder = SyncResponseBuilder::new();
1842 let response = sync_builder
1843 .add_joined_room(matrix_sdk_test::JoinedRoomBuilder::new(room_id))
1844 .build_sync_response();
1845 client.receive_sync_response(response).await.unwrap();
1846
1847 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1850
1851 let raw_member_event = json!({
1852 "content": {
1853 "avatar_url": "mxc://localhost/fewjilfewjil42",
1854 "displayname": "Invited Alice",
1855 "membership": "invite"
1856 },
1857 "event_id": "$151800140517rfvjc:localhost",
1858 "origin_server_ts": 151800140,
1859 "room_id": room_id,
1860 "sender": inviter_user_id,
1861 "state_key": user_id,
1862 "type": "m.room.member",
1863 "unsigned": {
1864 "age": 13374242,
1865 }
1866 });
1867 let response = api::membership::get_member_events::v3::Response::new(vec![Raw::from_json(
1868 to_raw_value(&raw_member_event).unwrap(),
1869 )]);
1870
1871 client.receive_all_members(room_id, &request, &response).await.unwrap();
1873
1874 let room = client.get_room(room_id).unwrap();
1875
1876 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1878
1879 assert_eq!(member.user_id(), user_id);
1880 assert_eq!(member.display_name().unwrap(), "Invited Alice");
1881 assert_eq!(member.avatar_url().unwrap().to_string(), "mxc://localhost/fewjilfewjil42");
1882 }
1883
1884 #[async_test]
1885 async fn test_reinvited_members_get_a_display_name() {
1886 let user_id = user_id!("@alice:example.org");
1887 let inviter_user_id = user_id!("@bob:example.org");
1888 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1889
1890 let client = BaseClient::new(
1891 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1892 ThreadingSupport::Disabled,
1893 DmRoomDefinition::default(),
1894 );
1895 client
1896 .activate(
1897 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1898 RoomLoadSettings::default(),
1899 #[cfg(feature = "e2e-encryption")]
1900 None,
1901 )
1902 .await
1903 .unwrap();
1904
1905 let f = EventFactory::new().sender(user_id);
1908 let mut sync_builder = SyncResponseBuilder::new();
1909 let response = sync_builder
1910 .add_joined_room(
1911 matrix_sdk_test::JoinedRoomBuilder::new(room_id)
1912 .add_state_event(f.member(user_id).leave()),
1913 )
1914 .build_sync_response();
1915 client.receive_sync_response(response).await.unwrap();
1916
1917 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1919
1920 let raw_member_event = json!({
1921 "content": {
1922 "avatar_url": "mxc://localhost/fewjilfewjil42",
1923 "displayname": "Invited Alice",
1924 "membership": "invite"
1925 },
1926 "event_id": "$151800140517rfvjc:localhost",
1927 "origin_server_ts": 151800140,
1928 "room_id": room_id,
1929 "sender": inviter_user_id,
1930 "state_key": user_id,
1931 "type": "m.room.member",
1932 "unsigned": {
1933 "age": 13374242,
1934 }
1935 });
1936 let response = api::membership::get_member_events::v3::Response::new(vec![Raw::from_json(
1937 to_raw_value(&raw_member_event).unwrap(),
1938 )]);
1939
1940 client.receive_all_members(room_id, &request, &response).await.unwrap();
1942
1943 let room = client.get_room(room_id).unwrap();
1944
1945 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1947
1948 assert_eq!(member.user_id(), user_id);
1949 assert_eq!(member.display_name().unwrap(), "Invited Alice");
1950 assert_eq!(member.avatar_url().unwrap().to_string(), "mxc://localhost/fewjilfewjil42");
1951 }
1952
1953 async fn base_client_with_joined_room(room_id: &RoomId) -> BaseClient {
1954 let client = logged_in_base_client(Some(user_id!("@alice:example.org"))).await;
1955
1956 let mut sync_builder = SyncResponseBuilder::new();
1957 let response = sync_builder
1958 .add_joined_room(matrix_sdk_test::JoinedRoomBuilder::new(room_id))
1959 .build_sync_response();
1960 client.receive_sync_response(response).await.unwrap();
1961
1962 client
1963 }
1964
1965 #[async_test]
1966 async fn test_inactive_members_do_not_make_a_display_name_ambiguous() {
1967 let joined_user_id = user_id!("@bob:example.org");
1968 let left_user_id = user_id!("@carol:example.org");
1969 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1970
1971 let client = base_client_with_joined_room(room_id).await;
1972
1973 let f = EventFactory::new().room(room_id);
1975 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1976 let response = api::membership::get_member_events::v3::Response::new(vec![
1977 f.member(joined_user_id).display_name("Amandine").into_raw(),
1978 f.member(left_user_id)
1979 .display_name("Amandine")
1980 .membership(MembershipState::Leave)
1981 .into_raw(),
1982 ]);
1983
1984 client.receive_all_members(room_id, &request, &response).await.unwrap();
1985
1986 let room = client.get_room(room_id).unwrap();
1987 let member = room.get_member(joined_user_id).await.expect("ok").expect("exists");
1988
1989 assert_eq!(member.display_name().unwrap(), "Amandine");
1990 assert!(!member.name_ambiguous());
1991 }
1992
1993 #[async_test]
1994 async fn test_active_members_make_a_display_name_ambiguous() {
1995 let joined_user_id = user_id!("@bob:example.org");
1996 let invited_user_id = user_id!("@carol:example.org");
1997 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1998
1999 let client = base_client_with_joined_room(room_id).await;
2000
2001 let f = EventFactory::new().room(room_id);
2003 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
2004 let response = api::membership::get_member_events::v3::Response::new(vec![
2005 f.member(joined_user_id).display_name("Amandine").into_raw(),
2006 f.member(invited_user_id)
2007 .display_name("Amandine")
2008 .membership(MembershipState::Invite)
2009 .into_raw(),
2010 ]);
2011
2012 client.receive_all_members(room_id, &request, &response).await.unwrap();
2013
2014 let room = client.get_room(room_id).unwrap();
2016
2017 let joined = room.get_member(joined_user_id).await.expect("ok").expect("exists");
2018 assert!(joined.name_ambiguous());
2019
2020 let invited = room.get_member(invited_user_id).await.expect("ok").expect("exists");
2021 assert!(invited.name_ambiguous());
2022 }
2023
2024 #[cfg(feature = "unstable-msc4426")]
2025 #[async_test]
2026 async fn test_room_member_carries_global_profile_status() {
2027 let user_id = user_id!("@alice:example.org");
2028 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
2029
2030 let client = BaseClient::new(
2031 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
2032 ThreadingSupport::Disabled,
2033 DmRoomDefinition::default(),
2034 );
2035 client
2036 .activate(
2037 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
2038 RoomLoadSettings::default(),
2039 #[cfg(feature = "e2e-encryption")]
2040 None,
2041 )
2042 .await
2043 .unwrap();
2044
2045 let f = EventFactory::new().sender(user_id);
2047 let mut sync_builder = SyncResponseBuilder::new();
2048 let response = sync_builder
2049 .add_joined_room(
2050 matrix_sdk_test::JoinedRoomBuilder::new(room_id).add_state_event(f.member(user_id)),
2051 )
2052 .build_sync_response();
2053 client.receive_sync_response(response).await.unwrap();
2054
2055 let room = client.get_room(room_id).unwrap();
2056
2057 let member = room.get_member(user_id).await.expect("ok").expect("exists");
2059 assert!(member.status().is_none());
2060
2061 let mut changes = StateChanges::default();
2063 changes.global_profiles.insert(user_id.to_owned(), {
2064 let mut profile_changes = UserProfileChanges::new();
2065 profile_changes.insert_updated_value(ProfileFieldValue::Status(
2066 StatusProfileField::new("Working".to_owned(), "💻".to_owned()),
2067 ));
2068 UserProfileUpdate::Updated(profile_changes)
2069 });
2070 client.state_store().save_changes(&changes).await.unwrap();
2071
2072 let member = room.get_member(user_id).await.expect("ok").expect("exists");
2074 let status = member.status().expect("status is set");
2075 assert_eq!(status.text, "Working");
2076 assert_eq!(status.emoji, "💻");
2077
2078 let members = room.members(RoomMemberships::JOIN).await.unwrap();
2080 let member =
2081 members.iter().find(|m| m.user_id() == user_id).expect("member is in the list");
2082 let status = member.status().expect("status is set");
2083 assert_eq!(status.text, "Working");
2084 assert_eq!(status.emoji, "💻");
2085 }
2086
2087 #[async_test]
2088 async fn test_ignored_user_list_changes() {
2089 let user_id = user_id!("@alice:example.org");
2090 let client = BaseClient::new(
2091 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
2092 ThreadingSupport::Disabled,
2093 DmRoomDefinition::default(),
2094 );
2095
2096 client
2097 .activate(
2098 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
2099 RoomLoadSettings::default(),
2100 #[cfg(feature = "e2e-encryption")]
2101 None,
2102 )
2103 .await
2104 .unwrap();
2105
2106 let mut subscriber = client.subscribe_to_ignore_user_list_changes();
2107 assert!(subscriber.next().now_or_never().is_none());
2108
2109 let f = EventFactory::new();
2110 let mut sync_builder = SyncResponseBuilder::new();
2111 let response = sync_builder
2112 .add_global_account_data(f.ignored_user_list([User::Bob.into()]))
2113 .build_sync_response();
2114 client.receive_sync_response(response).await.unwrap();
2115
2116 assert_let!(Some(ignored) = subscriber.next().await);
2117 assert_eq!(ignored, [User::Bob]);
2118
2119 let response = sync_builder
2121 .add_global_account_data(f.ignored_user_list([User::Bob.into()]))
2122 .build_sync_response();
2123 client.receive_sync_response(response).await.unwrap();
2124
2125 assert!(subscriber.next().now_or_never().is_none());
2127
2128 let response =
2130 sync_builder.add_global_account_data(f.ignored_user_list([])).build_sync_response();
2131 client.receive_sync_response(response).await.unwrap();
2132
2133 assert_let!(Some(ignored) = subscriber.next().await);
2134 assert!(ignored.is_empty());
2135 }
2136
2137 async fn store_with_ignored_user() -> StoreConfig {
2138 let store = StoreConfig::new(CrossProcessLockConfig::SingleProcess);
2139 let user_id = user_id!("@alice:example.org");
2140
2141 let client =
2142 BaseClient::new(store.clone(), ThreadingSupport::Disabled, DmRoomDefinition::default());
2143
2144 client
2145 .activate(
2146 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
2147 RoomLoadSettings::default(),
2148 #[cfg(feature = "e2e-encryption")]
2149 None,
2150 )
2151 .await
2152 .unwrap();
2153
2154 let event_factory = EventFactory::new();
2156 let response = SyncResponseBuilder::new()
2157 .add_global_account_data(event_factory.ignored_user_list([User::Bob.into()]))
2158 .build_sync_response();
2159
2160 client.receive_sync_response(response).await.unwrap();
2161
2162 store
2163 }
2164
2165 #[async_test]
2166 async fn test_ignored_user_list_restored_on_activation() {
2167 let store = store_with_ignored_user().await;
2168 let user_id = user_id!("@alice:example.org");
2169
2170 let client =
2172 BaseClient::new(store, ThreadingSupport::Disabled, DmRoomDefinition::default());
2173
2174 client
2176 .activate(
2177 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
2178 RoomLoadSettings::default(),
2179 #[cfg(feature = "e2e-encryption")]
2180 None,
2181 )
2182 .await
2183 .unwrap();
2184
2185 let mut subscriber = client.subscribe_to_ignore_user_list_changes();
2186
2187 assert_eq!(subscriber.get(), [User::Bob]);
2189
2190 assert!(subscriber.next().now_or_never().is_none());
2192 }
2193
2194 #[async_test]
2195 async fn test_is_user_ignored() {
2196 let ignored_user_id = user_id!("@alice:example.org");
2197 let client = logged_in_base_client(None).await;
2198
2199 let mut sync_builder = SyncResponseBuilder::new();
2200 let f = EventFactory::new();
2201 let response = sync_builder
2202 .add_global_account_data(f.ignored_user_list([ignored_user_id.to_owned()]))
2203 .build_sync_response();
2204 client.receive_sync_response(response).await.unwrap();
2205
2206 assert!(client.is_user_ignored(ignored_user_id).await);
2207 }
2208
2209 #[cfg(feature = "e2e-encryption")]
2210 #[async_test]
2211 async fn test_invite_details_are_set() {
2212 let user_id = user_id!("@alice:localhost");
2213 let client = logged_in_base_client(Some(user_id)).await;
2214 let known_room_id = room_id!("!invited:localhost");
2215 let unknown_room_id = room_id!("!unknown:localhost");
2216
2217 let mut sync_builder = SyncResponseBuilder::new();
2218 let response = sync_builder
2219 .add_invited_room(InvitedRoomBuilder::new(known_room_id))
2220 .build_sync_response();
2221 client.receive_sync_response(response).await.unwrap();
2222
2223 let invited_room = client
2226 .get_room(known_room_id)
2227 .expect("The sync should have created a room in the invited state");
2228
2229 assert_eq!(invited_room.state(), RoomState::Invited);
2230 assert!(
2231 client.get_pending_key_bundle_details_for_room(known_room_id).await.unwrap().is_none()
2232 );
2233
2234 let joined_room = client
2236 .room_joined(known_room_id, Some(user_id.to_owned()))
2237 .await
2238 .expect("We should be able to mark a room as joined");
2239
2240 assert_eq!(joined_room.state(), RoomState::Joined);
2242 assert_let!(
2243 Ok(Some(details)) = client.get_pending_key_bundle_details_for_room(known_room_id).await
2244 );
2245 assert_eq!(details.inviter, user_id);
2246
2247 assert!(client.get_room(unknown_room_id).is_none());
2250 let unknown_room = client
2251 .room_joined(unknown_room_id, Some(user_id.to_owned()))
2252 .await
2253 .expect("We should be able to mark a room as joined");
2254
2255 assert_eq!(unknown_room.state(), RoomState::Joined);
2256 assert!(
2257 client
2258 .get_pending_key_bundle_details_for_room(unknown_room_id)
2259 .await
2260 .unwrap()
2261 .is_none()
2262 );
2263
2264 sync_builder.clear();
2265 let response =
2266 sync_builder.add_left_room(LeftRoomBuilder::new(known_room_id)).build_sync_response();
2267 client.receive_sync_response(response).await.unwrap();
2268
2269 let left_room = client
2271 .get_room(known_room_id)
2272 .expect("The sync should have created a room in the invited state");
2273
2274 assert_eq!(left_room.state(), RoomState::Left);
2275 assert!(
2276 client.get_pending_key_bundle_details_for_room(known_room_id).await.unwrap().is_none()
2277 );
2278 }
2279}