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 = "e2e-encryption")]
43use ruma::events::room::{history_visibility::HistoryVisibility, member::MembershipState};
44use ruma::{
45 OwnedRoomId, OwnedUserId, RoomId, UserId,
46 api::client::{self as api, sync::sync_events::v5},
47 events::{
48 StateEvent, StateEventType,
49 ignored_user_list::IgnoredUserListEventContent,
50 push_rules::{PushRulesEvent, PushRulesEventContent},
51 room::member::SyncRoomMemberEvent,
52 },
53 profile::UserProfileUpdate,
54 push::Ruleset,
55};
56use tokio::sync::{Mutex, MutexGuard, broadcast};
57#[cfg(feature = "e2e-encryption")]
58use tokio::sync::{RwLock, RwLockReadGuard};
59use tracing::{Level, debug, info, instrument, warn};
60
61#[cfg(feature = "e2e-encryption")]
62use crate::RoomMemberships;
63use crate::{
64 RoomStateFilter, SessionMeta, StateStore,
65 deserialized_responses::DisplayName,
66 error::{Error, Result},
67 event_cache::store::EventCacheStoreLock,
68 media::store::MediaStoreLock,
69 response_processors::{self as processors, Context},
70 room::{
71 Room, RoomInfoNotableUpdate, RoomInfoNotableUpdateReasons, RoomMembersUpdate, RoomState,
72 },
73 store::{
74 AvatarCache, BaseStateStore, DynStateStore, MemoryStore, Result as StoreResult,
75 RoomLoadSettings, StateChanges, StateStoreDataKey, StateStoreDataValue, StateStoreExt,
76 StoreConfig,
77 ambiguity_map::{AmbiguityCache, is_member_active},
78 },
79 sync::{RoomUpdates, SyncResponse},
80};
81
82#[derive(Clone)]
103pub struct BaseClient {
104 pub(crate) state_store: BaseStateStore,
106
107 event_cache_store: EventCacheStoreLock,
109
110 media_store: MediaStoreLock,
112
113 #[cfg(feature = "e2e-encryption")]
118 crypto_store: Arc<DynCryptoStore>,
119
120 #[cfg(feature = "e2e-encryption")]
124 olm_machine: Arc<RwLock<Option<OlmMachine>>>,
125
126 pub(crate) ignore_user_list_changes: SharedObservable<Vec<OwnedUserId>>,
128
129 pub(crate) global_profile_updates_sender: broadcast::Sender<BTreeSet<OwnedUserId>>,
132
133 #[cfg(feature = "e2e-encryption")]
136 pub room_key_recipient_strategy: CollectStrategy,
137
138 #[cfg(feature = "e2e-encryption")]
140 pub decryption_settings: DecryptionSettings,
141
142 #[cfg(feature = "e2e-encryption")]
144 pub handle_verification_events: bool,
145
146 #[cfg(all(feature = "e2e-encryption", feature = "unstable-msc4354"))]
150 sticky_redecryptor: Arc<StdMutex<Option<AbortOnDrop<()>>>>,
151
152 pub threading_support: ThreadingSupport,
154
155 #[cfg(feature = "experimental-x509-identity-verification")]
158 x509_signer: Option<Arc<dyn RawX509Signer>>,
159
160 #[cfg(feature = "experimental-x509-identity-verification")]
163 x509_verifier: Option<Arc<dyn RawX509Verifier>>,
164
165 pub dm_room_definition: DmRoomDefinition,
167}
168
169#[cfg(not(tarpaulin_include))]
170impl fmt::Debug for BaseClient {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.debug_struct("BaseClient")
173 .field("session_meta", &self.state_store.session_meta())
174 .field("sync_token", &self.state_store.sync_token)
175 .finish_non_exhaustive()
176 }
177}
178
179#[derive(Clone, Copy, Debug)]
191pub enum ThreadingSupport {
192 Enabled {
194 with_subscriptions: bool,
200 },
201 Disabled,
203}
204
205impl BaseClient {
206 pub fn new(
213 config: StoreConfig,
214 threading_support: ThreadingSupport,
215 dm_room_definition: DmRoomDefinition,
216 ) -> Self {
217 let store = BaseStateStore::new(config.state_store);
218
219 BaseClient {
220 state_store: store,
221 event_cache_store: config.event_cache_store,
222 media_store: config.media_store,
223 #[cfg(feature = "e2e-encryption")]
224 crypto_store: config.crypto_store,
225 #[cfg(feature = "e2e-encryption")]
226 olm_machine: Default::default(),
227 ignore_user_list_changes: Default::default(),
228 global_profile_updates_sender: broadcast::Sender::new(16),
229 #[cfg(feature = "e2e-encryption")]
230 room_key_recipient_strategy: Default::default(),
231 #[cfg(feature = "e2e-encryption")]
232 decryption_settings: DecryptionSettings {
233 sender_device_trust_requirement: TrustRequirement::Untrusted,
234 },
235 #[cfg(feature = "e2e-encryption")]
236 handle_verification_events: true,
237 #[cfg(all(feature = "e2e-encryption", feature = "unstable-msc4354"))]
238 sticky_redecryptor: Default::default(),
239 threading_support,
240 #[cfg(feature = "experimental-x509-identity-verification")]
241 x509_signer: None,
242 #[cfg(feature = "experimental-x509-identity-verification")]
243 x509_verifier: None,
244 dm_room_definition,
245 }
246 }
247
248 #[cfg(feature = "e2e-encryption")]
251 pub async fn clone_with_in_memory_state_store(
252 &self,
253 cross_process_mode: CrossProcessLockConfig,
254 handle_verification_events: bool,
255 ) -> Result<Self> {
256 let config = StoreConfig::new(cross_process_mode).state_store(MemoryStore::new());
257 let config = config.crypto_store(self.crypto_store.clone());
258
259 let copy = Self {
260 state_store: BaseStateStore::new(config.state_store),
261 event_cache_store: config.event_cache_store,
262 media_store: config.media_store,
263 crypto_store: self.crypto_store.clone(),
272 olm_machine: self.olm_machine.clone(),
273 ignore_user_list_changes: Default::default(),
274 global_profile_updates_sender: broadcast::Sender::new(16),
275 room_key_recipient_strategy: self.room_key_recipient_strategy.clone(),
276 decryption_settings: self.decryption_settings.clone(),
277 handle_verification_events,
278 #[cfg(feature = "unstable-msc4354")]
279 sticky_redecryptor: Default::default(),
280 threading_support: self.threading_support,
281 #[cfg(feature = "experimental-x509-identity-verification")]
282 x509_signer: self.x509_signer.clone(),
283 #[cfg(feature = "experimental-x509-identity-verification")]
284 x509_verifier: self.x509_verifier.clone(),
285 dm_room_definition: self.dm_room_definition.clone(),
286 };
287
288 copy.state_store.derive_from_other(&self.state_store).await?;
289
290 Ok(copy)
291 }
292
293 #[cfg(feature = "experimental-x509-identity-verification")]
296 pub fn set_x509_signer(&mut self, x509_signer: Option<Arc<dyn RawX509Signer>>) {
297 self.x509_signer = x509_signer;
298 }
299
300 #[cfg(feature = "experimental-x509-identity-verification")]
303 pub fn set_x509_verifier(&mut self, x509_verifier: Option<Arc<dyn RawX509Verifier>>) {
304 self.x509_verifier = x509_verifier
305 }
306
307 #[cfg(not(feature = "e2e-encryption"))]
310 #[allow(clippy::unused_async)]
311 pub async fn clone_with_in_memory_state_store(
312 &self,
313 cross_process_store_config: CrossProcessLockConfig,
314 _handle_verification_events: bool,
315 ) -> Result<Self> {
316 let config = StoreConfig::new(cross_process_store_config).state_store(MemoryStore::new());
317 Ok(Self::new(config, ThreadingSupport::Disabled, DmRoomDefinition::default()))
318 }
319
320 pub fn session_meta(&self) -> Option<&SessionMeta> {
326 self.state_store.session_meta()
327 }
328
329 pub fn rooms(&self) -> Vec<Room> {
331 self.state_store.rooms()
332 }
333
334 pub fn rooms_filtered(&self, filter: RoomStateFilter) -> Vec<Room> {
336 self.state_store.rooms_filtered(filter)
337 }
338
339 pub fn rooms_stream(
342 &self,
343 ) -> (Vector<Room>, impl Stream<Item = Vec<VectorDiff<Room>>> + use<>) {
344 self.state_store.rooms_stream()
345 }
346
347 pub fn get_or_create_room(&self, room_id: &RoomId, room_state: RoomState) -> Room {
350 self.state_store.get_or_create_room(room_id, room_state)
351 }
352
353 pub fn state_store(&self) -> &DynStateStore {
355 self.state_store.deref()
356 }
357
358 pub fn event_cache_store(&self) -> &EventCacheStoreLock {
360 &self.event_cache_store
361 }
362
363 pub fn media_store(&self) -> &MediaStoreLock {
365 &self.media_store
366 }
367
368 pub fn is_active(&self) -> bool {
372 self.state_store.session_meta().is_some()
373 }
374
375 pub async fn activate(
405 &self,
406 session_meta: SessionMeta,
407 room_load_settings: RoomLoadSettings,
408 #[cfg(feature = "e2e-encryption")] custom_account: Option<
409 crate::crypto::vodozemac::olm::Account,
410 >,
411 ) -> Result<()> {
412 debug!(user_id = ?session_meta.user_id, device_id = ?session_meta.device_id, "Activating the client");
413
414 self.state_store.load_rooms(&session_meta.user_id, room_load_settings).await?;
415 self.state_store.load_sync_token().await?;
416 self.state_store.set_session_meta(session_meta);
417
418 #[cfg(feature = "e2e-encryption")]
419 self.regenerate_olm(custom_account).await?;
420
421 Ok(())
422 }
423
424 #[cfg(feature = "e2e-encryption")]
428 pub async fn regenerate_olm(
429 &self,
430 custom_account: Option<crate::crypto::vodozemac::olm::Account>,
431 ) -> Result<()> {
432 tracing::debug!("regenerating OlmMachine");
433 let session_meta = self.session_meta().ok_or(Error::OlmError(OlmError::MissingSession))?;
434
435 let builder = OlmMachineBuilder::new(&session_meta.user_id, &session_meta.device_id)
438 .with_crypto_store(self.crypto_store.clone())
439 .with_custom_account(custom_account);
440
441 #[cfg(feature = "experimental-x509-identity-verification")]
442 let builder = builder
443 .with_x509_verifier(self.x509_verifier.clone())
444 .with_x509_signer(self.x509_signer.clone());
445
446 let olm_machine = builder.build().await.map_err(OlmError::from)?;
447
448 #[cfg(feature = "unstable-msc4354")]
451 let room_keys_stream = olm_machine.store().room_keys_received_stream();
452
453 *self.olm_machine.write().await = Some(olm_machine);
454
455 #[cfg(feature = "unstable-msc4354")]
458 {
459 let redecryptor = crate::sticky::spawn_redecryptor(
460 room_keys_stream,
461 self.olm_machine.clone(),
462 self.decryption_settings.clone(),
463 self.state_store.clone(),
464 );
465 *self.sticky_redecryptor.lock().unwrap() = Some(redecryptor);
466 }
467
468 Ok(())
469 }
470
471 pub async fn sync_token(&self) -> Option<String> {
474 self.state_store.sync_token.read().await.clone()
475 }
476
477 pub async fn room_knocked(&self, room_id: &RoomId) -> Result<Room> {
481 let room = self.state_store.get_or_create_room(room_id, RoomState::Knocked);
482
483 if room.state() != RoomState::Knocked {
484 let store_guard = self.state_store.lock().lock().await;
485
486 #[cfg(feature = "e2e-encryption")]
489 if let Some(olm_machine) = self.olm_machine().await.as_ref() {
490 olm_machine.store().clear_room_pending_key_bundle(room_id).await?
491 }
492
493 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
494 info.mark_as_knocked();
495 info.mark_state_partially_synced();
496 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
498 })
499 .await?;
500 }
501
502 Ok(room)
503 }
504
505 pub async fn room_joined(
549 &self,
550 room_id: &RoomId,
551 inviter: Option<OwnedUserId>,
552 ) -> Result<Room> {
553 let room = self.state_store.get_or_create_room(room_id, RoomState::Joined);
554
555 if room.state() != RoomState::Joined {
558 let store_guard = self.state_store_lock().lock().await;
559
560 #[cfg(feature = "e2e-encryption")]
561 {
562 let previous_state = room.state();
575 if previous_state == RoomState::Invited
576 && let Some(inviter) = inviter
577 && let Some(olm_machine) = self.olm_machine().await.as_ref()
578 {
579 olm_machine.store().store_room_pending_key_bundle(room_id, &inviter).await?
580 }
581 }
582 #[cfg(not(feature = "e2e-encryption"))]
583 {
584 let _ = inviter;
586 }
587
588 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
589 info.mark_as_joined();
590 info.mark_state_partially_synced();
591 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
593 })
594 .await?;
595 }
596
597 Ok(room)
598 }
599
600 pub async fn room_left(&self, room_id: &RoomId) -> Result<()> {
604 let room = self.state_store.get_or_create_room(room_id, RoomState::Left);
605
606 if room.state() != RoomState::Left {
607 let store_guard = self.state_store.lock().lock().await;
608
609 #[cfg(feature = "e2e-encryption")]
612 if let Some(olm_machine) = self.olm_machine().await.as_ref() {
613 olm_machine.store().clear_room_pending_key_bundle(room_id).await?
614 }
615
616 room.update_and_save_room_info_with_store_guard(&store_guard, |mut info| {
617 info.mark_as_left();
618 info.mark_state_partially_synced();
619 info.mark_members_missing(); (info, RoomInfoNotableUpdateReasons::MEMBERSHIP)
621 })
622 .await?;
623 }
624
625 Ok(())
626 }
627
628 pub fn state_store_lock(&self) -> &Mutex<()> {
633 self.state_store.lock()
634 }
635
636 #[instrument(skip_all)]
642 pub async fn receive_sync_response(
643 &self,
644 response: api::sync::sync_events::v3::Response,
645 ) -> Result<SyncResponse> {
646 self.receive_sync_response_with_requested_required_states(
647 response,
648 &RequestedRequiredStates::default(),
649 )
650 .await
651 }
652
653 pub async fn receive_sync_response_with_requested_required_states(
661 &self,
662 response: api::sync::sync_events::v3::Response,
663 requested_required_states: &RequestedRequiredStates,
664 ) -> Result<SyncResponse> {
665 if self.state_store.sync_token.read().await.as_ref() == Some(&response.next_batch) {
669 info!("Got the same sync response twice");
670 return Ok(SyncResponse::default());
671 }
672
673 let _time = timer!(Level::INFO, "Processing a sync response");
674
675 let state_store_guard = self.state_store_lock().lock().await;
678
679 let user_id = self
680 .session_meta()
681 .expect("Sync shouldn't run without an authenticated user")
682 .user_id
683 .to_owned();
684
685 #[cfg(feature = "e2e-encryption")]
686 let olm_machine = self.olm_machine().await;
687
688 let mut context = Context::new(StateChanges::new(response.next_batch.clone()));
689
690 #[cfg(feature = "e2e-encryption")]
691 let processors::e2ee::to_device::Output { processed_to_device_events: to_device } =
692 processors::e2ee::to_device::from_sync_v2(
693 &response,
694 olm_machine.as_ref(),
695 &self.decryption_settings,
696 )
697 .await?;
698
699 #[cfg(not(feature = "e2e-encryption"))]
700 let to_device = response
701 .to_device
702 .events
703 .into_iter()
704 .map(|raw| {
705 use matrix_sdk_common::deserialized_responses::{
706 ProcessedToDeviceEvent, ToDeviceUnableToDecryptInfo,
707 ToDeviceUnableToDecryptReason,
708 };
709 use ruma::events::ToDeviceEventType;
710
711 if let Ok(Some(event_type)) = raw.get_field::<ToDeviceEventType>("type") {
712 if event_type == ToDeviceEventType::RoomEncrypted {
713 ProcessedToDeviceEvent::UnableToDecrypt {
714 encrypted_event: raw,
715 utd_info: ToDeviceUnableToDecryptInfo {
716 reason: ToDeviceUnableToDecryptReason::EncryptionIsDisabled,
717 },
718 }
719 } else {
720 ProcessedToDeviceEvent::PlainText(raw)
721 }
722 } else {
723 ProcessedToDeviceEvent::Invalid(raw)
725 }
726 })
727 .collect();
728
729 let mut ambiguity_cache = AmbiguityCache::new(self.state_store.inner.clone());
730 let mut avatar_cache = AvatarCache::new(self.state_store.inner.clone());
731
732 let global_account_data_processor =
733 processors::account_data::global(&response.account_data.events);
734
735 let push_rules = self.get_push_rules(&global_account_data_processor).await?;
736
737 let mut room_updates = RoomUpdates::default();
738 let mut notifications = Default::default();
739
740 let mut updated_members_in_room: BTreeMap<OwnedRoomId, BTreeSet<OwnedUserId>> =
741 BTreeMap::new();
742
743 #[cfg(feature = "e2e-encryption")]
744 let e2ee_context = processors::e2ee::E2EE::new(
745 olm_machine.as_ref(),
746 &self.decryption_settings,
747 self.handle_verification_events,
748 );
749
750 for (room_id, joined_room) in response.rooms.join {
751 let joined_room_update = processors::room::sync_v2::update_joined_room(
752 &mut context,
753 processors::room::RoomCreationData::new(
754 &room_id,
755 requested_required_states,
756 &mut ambiguity_cache,
757 &mut avatar_cache,
758 ),
759 joined_room,
760 &mut updated_members_in_room,
761 processors::notification::Notification::new(
762 &push_rules,
763 &mut notifications,
764 &self.state_store,
765 ),
766 #[cfg(feature = "e2e-encryption")]
767 &e2ee_context,
768 )
769 .await?;
770
771 room_updates.joined.insert(room_id, joined_room_update);
772 }
773
774 for (room_id, left_room) in response.rooms.leave {
775 let left_room_update = processors::room::sync_v2::update_left_room(
776 &mut context,
777 processors::room::RoomCreationData::new(
778 &room_id,
779 requested_required_states,
780 &mut ambiguity_cache,
781 &mut avatar_cache,
782 ),
783 left_room,
784 processors::notification::Notification::new(
785 &push_rules,
786 &mut notifications,
787 &self.state_store,
788 ),
789 #[cfg(feature = "e2e-encryption")]
790 &e2ee_context,
791 )
792 .await?;
793
794 room_updates.left.insert(room_id, left_room_update);
795 }
796
797 for (room_id, invited_room) in response.rooms.invite {
798 let invited_room_update = processors::room::sync_v2::update_invited_room(
799 &mut context,
800 &room_id,
801 &user_id,
802 invited_room,
803 processors::notification::Notification::new(
804 &push_rules,
805 &mut notifications,
806 &self.state_store,
807 ),
808 #[cfg(feature = "e2e-encryption")]
809 &e2ee_context,
810 )
811 .await?;
812
813 room_updates.invited.insert(room_id, invited_room_update);
814 }
815
816 for (room_id, knocked_room) in response.rooms.knock {
817 let knocked_room_update = processors::room::sync_v2::update_knocked_room(
818 &mut context,
819 &room_id,
820 &user_id,
821 knocked_room,
822 processors::notification::Notification::new(
823 &push_rules,
824 &mut notifications,
825 &self.state_store,
826 ),
827 #[cfg(feature = "e2e-encryption")]
828 &e2ee_context,
829 )
830 .await?;
831
832 room_updates.knocked.insert(room_id, knocked_room_update);
833 }
834
835 global_account_data_processor.apply(&mut context, &self.state_store).await;
836
837 context.state_changes.presence = response
838 .presence
839 .events
840 .iter()
841 .filter_map(|e| {
842 let event = e.deserialize().ok()?;
843 Some((event.sender, e.clone()))
844 })
845 .collect();
846
847 context.state_changes.ambiguity_maps = ambiguity_cache.cache;
848
849 processors::changes::save_and_apply(
850 context,
851 &self.state_store,
852 &state_store_guard,
853 &self.ignore_user_list_changes,
854 Some(response.next_batch.clone()),
855 )
856 .await?;
857
858 let mut context = Context::default();
859
860 processors::room::display_name::update_for_rooms(
864 &mut context,
865 &room_updates,
866 &self.state_store,
867 )
868 .await;
869
870 processors::changes::save_only(context, &self.state_store, &state_store_guard).await?;
872
873 for (room_id, member_ids) in updated_members_in_room {
874 if let Some(room) = self.get_room(&room_id) {
875 let _ =
876 room.room_member_updates_sender.send(RoomMembersUpdate::Partial(member_ids));
877 }
878 }
879
880 drop(state_store_guard);
882
883 let response = SyncResponse {
884 rooms: room_updates,
885 presence: response.presence.events,
886 account_data: response.account_data.events,
887 to_device,
888 notifications,
889 };
890
891 Ok(response)
892 }
893
894 #[instrument(skip_all, fields(?room_id))]
905 pub async fn receive_all_members(
906 &self,
907 room_id: &RoomId,
908 request: &api::membership::get_member_events::v3::Request,
909 response: &api::membership::get_member_events::v3::Response,
910 ) -> Result<()> {
911 if request.membership.is_some() || request.not_membership.is_some() || request.at.is_some()
912 {
913 return Err(Error::InvalidReceiveMembersParameters);
918 }
919
920 let Some(room) = self.state_store.room(room_id) else {
921 return Ok(());
923 };
924
925 let mut chunk = Vec::with_capacity(response.chunk.len());
926 let mut context = Context::default();
927
928 #[cfg(feature = "e2e-encryption")]
929 let mut user_ids = BTreeSet::new();
930
931 let mut ambiguity_map: HashMap<DisplayName, BTreeSet<OwnedUserId>> = Default::default();
932
933 for raw_event in &response.chunk {
934 let member = match raw_event.deserialize() {
935 Ok(ev) => ev,
936 Err(e) => {
937 let event_id: Option<String> = raw_event.get_field("event_id").ok().flatten();
938 debug!(event_id, "Failed to deserialize member event: {e}");
939 continue;
940 }
941 };
942
943 #[cfg(feature = "e2e-encryption")]
954 match member.membership() {
955 MembershipState::Join | MembershipState::Invite => {
956 user_ids.insert(member.state_key().to_owned());
957 }
958 _ => (),
959 }
960
961 if let StateEvent::Original(e) = &member
962 && is_member_active(&e.content.membership)
963 && let Some(d) = &e.content.displayname
964 {
965 let display_name = DisplayName::new(d);
966 ambiguity_map.entry(display_name).or_default().insert(member.state_key().clone());
967 }
968
969 let sync_member: SyncRoomMemberEvent = member.clone().into();
970 processors::profiles::upsert_or_delete(&mut context, room_id, &sync_member);
971
972 context
973 .state_changes
974 .state
975 .entry(room_id.to_owned())
976 .or_default()
977 .entry(member.event_type())
978 .or_default()
979 .insert(member.state_key().to_string(), raw_event.clone().cast());
980 chunk.push(member);
981 }
982
983 #[cfg(feature = "e2e-encryption")]
984 processors::e2ee::tracked_users::update(
985 self.olm_machine().await.as_ref(),
986 room.encryption_state(),
987 &user_ids,
988 )
989 .await?;
990
991 context.state_changes.ambiguity_maps.insert(room_id.to_owned(), ambiguity_map);
992
993 {
994 let state_store_guard = self.state_store_lock().lock().await;
995
996 let mut room_info = room.clone_info();
997 room_info.mark_members_synced();
998 context.state_changes.add_room(room_info);
999
1000 processors::changes::save_and_apply(
1001 context,
1002 &self.state_store,
1003 &state_store_guard,
1004 &self.ignore_user_list_changes,
1005 None,
1006 )
1007 .await?;
1008 }
1009
1010 let _ = room.room_member_updates_sender.send(RoomMembersUpdate::FullReload);
1011
1012 #[cfg(feature = "e2e-encryption")]
1013 if let Some(olm) = self.olm_machine().await.as_ref() {
1014 tracing::debug!("Rotating room key due to full member list reload");
1020 if let Err(e) = olm.discard_room_key(room_id).await {
1021 tracing::warn!("Error discarding room key: {e:?}");
1022 }
1023 }
1024
1025 Ok(())
1026 }
1027
1028 pub async fn receive_filter_upload(
1043 &self,
1044 filter_name: &str,
1045 response: &api::filter::create_filter::v3::Response,
1046 ) -> Result<()> {
1047 Ok(self
1048 .state_store
1049 .set_kv_data(
1050 StateStoreDataKey::Filter(filter_name),
1051 StateStoreDataValue::Filter(response.filter_id.clone()),
1052 )
1053 .await?)
1054 }
1055
1056 pub async fn get_filter(&self, filter_name: &str) -> StoreResult<Option<String>> {
1068 let filter = self
1069 .state_store
1070 .get_kv_data(StateStoreDataKey::Filter(filter_name))
1071 .await?
1072 .map(|d| d.into_filter().expect("State store data not a filter"));
1073
1074 Ok(filter)
1075 }
1076
1077 #[cfg(feature = "e2e-encryption")]
1079 pub async fn share_room_key(&self, room_id: &RoomId) -> Result<Vec<Arc<ToDeviceRequest>>> {
1080 match self.olm_machine().await.as_ref() {
1081 Some(o) => {
1082 let Some(room) = self.get_room(room_id) else {
1083 return Err(Error::InsufficientData);
1084 };
1085
1086 let history_visibility = room.history_visibility_or_default();
1087 let Some(room_encryption_event) = room.encryption_settings() else {
1088 return Err(Error::EncryptionNotEnabled);
1089 };
1090
1091 let filter = if history_visibility == HistoryVisibility::Joined {
1094 RoomMemberships::JOIN
1095 } else {
1096 RoomMemberships::ACTIVE
1097 };
1098
1099 let members = self.state_store.get_user_ids(room_id, filter).await?;
1100
1101 let Some(settings) = EncryptionSettings::from_possibly_redacted(
1102 room_encryption_event,
1103 history_visibility,
1104 self.room_key_recipient_strategy.clone(),
1105 ) else {
1106 return Err(Error::EncryptionNotEnabled);
1107 };
1108
1109 Ok(o.share_room_key(room_id, members.iter().map(Deref::deref), settings).await?)
1110 }
1111 None => panic!("Olm machine wasn't started"),
1112 }
1113 }
1114
1115 pub fn get_room(&self, room_id: &RoomId) -> Option<Room> {
1121 self.state_store.room(room_id)
1122 }
1123
1124 pub async fn forget_room(&self, room_id: &RoomId) -> Result<()> {
1132 self.state_store.forget_room(room_id).await?;
1134
1135 Ok(())
1136 }
1137
1138 #[cfg(feature = "e2e-encryption")]
1140 pub async fn olm_machine(&self) -> RwLockReadGuard<'_, Option<OlmMachine>> {
1141 self.olm_machine.read().await
1142 }
1143
1144 pub(crate) async fn get_push_rules(
1150 &self,
1151 global_account_data_processor: &processors::account_data::Global,
1152 ) -> Result<Ruleset> {
1153 let _timer = timer!(Level::TRACE, "get_push_rules");
1154 if let Some(event) = global_account_data_processor
1155 .push_rules()
1156 .and_then(|ev| ev.deserialize_as_unchecked::<PushRulesEvent>().ok())
1157 {
1158 Ok(event.content.global)
1159 } else if let Some(event) = self
1160 .state_store
1161 .get_account_data_event_static::<PushRulesEventContent>()
1162 .await?
1163 .and_then(|ev| ev.deserialize().ok())
1164 {
1165 Ok(event.content.global)
1166 } else if let Some(session_meta) = self.state_store.session_meta() {
1167 Ok(Ruleset::server_default(&session_meta.user_id))
1168 } else {
1169 Ok(Ruleset::new())
1170 }
1171 }
1172
1173 pub fn subscribe_to_ignore_user_list_changes(&self) -> Subscriber<Vec<OwnedUserId>> {
1176 self.ignore_user_list_changes.subscribe()
1177 }
1178
1179 pub fn room_info_notable_update_receiver(&self) -> broadcast::Receiver<RoomInfoNotableUpdate> {
1183 self.state_store.room_info_notable_update_sender.subscribe()
1184 }
1185
1186 pub fn subscribe_to_global_profile_updates(
1192 &self,
1193 ) -> broadcast::Receiver<BTreeSet<OwnedUserId>> {
1194 self.global_profile_updates_sender.subscribe()
1195 }
1196
1197 pub async fn own_profile_updated(&self, update: UserProfileUpdate) -> Result<()> {
1205 let own_user_id = self.session_meta().ok_or(Error::InsufficientData)?.user_id.clone();
1206 let state_store_guard = self.state_store_lock().lock().await;
1207
1208 let mut changes = StateChanges::default();
1209 changes.global_profiles.insert(own_user_id.clone(), update);
1210 self.state_store.save_changes_with_guard(&state_store_guard, &changes).await?;
1211
1212 self.notify_global_profile_updates(BTreeSet::from([own_user_id]), &state_store_guard)
1213 }
1214
1215 pub(crate) fn notify_global_profile_updates(
1221 &self,
1222 user_ids: BTreeSet<OwnedUserId>,
1223 #[cfg_attr(not(feature = "unstable-msc4426"), allow(unused_variables))]
1224 state_store_guard: &MutexGuard<'_, ()>,
1225 ) -> Result<()> {
1226 if user_ids.is_empty() {
1227 return Ok(());
1228 }
1229
1230 #[cfg(feature = "unstable-msc4426")]
1232 for room in self.state_store.rooms() {
1233 if room.hero_user_ids().iter().any(|hero| user_ids.contains(hero)) {
1234 room.update_room_info_with_store_guard(state_store_guard, |room_info| {
1235 (room_info, RoomInfoNotableUpdateReasons::HEROES)
1236 })
1237 .map_err(crate::StoreError::from)?;
1238 }
1239 }
1240
1241 let _ = self.global_profile_updates_sender.send(user_ids);
1242
1243 Ok(())
1244 }
1245
1246 pub async fn is_user_ignored(&self, user_id: &UserId) -> bool {
1248 match self.state_store.get_account_data_event_static::<IgnoredUserListEventContent>().await
1249 {
1250 Ok(Some(raw_ignored_user_list)) => match raw_ignored_user_list.deserialize() {
1251 Ok(current_ignored_user_list) => {
1252 current_ignored_user_list.content.ignored_users.contains_key(user_id)
1253 }
1254 Err(error) => {
1255 warn!(?error, "Failed to deserialize the ignored user list event");
1256 false
1257 }
1258 },
1259 Ok(None) => false,
1260 Err(error) => {
1261 warn!(?error, "Could not get the ignored user list from the state store");
1262 false
1263 }
1264 }
1265 }
1266
1267 #[cfg(feature = "e2e-encryption")]
1272 pub async fn get_pending_key_bundle_details_for_room(
1273 &self,
1274 room_id: &RoomId,
1275 ) -> Result<Option<RoomPendingKeyBundleDetails>> {
1276 let result = match self.olm_machine().await.as_ref() {
1277 Some(machine) => {
1278 machine.store().get_pending_key_bundle_details_for_room(room_id).await?
1279 }
1280 None => None,
1281 };
1282 Ok(result)
1283 }
1284
1285 pub async fn close_stores(&self) -> Result<()> {
1289 self.state_store.close().await?;
1290 self.event_cache_store.close().await.map_err(Error::EventCacheStore)?;
1291 self.media_store.close().await.map_err(Error::MediaStore)?;
1292
1293 #[cfg(feature = "e2e-encryption")]
1294 self.crypto_store.close().await.map_err(Error::CryptoStore)?;
1295
1296 Ok(())
1297 }
1298
1299 pub async fn reopen_stores(&self) -> Result<()> {
1301 #[cfg(feature = "e2e-encryption")]
1302 self.crypto_store.reopen().await.map_err(Error::CryptoStore)?;
1303
1304 self.media_store.reopen().await.map_err(Error::MediaStore)?;
1305 self.event_cache_store.reopen().await.map_err(Error::EventCacheStore)?;
1306 self.state_store.reopen().await?;
1307
1308 Ok(())
1309 }
1310}
1311
1312#[derive(Debug, Default)]
1324pub struct RequestedRequiredStates {
1325 default: Vec<(StateEventType, String)>,
1326 for_rooms: HashMap<OwnedRoomId, Vec<(StateEventType, String)>>,
1327}
1328
1329impl RequestedRequiredStates {
1330 pub fn new(
1335 default: Vec<(StateEventType, String)>,
1336 for_rooms: HashMap<OwnedRoomId, Vec<(StateEventType, String)>>,
1337 ) -> Self {
1338 Self { default, for_rooms }
1339 }
1340
1341 pub fn for_room(&self, room_id: &RoomId) -> &[(StateEventType, String)] {
1343 self.for_rooms.get(room_id).unwrap_or(&self.default)
1344 }
1345}
1346
1347impl From<&v5::Request> for RequestedRequiredStates {
1348 fn from(request: &v5::Request) -> Self {
1349 let mut default = BTreeSet::new();
1357
1358 for list in request.lists.values() {
1359 default.extend(BTreeSet::from_iter(list.room_details.required_state.iter().cloned()));
1360 }
1361
1362 for room_subscription in request.room_subscriptions.values() {
1363 default.extend(BTreeSet::from_iter(room_subscription.required_state.iter().cloned()));
1364 }
1365
1366 Self { default: default.into_iter().collect(), for_rooms: HashMap::new() }
1367 }
1368}
1369
1370#[derive(Debug, Clone, Default)]
1372#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
1373pub enum DmRoomDefinition {
1374 #[default]
1377 MatrixSpec,
1378 TwoMembers,
1381}
1382
1383#[cfg(test)]
1384mod tests {
1385 use std::collections::HashMap;
1386
1387 use futures_util::FutureExt as _;
1388 use matrix_sdk_common::cross_process_lock::CrossProcessLockConfig;
1389 use matrix_sdk_test::{
1390 InvitedRoomBuilder, LeftRoomBuilder, SyncResponseBuilder, User, async_test,
1391 event_factory::EventFactory, ruma_response_from_json,
1392 };
1393 #[cfg(feature = "unstable-msc4426")]
1394 use ruma::profile::{
1395 ProfileFieldValue, StatusProfileField, UserProfileChanges, UserProfileUpdate,
1396 };
1397 use ruma::{
1398 RoomId,
1399 api::client::{self as api, sync::sync_events::v5},
1400 event_id,
1401 events::{StateEventType, room::member::MembershipState},
1402 room_id,
1403 serde::Raw,
1404 user_id,
1405 };
1406 use serde_json::{json, value::to_raw_value};
1407 use strass::assert_let;
1408
1409 use super::{BaseClient, RequestedRequiredStates};
1410 use crate::{
1411 DmRoomDefinition, RoomDisplayName, RoomState, SessionMeta,
1412 client::ThreadingSupport,
1413 store::{RoomLoadSettings, StateStoreExt, StoreConfig},
1414 test_utils::logged_in_base_client,
1415 };
1416 #[cfg(feature = "unstable-msc4426")]
1417 use crate::{RoomMemberships, store::StateChanges};
1418
1419 #[test]
1420 fn test_requested_required_states() {
1421 let room_id_0 = room_id!("!r0");
1422 let room_id_1 = room_id!("!r1");
1423
1424 let requested_required_states = RequestedRequiredStates::new(
1425 vec![(StateEventType::RoomAvatar, "".to_owned())],
1426 HashMap::from([(
1427 room_id_0.to_owned(),
1428 vec![
1429 (StateEventType::RoomMember, "foo".to_owned()),
1430 (StateEventType::RoomEncryption, "".to_owned()),
1431 ],
1432 )]),
1433 );
1434
1435 assert_eq!(
1437 requested_required_states.for_room(room_id_0),
1438 &[
1439 (StateEventType::RoomMember, "foo".to_owned()),
1440 (StateEventType::RoomEncryption, "".to_owned()),
1441 ]
1442 );
1443
1444 assert_eq!(
1446 requested_required_states.for_room(room_id_1),
1447 &[(StateEventType::RoomAvatar, "".to_owned()),]
1448 );
1449 }
1450
1451 #[test]
1452 fn test_requested_required_states_from_sync_v5_request() {
1453 let room_id_0 = room_id!("!r0");
1454 let room_id_1 = room_id!("!r1");
1455
1456 let mut request = v5::Request::new();
1458
1459 {
1460 let requested_required_states = RequestedRequiredStates::from(&request);
1461
1462 assert!(requested_required_states.default.is_empty());
1463 assert!(requested_required_states.for_rooms.is_empty());
1464 }
1465
1466 request.lists.insert("foo".to_owned(), {
1468 let mut list = v5::request::List::default();
1469 list.room_details.required_state = vec![
1470 (StateEventType::RoomAvatar, "".to_owned()),
1471 (StateEventType::RoomEncryption, "".to_owned()),
1472 ];
1473
1474 list
1475 });
1476
1477 {
1478 let requested_required_states = RequestedRequiredStates::from(&request);
1479
1480 assert_eq!(
1481 requested_required_states.default,
1482 &[
1483 (StateEventType::RoomAvatar, "".to_owned()),
1484 (StateEventType::RoomEncryption, "".to_owned())
1485 ]
1486 );
1487 assert!(requested_required_states.for_rooms.is_empty());
1488 }
1489
1490 request.lists.insert("bar".to_owned(), {
1492 let mut list = v5::request::List::default();
1493 list.room_details.required_state = vec![
1494 (StateEventType::RoomEncryption, "".to_owned()),
1495 (StateEventType::RoomName, "".to_owned()),
1496 ];
1497
1498 list
1499 });
1500
1501 {
1502 let requested_required_states = RequestedRequiredStates::from(&request);
1503
1504 assert_eq!(
1506 requested_required_states.default,
1507 &[
1508 (StateEventType::RoomAvatar, "".to_owned()),
1509 (StateEventType::RoomEncryption, "".to_owned()),
1510 (StateEventType::RoomName, "".to_owned()),
1511 ]
1512 );
1513 assert!(requested_required_states.for_rooms.is_empty());
1514 }
1515
1516 request.room_subscriptions.insert(room_id_0.to_owned(), {
1518 let mut room_subscription = v5::request::RoomSubscription::default();
1519
1520 room_subscription.required_state = vec![
1521 (StateEventType::RoomJoinRules, "".to_owned()),
1522 (StateEventType::RoomEncryption, "".to_owned()),
1523 ];
1524
1525 room_subscription
1526 });
1527
1528 {
1529 let requested_required_states = RequestedRequiredStates::from(&request);
1530
1531 assert_eq!(
1534 requested_required_states.default,
1535 &[
1536 (StateEventType::RoomAvatar, "".to_owned()),
1537 (StateEventType::RoomEncryption, "".to_owned()),
1538 (StateEventType::RoomJoinRules, "".to_owned()),
1539 (StateEventType::RoomName, "".to_owned()),
1540 ]
1541 );
1542 assert!(requested_required_states.for_rooms.is_empty());
1543 }
1544
1545 request.room_subscriptions.insert(room_id_1.to_owned(), {
1547 let mut room_subscription = v5::request::RoomSubscription::default();
1548
1549 room_subscription.required_state = vec![
1550 (StateEventType::RoomName, "".to_owned()),
1551 (StateEventType::RoomTopic, "".to_owned()),
1552 ];
1553
1554 room_subscription
1555 });
1556
1557 {
1558 let requested_required_states = RequestedRequiredStates::from(&request);
1559
1560 assert_eq!(
1563 requested_required_states.default,
1564 &[
1565 (StateEventType::RoomAvatar, "".to_owned()),
1566 (StateEventType::RoomEncryption, "".to_owned()),
1567 (StateEventType::RoomJoinRules, "".to_owned()),
1568 (StateEventType::RoomName, "".to_owned()),
1569 (StateEventType::RoomTopic, "".to_owned()),
1570 ]
1571 );
1572 }
1573 }
1574
1575 #[async_test]
1576 async fn test_invite_after_leaving() {
1577 let user_id = user_id!("@alice:example.org");
1578 let room_id = room_id!("!test:example.org");
1579
1580 let client = logged_in_base_client(Some(user_id)).await;
1581 let f = EventFactory::new();
1582
1583 let mut sync_builder = SyncResponseBuilder::new();
1584
1585 let response = sync_builder
1586 .add_left_room(
1587 LeftRoomBuilder::new(room_id).add_timeline_event(
1588 EventFactory::new()
1589 .member(user_id)
1590 .membership(MembershipState::Leave)
1591 .display_name("Alice")
1592 .event_id(event_id!("$994173582443PhrSn:example.org")),
1593 ),
1594 )
1595 .build_sync_response();
1596 client.receive_sync_response(response).await.unwrap();
1597 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Left);
1598
1599 let response = sync_builder
1600 .add_invited_room(
1601 InvitedRoomBuilder::new(room_id).add_state_event(
1602 f.member(user_id)
1603 .sender(user_id!("@example:example.org"))
1604 .membership(MembershipState::Invite)
1605 .display_name("Alice"),
1606 ),
1607 )
1608 .build_sync_response();
1609 client.receive_sync_response(response).await.unwrap();
1610 assert_eq!(client.get_room(room_id).unwrap().state(), RoomState::Invited);
1611 }
1612
1613 #[async_test]
1614 async fn test_invite_displayname() {
1615 let user_id = user_id!("@alice:example.org");
1616 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1617
1618 let client = logged_in_base_client(Some(user_id)).await;
1619
1620 let response = ruma_response_from_json(&json!({
1621 "next_batch": "asdkl;fjasdkl;fj;asdkl;f",
1622 "device_one_time_keys_count": {
1623 "signed_curve25519": 50u64
1624 },
1625 "device_unused_fallback_key_types": [
1626 "signed_curve25519"
1627 ],
1628 "rooms": {
1629 "invite": {
1630 "!ithpyNKDtmhneaTQja:example.org": {
1631 "invite_state": {
1632 "events": [
1633 {
1634 "content": {
1635 "creator": "@test:example.org",
1636 "room_version": "9"
1637 },
1638 "sender": "@test:example.org",
1639 "state_key": "",
1640 "type": "m.room.create"
1641 },
1642 {
1643 "content": {
1644 "join_rule": "invite"
1645 },
1646 "sender": "@test:example.org",
1647 "state_key": "",
1648 "type": "m.room.join_rules"
1649 },
1650 {
1651 "content": {
1652 "algorithm": "m.megolm.v1.aes-sha2"
1653 },
1654 "sender": "@test:example.org",
1655 "state_key": "",
1656 "type": "m.room.encryption"
1657 },
1658 {
1659 "content": {
1660 "avatar_url": "mxc://example.org/dcBBDwuWEUrjfrOchvkirUST",
1661 "displayname": "Kyra",
1662 "membership": "join"
1663 },
1664 "sender": "@test:example.org",
1665 "state_key": "@test:example.org",
1666 "type": "m.room.member"
1667 },
1668 {
1669 "content": {
1670 "avatar_url": "mxc://example.org/ABFEXSDrESxovWwEnCYdNcHT",
1671 "displayname": "alice",
1672 "is_direct": true,
1673 "membership": "invite"
1674 },
1675 "origin_server_ts": 1650878657984u64,
1676 "sender": "@test:example.org",
1677 "state_key": "@alice:example.org",
1678 "type": "m.room.member",
1679 "unsigned": {
1680 "age": 14u64
1681 },
1682 "event_id": "$fLDqltg9Puj-kWItLSFVHPGN4YkgpYQf2qImPzdmgrE"
1683 }
1684 ]
1685 }
1686 }
1687 }
1688 }
1689 }));
1690
1691 client.receive_sync_response(response).await.unwrap();
1692
1693 let room = client.get_room(room_id).expect("Room not found");
1694 assert_eq!(room.state(), RoomState::Invited);
1695 assert_eq!(
1696 room.compute_display_name().await.expect("fetching display name failed").into_inner(),
1697 RoomDisplayName::Calculated("Kyra".to_owned())
1698 );
1699 }
1700
1701 #[async_test]
1702 async fn test_deserialization_failure() {
1703 let user_id = user_id!("@alice:example.org");
1704 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1705
1706 let client = BaseClient::new(
1707 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1708 ThreadingSupport::Disabled,
1709 DmRoomDefinition::default(),
1710 );
1711 client
1712 .activate(
1713 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1714 RoomLoadSettings::default(),
1715 #[cfg(feature = "e2e-encryption")]
1716 None,
1717 )
1718 .await
1719 .unwrap();
1720
1721 let response = ruma_response_from_json(&json!({
1722 "next_batch": "asdkl;fjasdkl;fj;asdkl;f",
1723 "rooms": {
1724 "join": {
1725 "!ithpyNKDtmhneaTQja:example.org": {
1726 "state": {
1727 "events": [
1728 {
1729 "invalid": "invalid",
1730 },
1731 {
1732 "content": {
1733 "name": "The room name"
1734 },
1735 "event_id": "$143273582443PhrSn:example.org",
1736 "origin_server_ts": 1432735824653u64,
1737 "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
1738 "sender": "@example:example.org",
1739 "state_key": "",
1740 "type": "m.room.name",
1741 "unsigned": {
1742 "age": 1234
1743 }
1744 },
1745 ]
1746 }
1747 }
1748 }
1749 }
1750 }));
1751
1752 client.receive_sync_response(response).await.unwrap();
1753 client
1754 .state_store()
1755 .get_state_event_static::<ruma::events::room::name::RoomNameEventContent>(room_id)
1756 .await
1757 .expect("Failed to fetch state event")
1758 .expect("State event not found")
1759 .deserialize()
1760 .expect("Failed to deserialize state event");
1761 }
1762
1763 #[async_test]
1764 async fn test_invited_members_arent_ignored() {
1765 let user_id = user_id!("@alice:example.org");
1766 let inviter_user_id = user_id!("@bob:example.org");
1767 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1768
1769 let client = BaseClient::new(
1770 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1771 ThreadingSupport::Disabled,
1772 DmRoomDefinition::default(),
1773 );
1774 client
1775 .activate(
1776 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1777 RoomLoadSettings::default(),
1778 #[cfg(feature = "e2e-encryption")]
1779 None,
1780 )
1781 .await
1782 .unwrap();
1783
1784 let mut sync_builder = SyncResponseBuilder::new();
1786 let response = sync_builder
1787 .add_joined_room(matrix_sdk_test::JoinedRoomBuilder::new(room_id))
1788 .build_sync_response();
1789 client.receive_sync_response(response).await.unwrap();
1790
1791 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1794
1795 let raw_member_event = json!({
1796 "content": {
1797 "avatar_url": "mxc://localhost/fewjilfewjil42",
1798 "displayname": "Invited Alice",
1799 "membership": "invite"
1800 },
1801 "event_id": "$151800140517rfvjc:localhost",
1802 "origin_server_ts": 151800140,
1803 "room_id": room_id,
1804 "sender": inviter_user_id,
1805 "state_key": user_id,
1806 "type": "m.room.member",
1807 "unsigned": {
1808 "age": 13374242,
1809 }
1810 });
1811 let response = api::membership::get_member_events::v3::Response::new(vec![Raw::from_json(
1812 to_raw_value(&raw_member_event).unwrap(),
1813 )]);
1814
1815 client.receive_all_members(room_id, &request, &response).await.unwrap();
1817
1818 let room = client.get_room(room_id).unwrap();
1819
1820 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1822
1823 assert_eq!(member.user_id(), user_id);
1824 assert_eq!(member.display_name().unwrap(), "Invited Alice");
1825 assert_eq!(member.avatar_url().unwrap().to_string(), "mxc://localhost/fewjilfewjil42");
1826 }
1827
1828 #[async_test]
1829 async fn test_reinvited_members_get_a_display_name() {
1830 let user_id = user_id!("@alice:example.org");
1831 let inviter_user_id = user_id!("@bob:example.org");
1832 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1833
1834 let client = BaseClient::new(
1835 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1836 ThreadingSupport::Disabled,
1837 DmRoomDefinition::default(),
1838 );
1839 client
1840 .activate(
1841 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1842 RoomLoadSettings::default(),
1843 #[cfg(feature = "e2e-encryption")]
1844 None,
1845 )
1846 .await
1847 .unwrap();
1848
1849 let f = EventFactory::new().sender(user_id);
1852 let mut sync_builder = SyncResponseBuilder::new();
1853 let response = sync_builder
1854 .add_joined_room(
1855 matrix_sdk_test::JoinedRoomBuilder::new(room_id)
1856 .add_state_event(f.member(user_id).leave()),
1857 )
1858 .build_sync_response();
1859 client.receive_sync_response(response).await.unwrap();
1860
1861 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1863
1864 let raw_member_event = json!({
1865 "content": {
1866 "avatar_url": "mxc://localhost/fewjilfewjil42",
1867 "displayname": "Invited Alice",
1868 "membership": "invite"
1869 },
1870 "event_id": "$151800140517rfvjc:localhost",
1871 "origin_server_ts": 151800140,
1872 "room_id": room_id,
1873 "sender": inviter_user_id,
1874 "state_key": user_id,
1875 "type": "m.room.member",
1876 "unsigned": {
1877 "age": 13374242,
1878 }
1879 });
1880 let response = api::membership::get_member_events::v3::Response::new(vec![Raw::from_json(
1881 to_raw_value(&raw_member_event).unwrap(),
1882 )]);
1883
1884 client.receive_all_members(room_id, &request, &response).await.unwrap();
1886
1887 let room = client.get_room(room_id).unwrap();
1888
1889 let member = room.get_member(user_id).await.expect("ok").expect("exists");
1891
1892 assert_eq!(member.user_id(), user_id);
1893 assert_eq!(member.display_name().unwrap(), "Invited Alice");
1894 assert_eq!(member.avatar_url().unwrap().to_string(), "mxc://localhost/fewjilfewjil42");
1895 }
1896
1897 async fn base_client_with_joined_room(room_id: &RoomId) -> BaseClient {
1898 let client = logged_in_base_client(Some(user_id!("@alice:example.org"))).await;
1899
1900 let mut sync_builder = SyncResponseBuilder::new();
1901 let response = sync_builder
1902 .add_joined_room(matrix_sdk_test::JoinedRoomBuilder::new(room_id))
1903 .build_sync_response();
1904 client.receive_sync_response(response).await.unwrap();
1905
1906 client
1907 }
1908
1909 #[async_test]
1910 async fn test_inactive_members_do_not_make_a_display_name_ambiguous() {
1911 let joined_user_id = user_id!("@bob:example.org");
1912 let left_user_id = user_id!("@carol:example.org");
1913 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1914
1915 let client = base_client_with_joined_room(room_id).await;
1916
1917 let f = EventFactory::new().room(room_id);
1919 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1920 let response = api::membership::get_member_events::v3::Response::new(vec![
1921 f.member(joined_user_id).display_name("Amandine").into_raw(),
1922 f.member(left_user_id)
1923 .display_name("Amandine")
1924 .membership(MembershipState::Leave)
1925 .into_raw(),
1926 ]);
1927
1928 client.receive_all_members(room_id, &request, &response).await.unwrap();
1929
1930 let room = client.get_room(room_id).unwrap();
1931 let member = room.get_member(joined_user_id).await.expect("ok").expect("exists");
1932
1933 assert_eq!(member.display_name().unwrap(), "Amandine");
1934 assert!(!member.name_ambiguous());
1935 }
1936
1937 #[async_test]
1938 async fn test_active_members_make_a_display_name_ambiguous() {
1939 let joined_user_id = user_id!("@bob:example.org");
1940 let invited_user_id = user_id!("@carol:example.org");
1941 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1942
1943 let client = base_client_with_joined_room(room_id).await;
1944
1945 let f = EventFactory::new().room(room_id);
1947 let request = api::membership::get_member_events::v3::Request::new(room_id.to_owned());
1948 let response = api::membership::get_member_events::v3::Response::new(vec![
1949 f.member(joined_user_id).display_name("Amandine").into_raw(),
1950 f.member(invited_user_id)
1951 .display_name("Amandine")
1952 .membership(MembershipState::Invite)
1953 .into_raw(),
1954 ]);
1955
1956 client.receive_all_members(room_id, &request, &response).await.unwrap();
1957
1958 let room = client.get_room(room_id).unwrap();
1960
1961 let joined = room.get_member(joined_user_id).await.expect("ok").expect("exists");
1962 assert!(joined.name_ambiguous());
1963
1964 let invited = room.get_member(invited_user_id).await.expect("ok").expect("exists");
1965 assert!(invited.name_ambiguous());
1966 }
1967
1968 #[cfg(feature = "unstable-msc4426")]
1969 #[async_test]
1970 async fn test_room_member_carries_global_profile_status() {
1971 let user_id = user_id!("@alice:example.org");
1972 let room_id = room_id!("!ithpyNKDtmhneaTQja:example.org");
1973
1974 let client = BaseClient::new(
1975 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
1976 ThreadingSupport::Disabled,
1977 DmRoomDefinition::default(),
1978 );
1979 client
1980 .activate(
1981 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
1982 RoomLoadSettings::default(),
1983 #[cfg(feature = "e2e-encryption")]
1984 None,
1985 )
1986 .await
1987 .unwrap();
1988
1989 let f = EventFactory::new().sender(user_id);
1991 let mut sync_builder = SyncResponseBuilder::new();
1992 let response = sync_builder
1993 .add_joined_room(
1994 matrix_sdk_test::JoinedRoomBuilder::new(room_id).add_state_event(f.member(user_id)),
1995 )
1996 .build_sync_response();
1997 client.receive_sync_response(response).await.unwrap();
1998
1999 let room = client.get_room(room_id).unwrap();
2000
2001 let member = room.get_member(user_id).await.expect("ok").expect("exists");
2003 assert!(member.status().is_none());
2004
2005 let mut changes = StateChanges::default();
2007 changes.global_profiles.insert(user_id.to_owned(), {
2008 let mut profile_changes = UserProfileChanges::new();
2009 profile_changes.insert_updated_value(ProfileFieldValue::Status(
2010 StatusProfileField::new("Working".to_owned(), "💻".to_owned()),
2011 ));
2012 UserProfileUpdate::Updated(profile_changes)
2013 });
2014 client.state_store().save_changes(&changes).await.unwrap();
2015
2016 let member = room.get_member(user_id).await.expect("ok").expect("exists");
2018 let status = member.status().expect("status is set");
2019 assert_eq!(status.text, "Working");
2020 assert_eq!(status.emoji, "💻");
2021
2022 let members = room.members(RoomMemberships::JOIN).await.unwrap();
2024 let member =
2025 members.iter().find(|m| m.user_id() == user_id).expect("member is in the list");
2026 let status = member.status().expect("status is set");
2027 assert_eq!(status.text, "Working");
2028 assert_eq!(status.emoji, "💻");
2029 }
2030
2031 #[async_test]
2032 async fn test_ignored_user_list_changes() {
2033 let user_id = user_id!("@alice:example.org");
2034 let client = BaseClient::new(
2035 StoreConfig::new(CrossProcessLockConfig::SingleProcess),
2036 ThreadingSupport::Disabled,
2037 DmRoomDefinition::default(),
2038 );
2039
2040 client
2041 .activate(
2042 SessionMeta { user_id: user_id.to_owned(), device_id: "FOOBAR".into() },
2043 RoomLoadSettings::default(),
2044 #[cfg(feature = "e2e-encryption")]
2045 None,
2046 )
2047 .await
2048 .unwrap();
2049
2050 let mut subscriber = client.subscribe_to_ignore_user_list_changes();
2051 assert!(subscriber.next().now_or_never().is_none());
2052
2053 let f = EventFactory::new();
2054 let mut sync_builder = SyncResponseBuilder::new();
2055 let response = sync_builder
2056 .add_global_account_data(f.ignored_user_list([User::Bob.into()]))
2057 .build_sync_response();
2058 client.receive_sync_response(response).await.unwrap();
2059
2060 assert_let!(Some(ignored) = subscriber.next().await);
2061 assert_eq!(ignored, [User::Bob]);
2062
2063 let response = sync_builder
2065 .add_global_account_data(f.ignored_user_list([User::Bob.into()]))
2066 .build_sync_response();
2067 client.receive_sync_response(response).await.unwrap();
2068
2069 assert!(subscriber.next().now_or_never().is_none());
2071
2072 let response =
2074 sync_builder.add_global_account_data(f.ignored_user_list([])).build_sync_response();
2075 client.receive_sync_response(response).await.unwrap();
2076
2077 assert_let!(Some(ignored) = subscriber.next().await);
2078 assert!(ignored.is_empty());
2079 }
2080
2081 #[async_test]
2082 async fn test_is_user_ignored() {
2083 let ignored_user_id = user_id!("@alice:example.org");
2084 let client = logged_in_base_client(None).await;
2085
2086 let mut sync_builder = SyncResponseBuilder::new();
2087 let f = EventFactory::new();
2088 let response = sync_builder
2089 .add_global_account_data(f.ignored_user_list([ignored_user_id.to_owned()]))
2090 .build_sync_response();
2091 client.receive_sync_response(response).await.unwrap();
2092
2093 assert!(client.is_user_ignored(ignored_user_id).await);
2094 }
2095
2096 #[cfg(feature = "e2e-encryption")]
2097 #[async_test]
2098 async fn test_invite_details_are_set() {
2099 let user_id = user_id!("@alice:localhost");
2100 let client = logged_in_base_client(Some(user_id)).await;
2101 let known_room_id = room_id!("!invited:localhost");
2102 let unknown_room_id = room_id!("!unknown:localhost");
2103
2104 let mut sync_builder = SyncResponseBuilder::new();
2105 let response = sync_builder
2106 .add_invited_room(InvitedRoomBuilder::new(known_room_id))
2107 .build_sync_response();
2108 client.receive_sync_response(response).await.unwrap();
2109
2110 let invited_room = client
2113 .get_room(known_room_id)
2114 .expect("The sync should have created a room in the invited state");
2115
2116 assert_eq!(invited_room.state(), RoomState::Invited);
2117 assert!(
2118 client.get_pending_key_bundle_details_for_room(known_room_id).await.unwrap().is_none()
2119 );
2120
2121 let joined_room = client
2123 .room_joined(known_room_id, Some(user_id.to_owned()))
2124 .await
2125 .expect("We should be able to mark a room as joined");
2126
2127 assert_eq!(joined_room.state(), RoomState::Joined);
2129 assert_let!(
2130 Ok(Some(details)) = client.get_pending_key_bundle_details_for_room(known_room_id).await
2131 );
2132 assert_eq!(details.inviter, user_id);
2133
2134 assert!(client.get_room(unknown_room_id).is_none());
2137 let unknown_room = client
2138 .room_joined(unknown_room_id, Some(user_id.to_owned()))
2139 .await
2140 .expect("We should be able to mark a room as joined");
2141
2142 assert_eq!(unknown_room.state(), RoomState::Joined);
2143 assert!(
2144 client
2145 .get_pending_key_bundle_details_for_room(unknown_room_id)
2146 .await
2147 .unwrap()
2148 .is_none()
2149 );
2150
2151 sync_builder.clear();
2152 let response =
2153 sync_builder.add_left_room(LeftRoomBuilder::new(known_room_id)).build_sync_response();
2154 client.receive_sync_response(response).await.unwrap();
2155
2156 let left_room = client
2158 .get_room(known_room_id)
2159 .expect("The sync should have created a room in the invited state");
2160
2161 assert_eq!(left_room.state(), RoomState::Left);
2162 assert!(
2163 client.get_pending_key_bundle_details_for_room(known_room_id).await.unwrap().is_none()
2164 );
2165 }
2166}