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