1#![doc = include_str!("README.md")]
17
18mod builder;
19mod cache;
20mod client;
21mod error;
22mod list;
23
24use std::{
25 collections::{BTreeMap, btree_map::Entry},
26 fmt::Debug,
27 future::Future,
28 sync::{Arc, RwLock as StdRwLock},
29 time::Duration,
30};
31
32use async_stream::stream;
33pub use client::{Version, VersionBuilder};
34use futures_core::stream::Stream;
35use matrix_sdk_base::RequestedRequiredStates;
36#[cfg(feature = "e2e-encryption")]
37use matrix_sdk_common::executor::JoinHandleExt as _;
38use matrix_sdk_common::{executor::spawn, timer};
39use ruma::{
40 OwnedRoomId, RoomId,
41 api::{client::sync::sync_events::v5 as http, error::ErrorKind},
42 assign,
43};
44use tokio::{
45 select,
46 sync::{Mutex as AsyncMutex, OwnedMutexGuard, RwLock as AsyncRwLock, broadcast::Sender},
47};
48use tracing::{Instrument, Span, debug, error, info, instrument, trace, warn};
49
50pub use self::{builder::*, client::VersionBuilderError, error::*, list::*};
51use self::{cache::restore_sliding_sync_state, client::SlidingSyncResponseProcessor};
52use crate::{Client, Result, config::RequestConfig};
53
54#[derive(Clone, Debug)]
58pub struct SlidingSync {
59 inner: Arc<SlidingSyncInner>,
61}
62
63#[derive(Debug)]
64pub(super) struct SlidingSyncInner {
65 id: String,
69
70 client: Client,
72
73 poll_timeout: Duration,
75
76 network_timeout: Duration,
79
80 storage_key: String,
82
83 share_pos: bool,
90
91 position: Arc<AsyncMutex<SlidingSyncPositionMarkers>>,
104
105 lists: AsyncRwLock<BTreeMap<String, SlidingSyncList>>,
107
108 room_subscriptions: StdRwLock<BTreeMap<OwnedRoomId, http::request::RoomSubscription>>,
111
112 extensions: http::request::Extensions,
115
116 internal_channel: Sender<SlidingSyncInternalMessage>,
119}
120
121impl SlidingSync {
122 pub(super) fn new(inner: SlidingSyncInner) -> Self {
123 Self { inner: Arc::new(inner) }
124 }
125
126 async fn cache_to_storage(&self, position: &SlidingSyncPositionMarkers) -> Result<()> {
127 cache::store_sliding_sync_state(self, position).await
128 }
129
130 pub fn builder(id: String, client: Client) -> Result<SlidingSyncBuilder, Error> {
132 SlidingSyncBuilder::new(id, client)
133 }
134
135 pub fn subscribe_to_rooms(
144 &self,
145 room_ids: &[&RoomId],
146 settings: Option<http::request::RoomSubscription>,
147 cancel_in_flight_request: bool,
148 ) {
149 let subscriptions_have_changed = add_room_subscriptions(
150 &mut self.inner.room_subscriptions.write().unwrap(),
151 &self.inner.client,
152 room_ids,
153 settings,
154 );
155
156 if cancel_in_flight_request && subscriptions_have_changed {
157 self.inner.cancel_in_flight_request();
158 }
159 }
160
161 pub fn unsubscribe_to_rooms(&self, room_ids: &[&RoomId], cancel_in_flight_request: bool) {
163 let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
164 let mut subscriptions_have_changed = false;
165
166 for room_id in room_ids {
167 if room_subscriptions.remove(*room_id).is_some() {
168 subscriptions_have_changed = true;
169 }
170 }
171
172 if cancel_in_flight_request && subscriptions_have_changed {
173 self.inner.cancel_in_flight_request();
174 }
175 }
176
177 pub fn resubscribe_to_rooms(
195 &self,
196 room_ids: &[&RoomId],
197 settings: Option<http::request::RoomSubscription>,
198 cancel_in_flight_request: bool,
199 ) {
200 let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
201
202 let number_of_subscriptions_before = room_subscriptions.len();
204 room_subscriptions.retain(|room_id, _| room_ids.contains(&room_id.as_ref()));
205 let a_subscription_has_been_removed =
206 room_subscriptions.len() != number_of_subscriptions_before;
207
208 let a_subscription_has_been_added_or_updated =
211 add_room_subscriptions(&mut room_subscriptions, &self.inner.client, room_ids, settings);
212
213 if cancel_in_flight_request
216 && (a_subscription_has_been_added_or_updated || a_subscription_has_been_removed)
217 {
218 self.inner.cancel_in_flight_request();
219 }
220 }
221
222 pub fn clear_and_subscribe_to_rooms(
227 &self,
228 room_ids: &[&RoomId],
229 settings: Option<http::request::RoomSubscription>,
230 cancel_in_flight_request: bool,
231 ) {
232 let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
233 room_subscriptions.clear();
234
235 let subscriptions_have_changed =
236 add_room_subscriptions(&mut room_subscriptions, &self.inner.client, room_ids, settings);
237
238 if cancel_in_flight_request && subscriptions_have_changed {
239 self.inner.cancel_in_flight_request();
240 }
241 }
242
243 pub async fn on_list<Function, FunctionOutput, R>(
245 &self,
246 list_name: &str,
247 function: Function,
248 ) -> Option<R>
249 where
250 Function: FnOnce(&SlidingSyncList) -> FunctionOutput,
251 FunctionOutput: Future<Output = R>,
252 {
253 let lists = self.inner.lists.read().await;
254
255 match lists.get(list_name) {
256 Some(list) => Some(function(list).await),
257 None => None,
258 }
259 }
260
261 pub async fn add_list(
267 &self,
268 list_builder: SlidingSyncListBuilder,
269 ) -> Result<Option<SlidingSyncList>> {
270 let list = list_builder.build(self.inner.internal_channel.clone());
271
272 let old_list = self.inner.lists.write().await.insert(list.name().to_owned(), list);
273
274 self.inner.internal_channel_send_if_possible(
275 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
276 );
277
278 Ok(old_list)
279 }
280
281 pub async fn add_cached_list(
288 &self,
289 mut list_builder: SlidingSyncListBuilder,
290 ) -> Result<Option<SlidingSyncList>> {
291 let _timer = timer!(format!("restoring (loading+processing) list {}", list_builder.name));
292
293 list_builder.set_cached_and_reload(&self.inner.client, &self.inner.storage_key).await?;
294
295 self.add_list(list_builder).await
296 }
297
298 #[instrument(skip_all)]
300 async fn handle_response(
301 &self,
302 mut sliding_sync_response: http::Response,
303 position: &mut SlidingSyncPositionMarkers,
304 requested_required_states: RequestedRequiredStates,
305 ) -> Result<UpdateSummary, crate::Error> {
306 let pos = Some(sliding_sync_response.pos.clone());
307
308 let must_process_rooms_response = self.must_process_rooms_response().await;
309
310 trace!(yes = must_process_rooms_response, "Must process rooms response?");
311
312 let sync_response = {
320 let _timer = timer!("response processor");
321
322 let response_processor = {
323 let state_store_guard = {
326 let _timer = timer!("acquiring the `state_store_lock`");
327
328 self.inner.client.base_client().state_store_lock().lock().await
329 };
330
331 let mut response_processor =
332 SlidingSyncResponseProcessor::new(self.inner.client.clone());
333
334 if self.is_thread_subscriptions_enabled() {
340 response_processor
341 .handle_thread_subscriptions(
342 position.pos.as_deref(),
343 std::mem::take(
344 &mut sliding_sync_response.extensions.thread_subscriptions,
345 ),
346 )
347 .await?;
348 }
349
350 #[cfg(feature = "e2e-encryption")]
351 if self.is_e2ee_enabled() {
352 response_processor
353 .handle_encryption(&sliding_sync_response.extensions, &state_store_guard)
354 .await?
355 }
356
357 if must_process_rooms_response {
360 response_processor
361 .handle_room_response(
362 &sliding_sync_response,
363 &requested_required_states,
364 &state_store_guard,
365 )
366 .await?;
367 }
368
369 response_processor
370 };
371
372 response_processor.process_and_take_response().await?
374 };
375
376 debug!("Sliding Sync response has been handled by the client");
377 trace!(?sync_response);
378
379 let update_summary = {
380 let updated_rooms = {
382 let mut updated_rooms = Vec::with_capacity(
383 sliding_sync_response.rooms.len() + sync_response.rooms.joined.len(),
384 );
385
386 updated_rooms.extend(sliding_sync_response.rooms.keys().cloned());
387
388 updated_rooms.extend(sync_response.rooms.joined.keys().cloned());
396
397 updated_rooms
398 };
399
400 let updated_lists = {
402 debug!(
403 lists = ?sliding_sync_response.lists,
404 "Update lists"
405 );
406
407 let mut updated_lists = Vec::with_capacity(sliding_sync_response.lists.len());
408 let mut lists = self.inner.lists.write().await;
409
410 for (name, list) in lists.iter_mut() {
413 if let Some(updates) = sliding_sync_response.lists.get(name) {
414 let maximum_number_of_rooms: u32 =
415 updates.count.try_into().expect("failed to convert `count` to `u32`");
416
417 if list.update(Some(maximum_number_of_rooms))? {
418 updated_lists.push(name.clone());
419 }
420 } else if list.update(None)? {
421 updated_lists.push(name.clone());
422 }
423 }
424
425 for name in sliding_sync_response.lists.keys() {
427 if !lists.contains_key(name) {
428 error!("Response for list `{name}` - unknown to us; skipping");
429 }
430 }
431
432 updated_lists
433 };
434
435 UpdateSummary { lists: updated_lists, rooms: updated_rooms }
436 };
437
438 debug!(previous_pos = position.pos, new_pos = pos, "Updating `pos`");
442
443 position.pos = pos;
444
445 Ok(update_summary)
446 }
447
448 async fn generate_sync_request(
449 &self,
450 ) -> Result<(http::Request, RequestConfig, OwnedMutexGuard<SlidingSyncPositionMarkers>)> {
451 let mut requests_lists = BTreeMap::new();
453
454 let timeout = {
455 let lists = self.inner.lists.read().await;
456
457 let mut timeout = PollTimeout::Default;
459
460 for (name, list) in lists.iter() {
461 requests_lists.insert(name.clone(), list.next_request()?);
462 timeout = timeout.min(list.requires_timeout());
463 }
464
465 timeout
466 };
467
468 let mut position_guard = {
476 debug!("Waiting to acquire the `position` lock");
477
478 let _timer = timer!("acquiring the `position` lock");
479
480 self.inner.position.clone().lock_owned().await
481 };
482
483 debug!(pos = ?position_guard.pos, "Got a position");
484
485 let to_device_enabled = self.inner.extensions.to_device.enabled == Some(true);
486
487 let restored_fields = if self.inner.share_pos || to_device_enabled {
488 restore_sliding_sync_state(&self.inner.client, &self.inner.storage_key).await?
489 } else {
490 None
491 };
492
493 let pos = if self.inner.share_pos {
496 if let Some(fields) = &restored_fields {
497 if fields.pos != position_guard.pos {
499 info!(
500 "Pos from previous request ('{:?}') was different from \
501 pos in database ('{:?}').",
502 position_guard.pos, fields.pos
503 );
504 position_guard.pos = fields.pos.clone();
505 }
506 fields.pos.clone()
507 } else {
508 position_guard.pos.clone()
509 }
510 } else {
511 position_guard.pos.clone()
512 };
513
514 #[cfg(feature = "e2e-encryption")]
523 if pos.is_none() && self.is_e2ee_enabled() {
524 info!("Marking all tracked users as dirty");
525
526 let olm_machine = self.inner.client.olm_machine().await;
527 let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
528 olm_machine.mark_all_tracked_users_as_dirty().await?;
529 }
530
531 let timeout = match timeout {
536 PollTimeout::None => None,
537 PollTimeout::Some(timeout) => Some(Duration::from_secs(timeout.into())),
538 PollTimeout::Default => Some(self.inner.poll_timeout),
539 };
540
541 Span::current()
542 .record("pos", &pos)
543 .record("timeout", timeout.map(|duration| duration.as_millis()));
544
545 let mut request = assign!(http::Request::new(), {
546 conn_id: Some(self.inner.id.clone()),
547 pos,
548 set_presence: self.inner.client.sync_presence(),
549 timeout,
550 lists: requests_lists,
551 });
552
553 request.room_subscriptions = self.inner.room_subscriptions.read().unwrap().clone();
555
556 request.extensions = self.inner.extensions.clone();
558
559 if to_device_enabled {
561 request.extensions.to_device.since =
562 restored_fields.and_then(|fields| fields.to_device_token);
563 }
564
565 Ok((
566 request,
568 RequestConfig::default()
571 .timeout(self.inner.poll_timeout + self.inner.network_timeout)
572 .retry_limit(3),
573 position_guard,
574 ))
575 }
576
577 async fn send_sync_request(
581 &self,
582 request: http::Request,
583 request_config: RequestConfig,
584 mut position_guard: OwnedMutexGuard<SlidingSyncPositionMarkers>,
585 ) -> Result<UpdateSummary> {
586 debug!("Sending request");
587
588 let requested_required_states = RequestedRequiredStates::from(&request);
590 let request = self.inner.client.send(request).with_request_config(request_config);
591
592 #[cfg(feature = "e2e-encryption")]
599 let response = {
600 if self.is_e2ee_enabled() {
601 let client = self.inner.client.clone();
618 let e2ee_uploads = spawn(
619 async move {
620 if let Err(error) = client.send_outgoing_requests().await {
621 error!(?error, "Error while sending outgoing E2EE requests");
622 }
623 }
624 .instrument(Span::current()),
625 )
626 .abort_on_drop();
629
630 let response = request.await?;
632
633 e2ee_uploads.await.map_err(|error| Error::JoinError {
638 task_description: "e2ee_uploads".to_owned(),
639 error,
640 })?;
641
642 response
643 } else {
644 request.await?
645 }
646 };
647
648 #[cfg(not(feature = "e2e-encryption"))]
650 let response = request.await?;
651
652 debug!("Received response");
653
654 let this = self.clone();
664
665 let future = async move {
668 debug!("Start handling response");
669
670 let updates = this
676 .handle_response(response, &mut position_guard, requested_required_states)
677 .await?;
678
679 this.cache_to_storage(&position_guard).await?;
680
681 drop(position_guard);
684
685 debug!("Done handling response");
686
687 Ok(updates)
688 };
689
690 spawn(future.instrument(Span::current())).await.map_err(|error| Error::JoinError {
691 task_description: "handle_response".to_owned(),
692 error,
693 })?
694 }
695
696 #[cfg(feature = "e2e-encryption")]
698 fn is_e2ee_enabled(&self) -> bool {
699 self.inner.extensions.e2ee.enabled == Some(true)
700 }
701
702 fn is_thread_subscriptions_enabled(&self) -> bool {
705 self.inner.extensions.thread_subscriptions.enabled == Some(true)
706 }
707
708 #[cfg(not(feature = "e2e-encryption"))]
709 fn is_e2ee_enabled(&self) -> bool {
710 false
711 }
712
713 async fn must_process_rooms_response(&self) -> bool {
715 !self.inner.room_subscriptions.read().unwrap().is_empty()
718 || !self.inner.lists.read().await.is_empty()
719 }
720
721 #[doc(hidden)]
725 #[instrument(skip_all, fields(conn_id = self.inner.id, pos, timeout))]
726 pub async fn sync_once(&self) -> Result<UpdateSummary> {
727 let (request, request_config, position_guard) = self.generate_sync_request().await?;
728
729 let summaries = self.send_sync_request(request, request_config, position_guard).await?;
731
732 self.inner.client.inner.sync_beat.notify(usize::MAX);
734
735 Ok(summaries)
736 }
737
738 #[allow(unknown_lints, clippy::let_with_type_underscore)] #[instrument(name = "sync_stream", skip_all, fields(conn_id = self.inner.id, with_e2ee = self.is_e2ee_enabled()))]
748 pub fn sync(&self) -> impl Stream<Item = Result<UpdateSummary, crate::Error>> + '_ {
749 debug!("Starting sync stream");
750
751 let mut internal_channel_receiver = self.inner.internal_channel.subscribe();
752
753 stream! {
754 loop {
755 debug!("Sync stream is running");
756
757 select! {
758 biased;
759
760 internal_message = internal_channel_receiver.recv() => {
761 use SlidingSyncInternalMessage::*;
762
763 debug!(?internal_message, "Sync stream has received an internal message");
764
765 match internal_message {
766 Err(_) | Ok(SyncLoopStop) => {
767 break;
768 }
769
770 Ok(SyncLoopSkipOverCurrentIteration) => {
771 continue;
772 }
773 }
774 }
775
776 update_summary = self.sync_once() => {
777 match update_summary {
778 Ok(updates) => {
779 yield Ok(updates);
780 }
781
782 Err(error) => {
784 if error.client_api_error_kind() == Some(&ErrorKind::UnknownPos) {
785 self.expire_session().await;
787 }
788
789 yield Err(error);
790
791 break;
793 }
794 }
795 }
796 }
797 }
798
799 debug!("Sync stream has exited.");
800 }
801 }
802
803 pub fn stop_sync(&self) -> Result<()> {
812 Ok(self.inner.internal_channel_send(SlidingSyncInternalMessage::SyncLoopStop)?)
813 }
814
815 #[doc(hidden)]
826 pub async fn expire_session(&self) {
827 info!("Session expired; resetting `pos`");
828
829 {
830 let lists = self.inner.lists.read().await;
831
832 for list in lists.values() {
833 list.set_maximum_number_of_rooms(None);
835 }
836 }
837
838 {
840 let mut position = self.inner.position.lock().await;
841
842 position.pos = None;
844
845 if let Err(err) = self.cache_to_storage(&position).await {
849 warn!("Failed to invalidate cached sliding sync state: {err}");
850 }
851 }
852
853 {
854 self.inner.room_subscriptions.write().unwrap().clear();
857 }
858 }
859}
860
861fn add_room_subscriptions(
870 room_subscriptions: &mut BTreeMap<OwnedRoomId, http::request::RoomSubscription>,
871 client: &Client,
872 room_ids: &[&RoomId],
873 settings: Option<http::request::RoomSubscription>,
874) -> bool {
875 let settings = settings.unwrap_or_default();
876 let mut subscriptions_have_changed = false;
877
878 for room_id in room_ids {
879 match room_subscriptions.entry((*room_id).to_owned()) {
880 Entry::Vacant(entry) => {
881 if let Some(room) = client.get_room(room_id) {
882 room.mark_members_missing();
883 }
884
885 entry.insert(settings.clone());
886
887 subscriptions_have_changed = true;
888 }
889
890 Entry::Occupied(mut entry) => {
893 if room_subscriptions_differ(entry.get(), &settings) {
894 entry.insert(settings.clone());
895
896 subscriptions_have_changed = true;
897 }
898 }
899 }
900 }
901
902 subscriptions_have_changed
903}
904
905fn room_subscriptions_differ(
907 left: &http::request::RoomSubscription,
908 right: &http::request::RoomSubscription,
909) -> bool {
910 left.timeline_limit != right.timeline_limit || left.required_state != right.required_state
911}
912
913impl SlidingSyncInner {
914 #[instrument]
916 fn internal_channel_send(&self, message: SlidingSyncInternalMessage) -> Result<(), Error> {
917 self.internal_channel.send(message).map(|_| ()).map_err(|_| Error::InternalChannelIsBroken)
918 }
919
920 #[instrument]
923 fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
924 let _ = self.internal_channel.send(message);
926 }
927
928 fn cancel_in_flight_request(&self) {
931 self.internal_channel_send_if_possible(
932 SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
933 );
934 }
935}
936
937#[derive(Copy, Clone, Debug, PartialEq)]
938enum SlidingSyncInternalMessage {
939 SyncLoopStop,
941
942 SyncLoopSkipOverCurrentIteration,
945}
946
947#[cfg(any(test, feature = "testing"))]
948impl SlidingSync {
949 pub async fn set_pos(&self, new_pos: String) {
951 let mut position_lock = self.inner.position.lock().await;
952 position_lock.pos = Some(new_pos);
953 }
954}
955
956#[derive(Clone, Debug)]
957pub(super) struct SlidingSyncPositionMarkers {
958 pos: Option<String>,
961}
962
963#[derive(Debug, Clone)]
966pub struct UpdateSummary {
967 pub lists: Vec<String>,
969 pub rooms: Vec<OwnedRoomId>,
971}
972
973#[derive(Debug)]
982pub enum PollTimeout {
983 None,
985
986 Some(u32),
989
990 Default,
993}
994
995impl PollTimeout {
996 fn min(self, left: Self) -> Self {
1007 match (self, left) {
1008 (Self::None, _) => Self::None,
1009
1010 (Self::Some(_), Self::None) => Self::None,
1011 (Self::Some(right), Self::Some(left)) => Self::Some(right.min(left)),
1012 (Self::Some(right), Self::Default) => Self::Some(right),
1013
1014 (Self::Default, Self::None) => Self::None,
1015 (Self::Default, Self::Some(left)) => Self::Some(left),
1016 (Self::Default, Self::Default) => Self::Default,
1017 }
1018 }
1019}
1020
1021#[cfg(all(test, not(target_family = "wasm")))]
1022#[allow(clippy::dbg_macro)]
1023mod tests {
1024 use std::{
1025 collections::BTreeMap,
1026 future::ready,
1027 ops::Not,
1028 sync::{Arc, Mutex},
1029 time::Duration,
1030 };
1031
1032 use assert_matches::assert_matches;
1033 use event_listener::Listener;
1034 use futures_util::{StreamExt, future::join_all, pin_mut};
1035 use matrix_sdk_base::{RequestedRequiredStates, RoomMemberships};
1036 use matrix_sdk_common::executor::spawn;
1037 use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};
1038 use ruma::{
1039 OwnedRoomId, assign,
1040 events::{direct::DirectEvent, room::member::MembershipState},
1041 owned_room_id,
1042 presence::PresenceState,
1043 profile::{
1044 AvatarUrl, DisplayName, ProfileFieldName, UserProfileChanges, UserProfileUpdate,
1045 },
1046 room_id,
1047 serde::Raw,
1048 uint,
1049 };
1050 use serde::Deserialize;
1051 use serde_json::json;
1052 use stream_assert::assert_pending;
1053 use wiremock::{
1054 Match, Mock, MockServer, Request, ResponseTemplate, http::Method, matchers::method,
1055 };
1056
1057 use super::{
1058 SlidingSync, SlidingSyncBuilder, SlidingSyncInternalMessage, SlidingSyncList,
1059 SlidingSyncListBuilder, SlidingSyncMode, cache::restore_sliding_sync_state, http,
1060 };
1061 use crate::{
1062 Client, Result,
1063 test_utils::{logged_in_client, mocks::MatrixMockServer},
1064 };
1065
1066 #[derive(Copy, Clone)]
1067 struct SlidingSyncMatcher;
1068
1069 impl Match for SlidingSyncMatcher {
1070 fn matches(&self, request: &Request) -> bool {
1071 request.url.path() == "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync"
1072 && request.method == Method::POST
1073 }
1074 }
1075
1076 async fn new_sliding_sync(
1077 lists: Vec<SlidingSyncListBuilder>,
1078 ) -> Result<(MockServer, SlidingSync)> {
1079 let server = MockServer::start().await;
1080 let client = logged_in_client(Some(server.uri())).await;
1081
1082 let mut sliding_sync_builder = client.sliding_sync("test-slidingsync")?;
1083
1084 for list in lists {
1085 sliding_sync_builder = sliding_sync_builder.add_list(list);
1086 }
1087
1088 let sliding_sync = sliding_sync_builder.build().await?;
1089
1090 Ok((server, sliding_sync))
1091 }
1092
1093 #[async_test]
1094 async fn test_subscribe_to_own_profile() {
1095 let client = logged_in_client(None).await;
1096 let own_user_id = client.user_id().expect("client should be logged in").to_owned();
1097
1098 let mut response = http::Response::new("0".to_owned());
1101
1102 let mut profile_changes = UserProfileChanges::new();
1103 profile_changes.updated.insert(ProfileFieldName::DisplayName, json!("Example"));
1104
1105 response
1106 .extensions
1107 .profiles
1108 .users
1109 .insert(own_user_id.clone(), UserProfileUpdate::Updated(profile_changes));
1110 client
1111 .process_sliding_sync_test_helper(&response, &RequestedRequiredStates::default())
1112 .await
1113 .expect("Failed to process sync");
1114
1115 let stream = client.subscribe_to_own_profile().expect("client should be logged in");
1117 pin_mut!(stream);
1118
1119 let profile = stream.next().await.expect("should emit the initial profile");
1120 assert_eq!(profile.get_static::<DisplayName>().unwrap().as_deref(), Some("Example"));
1121
1122 let mut response = http::Response::new("1".to_owned());
1124
1125 let mut profile_changes = UserProfileChanges::new();
1126 profile_changes.updated.insert(ProfileFieldName::DisplayName, json!("Alice"));
1127 response
1128 .extensions
1129 .profiles
1130 .users
1131 .insert(ALICE.to_owned(), UserProfileUpdate::Updated(profile_changes));
1132 client
1133 .process_sliding_sync_test_helper(&response, &RequestedRequiredStates::default())
1134 .await
1135 .expect("Failed to process sync");
1136
1137 assert_pending!(stream);
1138
1139 let mut response = http::Response::new("2".to_owned());
1141
1142 let mut profile_changes = UserProfileChanges::new();
1143 profile_changes
1144 .updated
1145 .insert(ProfileFieldName::AvatarUrl, json!("mxc://example.org/avatar"));
1146
1147 response
1148 .extensions
1149 .profiles
1150 .users
1151 .insert(own_user_id.clone(), UserProfileUpdate::Updated(profile_changes));
1152 client
1153 .process_sliding_sync_test_helper(&response, &RequestedRequiredStates::default())
1154 .await
1155 .expect("Failed to process sync");
1156
1157 let profile = stream.next().await.expect("should emit the updated profile");
1158 assert_eq!(profile.get_static::<DisplayName>().unwrap().as_deref(), Some("Example"));
1159 assert_eq!(
1160 profile.get_static::<AvatarUrl>().unwrap().map(|url| url.to_string()).as_deref(),
1161 Some("mxc://example.org/avatar")
1162 );
1163 }
1164
1165 #[async_test]
1166 async fn test_sliding_sync_request_uses_client_sync_presence() -> Result<()> {
1167 let (_server, sliding_sync) = new_sliding_sync(vec![]).await?;
1168 let client = sliding_sync.inner.client.clone();
1169
1170 {
1171 let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1172
1173 assert_eq!(request.set_presence, PresenceState::Online);
1174 }
1175
1176 client.set_presence(PresenceState::Unavailable, None, false).await?;
1177
1178 {
1179 let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1180
1181 assert_eq!(request.set_presence, PresenceState::Unavailable);
1182 }
1183
1184 client.set_presence(PresenceState::Offline, None, false).await?;
1185
1186 {
1187 let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1188
1189 assert_eq!(request.set_presence, PresenceState::Offline);
1190 }
1191
1192 Ok(())
1193 }
1194
1195 #[async_test]
1196 async fn test_subscribe_to_rooms() -> Result<()> {
1197 let (server, sliding_sync) = new_sliding_sync(vec![
1198 SlidingSyncList::builder("foo")
1199 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1200 ])
1201 .await?;
1202
1203 let stream = sliding_sync.sync();
1204 pin_mut!(stream);
1205
1206 let room_id_0 = room_id!("!r0:bar.org");
1207 let room_id_1 = room_id!("!r1:bar.org");
1208 let room_id_2 = room_id!("!r2:bar.org");
1209
1210 {
1211 let _mock_guard = Mock::given(SlidingSyncMatcher)
1212 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1213 "pos": "1",
1214 "lists": {},
1215 "rooms": {
1216 room_id_0: {
1217 "name": "Room #0",
1218 "initial": true,
1219 },
1220 room_id_1: {
1221 "name": "Room #1",
1222 "initial": true,
1223 },
1224 room_id_2: {
1225 "name": "Room #2",
1226 "initial": true,
1227 },
1228 }
1229 })))
1230 .mount_as_scoped(&server)
1231 .await;
1232
1233 let _ = stream.next().await.unwrap()?;
1234 }
1235
1236 let room0 = sliding_sync.inner.client.get_room(room_id_0).unwrap();
1237
1238 assert!(room0.are_members_synced().not());
1242
1243 {
1244 struct MemberMatcher(OwnedRoomId);
1245
1246 impl Match for MemberMatcher {
1247 fn matches(&self, request: &Request) -> bool {
1248 request.url.path()
1249 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1250 && request.method == Method::GET
1251 }
1252 }
1253
1254 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1255 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1256 "chunk": [],
1257 })))
1258 .mount_as_scoped(&server)
1259 .await;
1260
1261 assert_matches!(room0.request_members().await, Ok(()));
1262 }
1263
1264 assert!(room0.are_members_synced());
1266
1267 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], None, true);
1268
1269 assert!(room0.are_members_synced().not());
1272
1273 {
1274 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1275
1276 assert!(room_subscriptions.contains_key(room_id_0));
1277 assert!(room_subscriptions.contains_key(room_id_1));
1278 assert!(!room_subscriptions.contains_key(room_id_2));
1279 }
1280
1281 {
1284 struct MemberMatcher(OwnedRoomId);
1285
1286 impl Match for MemberMatcher {
1287 fn matches(&self, request: &Request) -> bool {
1288 request.url.path()
1289 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1290 && request.method == Method::GET
1291 }
1292 }
1293
1294 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1295 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1296 "chunk": [],
1297 })))
1298 .mount_as_scoped(&server)
1299 .await;
1300
1301 assert_matches!(room0.request_members().await, Ok(()));
1302 }
1303
1304 assert!(room0.are_members_synced());
1306
1307 sliding_sync.subscribe_to_rooms(&[room_id_0], None, false);
1308
1309 assert!(room0.are_members_synced());
1312
1313 Ok(())
1314 }
1315
1316 #[async_test]
1317 async fn test_subscribe_unsubscribe_and_clear_and_subscribe_to_rooms() -> Result<()> {
1318 let (_server, sliding_sync) = new_sliding_sync(vec![
1319 SlidingSyncList::builder("foo")
1320 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1321 ])
1322 .await?;
1323
1324 let room_id_0 = room_id!("!r0:bar.org");
1325 let room_id_1 = room_id!("!r1:bar.org");
1326 let room_id_2 = room_id!("!r2:bar.org");
1327 let room_id_3 = room_id!("!r3:bar.org");
1328
1329 {
1331 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1332
1333 assert!(room_subscriptions.is_empty());
1334 }
1335
1336 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], Default::default(), false);
1338
1339 {
1340 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1341
1342 assert_eq!(room_subscriptions.len(), 2);
1343 assert!(room_subscriptions.contains_key(room_id_0));
1344 assert!(room_subscriptions.contains_key(room_id_1));
1345 }
1346
1347 sliding_sync.unsubscribe_to_rooms(&[room_id_0], false);
1349
1350 {
1351 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1352
1353 assert_eq!(room_subscriptions.len(), 1);
1354 assert!(room_subscriptions.contains_key(room_id_1));
1355 }
1356
1357 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], Default::default(), false);
1359
1360 {
1361 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1362
1363 assert_eq!(room_subscriptions.len(), 2);
1364 assert!(room_subscriptions.contains_key(room_id_0));
1365 assert!(room_subscriptions.contains_key(room_id_1));
1366 }
1367
1368 sliding_sync.clear_and_subscribe_to_rooms(
1370 &[room_id_2, room_id_3],
1371 Default::default(),
1372 false,
1373 );
1374
1375 {
1376 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1377
1378 assert_eq!(room_subscriptions.len(), 2);
1379 assert!(room_subscriptions.contains_key(room_id_2));
1380 assert!(room_subscriptions.contains_key(room_id_3));
1381 }
1382
1383 Ok(())
1384 }
1385
1386 #[async_test]
1387 async fn test_resubscribe_to_rooms_refreshes_the_settings() -> Result<()> {
1388 let (_server, sliding_sync) = new_sliding_sync(vec![
1389 SlidingSyncList::builder("foo")
1390 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1391 ])
1392 .await?;
1393
1394 let room_id_0 = room_id!("!r0:bar.org");
1395
1396 let settings = |timeline_limit: u32| {
1397 Some(assign!(http::request::RoomSubscription::default(), {
1398 timeline_limit: timeline_limit.into(),
1399 }))
1400 };
1401 let timeline_limit_of_room_0 = || {
1402 sliding_sync
1403 .inner
1404 .room_subscriptions
1405 .read()
1406 .unwrap()
1407 .get(room_id_0)
1408 .map(|subscription| subscription.timeline_limit)
1409 };
1410
1411 let mut internal_channel = sliding_sync.inner.internal_channel.subscribe();
1412
1413 sliding_sync.resubscribe_to_rooms(&[room_id_0], settings(10), true);
1415
1416 assert_eq!(timeline_limit_of_room_0(), Some(10u32.into()));
1417 assert_matches!(
1418 internal_channel.try_recv(),
1419 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1420 );
1421
1422 sliding_sync.resubscribe_to_rooms(&[room_id_0], settings(10), true);
1424
1425 assert_eq!(timeline_limit_of_room_0(), Some(10u32.into()));
1426 assert!(internal_channel.try_recv().is_err());
1427
1428 sliding_sync.resubscribe_to_rooms(&[room_id_0], settings(42), true);
1431
1432 assert_eq!(timeline_limit_of_room_0(), Some(42u32.into()));
1433 assert_matches!(
1434 internal_channel.try_recv(),
1435 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1436 );
1437
1438 Ok(())
1439 }
1440
1441 #[async_test]
1442 async fn test_resubscribe_to_rooms_cancels_the_in_flight_request() -> Result<()> {
1443 let (_server, sliding_sync) = new_sliding_sync(vec![
1444 SlidingSyncList::builder("foo")
1445 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1446 ])
1447 .await?;
1448
1449 let room_id_0 = room_id!("!r0:bar.org");
1450 let room_id_1 = room_id!("!r1:bar.org");
1451
1452 let mut internal_channel = sliding_sync.inner.internal_channel.subscribe();
1453
1454 sliding_sync.resubscribe_to_rooms(&[room_id_0], None, true);
1457
1458 assert_matches!(
1459 internal_channel.try_recv(),
1460 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1461 );
1462
1463 sliding_sync.resubscribe_to_rooms(&[room_id_0], None, true);
1466
1467 assert!(internal_channel.try_recv().is_err());
1468
1469 sliding_sync.resubscribe_to_rooms(&[room_id_1], None, true);
1471
1472 assert_matches!(
1473 internal_channel.try_recv(),
1474 Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1475 );
1476
1477 sliding_sync.resubscribe_to_rooms(&[room_id_0], None, false);
1479
1480 assert!(internal_channel.try_recv().is_err());
1481
1482 Ok(())
1483 }
1484
1485 #[async_test]
1486 async fn test_resubscribe_to_rooms() -> Result<()> {
1487 let (server, sliding_sync) = new_sliding_sync(vec![
1488 SlidingSyncList::builder("foo")
1489 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1490 ])
1491 .await?;
1492
1493 let stream = sliding_sync.sync();
1494 pin_mut!(stream);
1495
1496 let room_id_0 = room_id!("!r0:bar.org");
1497 let room_id_1 = room_id!("!r1:bar.org");
1498 let room_id_2 = room_id!("!r2:bar.org");
1499
1500 {
1501 let _mock_guard = Mock::given(SlidingSyncMatcher)
1502 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1503 "pos": "1",
1504 "lists": {},
1505 "rooms": {
1506 room_id_0: {
1507 "name": "Room #0",
1508 "initial": true,
1509 },
1510 room_id_1: {
1511 "name": "Room #1",
1512 "initial": true,
1513 },
1514 room_id_2: {
1515 "name": "Room #2",
1516 "initial": true,
1517 },
1518 }
1519 })))
1520 .mount_as_scoped(&server)
1521 .await;
1522
1523 let _ = stream.next().await.unwrap()?;
1524 }
1525
1526 let room0 = sliding_sync.inner.client.get_room(room_id_0).unwrap();
1527
1528 assert!(room0.are_members_synced().not());
1532
1533 {
1534 struct MemberMatcher(OwnedRoomId);
1535
1536 impl Match for MemberMatcher {
1537 fn matches(&self, request: &Request) -> bool {
1538 request.url.path()
1539 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1540 && request.method == Method::GET
1541 }
1542 }
1543
1544 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1545 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1546 "chunk": [],
1547 })))
1548 .mount_as_scoped(&server)
1549 .await;
1550
1551 assert_matches!(room0.request_members().await, Ok(()));
1552 }
1553
1554 assert!(room0.are_members_synced());
1556
1557 sliding_sync.resubscribe_to_rooms(&[room_id_0, room_id_1], None, true);
1558
1559 assert!(room0.are_members_synced().not());
1562
1563 {
1565 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1566
1567 assert!(room_subscriptions.contains_key(room_id_0));
1568 assert!(room_subscriptions.contains_key(room_id_1));
1569 assert!(!room_subscriptions.contains_key(room_id_2));
1570 }
1571
1572 {
1575 struct MemberMatcher(OwnedRoomId);
1576
1577 impl Match for MemberMatcher {
1578 fn matches(&self, request: &Request) -> bool {
1579 request.url.path()
1580 == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1581 && request.method == Method::GET
1582 }
1583 }
1584
1585 let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1586 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1587 "chunk": [],
1588 })))
1589 .mount_as_scoped(&server)
1590 .await;
1591
1592 assert_matches!(room0.request_members().await, Ok(()));
1593 }
1594
1595 assert!(room0.are_members_synced());
1597
1598 sliding_sync.resubscribe_to_rooms(&[room_id_0], None, false);
1599
1600 assert!(room0.are_members_synced());
1603
1604 {
1606 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1607
1608 assert!(room_subscriptions.contains_key(room_id_0));
1609 assert!(!room_subscriptions.contains_key(room_id_1));
1610 assert!(!room_subscriptions.contains_key(room_id_2));
1611 }
1612 Ok(())
1613 }
1614
1615 #[async_test]
1616 async fn test_room_subscriptions_are_reset_when_session_expires() -> Result<()> {
1617 let (_server, sliding_sync) = new_sliding_sync(vec![
1618 SlidingSyncList::builder("foo")
1619 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1620 ])
1621 .await?;
1622
1623 let room_id_0 = room_id!("!r0:bar.org");
1624 let room_id_1 = room_id!("!r1:bar.org");
1625 let room_id_2 = room_id!("!r2:bar.org");
1626
1627 sliding_sync.subscribe_to_rooms(&[room_id_0, room_id_1], None, false);
1629
1630 {
1631 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1632
1633 assert!(room_subscriptions.contains_key(room_id_0));
1634 assert!(room_subscriptions.contains_key(room_id_1));
1635 assert!(room_subscriptions.contains_key(room_id_2).not());
1636 }
1637
1638 sliding_sync.subscribe_to_rooms(&[room_id_2], None, false);
1640
1641 {
1642 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1643
1644 assert!(room_subscriptions.contains_key(room_id_0));
1645 assert!(room_subscriptions.contains_key(room_id_1));
1646 assert!(room_subscriptions.contains_key(room_id_2));
1647 }
1648
1649 sliding_sync.expire_session().await;
1651
1652 {
1653 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1654
1655 assert!(room_subscriptions.is_empty());
1656 }
1657
1658 sliding_sync.subscribe_to_rooms(&[room_id_2], None, false);
1660
1661 {
1662 let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1663
1664 assert!(room_subscriptions.contains_key(room_id_0).not());
1665 assert!(room_subscriptions.contains_key(room_id_1).not());
1666 assert!(room_subscriptions.contains_key(room_id_2));
1667 }
1668
1669 Ok(())
1670 }
1671
1672 #[async_test]
1673 async fn test_add_list() -> Result<()> {
1674 let (_server, sliding_sync) = new_sliding_sync(vec![
1675 SlidingSyncList::builder("foo")
1676 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1677 ])
1678 .await?;
1679
1680 let _stream = sliding_sync.sync();
1681 pin_mut!(_stream);
1682
1683 sliding_sync
1684 .add_list(
1685 SlidingSyncList::builder("bar")
1686 .sync_mode(SlidingSyncMode::new_selective().add_range(50..=60)),
1687 )
1688 .await?;
1689
1690 let lists = sliding_sync.inner.lists.read().await;
1691
1692 assert!(lists.contains_key("foo"));
1693 assert!(lists.contains_key("bar"));
1694
1695 Ok(())
1698 }
1699
1700 #[cfg(feature = "e2e-encryption")]
1701 #[async_test]
1702 async fn test_extensions_to_device_since_is_set() {
1703 use matrix_sdk_base::crypto::store::types::Changes;
1704
1705 let client = logged_in_client(None).await;
1706 let sliding_sync = SlidingSyncBuilder::new("foo".to_owned(), client.clone())
1707 .unwrap()
1708 .with_to_device_extension(assign!(
1709 http::request::ToDevice::default(),
1710 {
1711 enabled: Some(true),
1712 }
1713 ))
1714 .build()
1715 .await
1716 .unwrap();
1717
1718 {
1720 let to_device = &sliding_sync.inner.extensions.to_device;
1721
1722 assert_eq!(to_device.enabled, Some(true));
1723 assert!(to_device.since.is_none());
1724 }
1725
1726 {
1728 let (request, _, _) = sliding_sync.generate_sync_request().await.unwrap();
1729
1730 let to_device = &request.extensions.to_device;
1731
1732 assert_eq!(to_device.enabled, Some(true));
1733 assert!(to_device.since.is_none());
1734 }
1735
1736 let since_token = "depuis".to_owned();
1738
1739 {
1740 if let Some(olm_machine) = &*client.olm_machine().await {
1741 olm_machine
1742 .store()
1743 .save_changes(Changes {
1744 next_batch_token: Some(since_token.clone()),
1745 ..Default::default()
1746 })
1747 .await
1748 .unwrap();
1749 } else {
1750 panic!("Where is the Olm machine?");
1751 }
1752 }
1753
1754 {
1756 let (request, _, _) = sliding_sync.generate_sync_request().await.unwrap();
1757
1758 let to_device = &request.extensions.to_device;
1759
1760 assert_eq!(to_device.enabled, Some(true));
1761 assert_eq!(to_device.since, Some(since_token));
1762 }
1763 }
1764
1765 #[async_test]
1771 #[cfg(feature = "e2e-encryption")]
1772 async fn test_no_pos_with_e2ee_marks_all_tracked_users_as_dirty() -> anyhow::Result<()> {
1773 use matrix_sdk_base::crypto::types::requests::{AnyIncomingResponse, AnyOutgoingRequest};
1774 use matrix_sdk_test::ruma_response_from_json;
1775 use ruma::user_id;
1776
1777 let server = MockServer::start().await;
1778 let client = logged_in_client(Some(server.uri())).await;
1779
1780 let alice = user_id!("@alice:localhost");
1781 let bob = user_id!("@bob:localhost");
1782 let me = user_id!("@example:localhost");
1783
1784 {
1787 let olm_machine = client.olm_machine().await;
1788 let olm_machine = olm_machine.as_ref().unwrap();
1789
1790 olm_machine.update_tracked_users([alice, bob]).await?;
1791
1792 let outgoing_requests = olm_machine.outgoing_requests().await?;
1794
1795 assert_eq!(outgoing_requests.len(), 2);
1796 assert_matches!(outgoing_requests[0].request(), AnyOutgoingRequest::KeysUpload(_));
1797 assert_matches!(outgoing_requests[1].request(), AnyOutgoingRequest::KeysQuery(_));
1798
1799 olm_machine
1801 .mark_request_as_sent(
1802 outgoing_requests[0].request_id(),
1803 AnyIncomingResponse::KeysUpload(&ruma_response_from_json(&json!({
1804 "one_time_key_counts": {}
1805 }))),
1806 )
1807 .await?;
1808
1809 olm_machine
1810 .mark_request_as_sent(
1811 outgoing_requests[1].request_id(),
1812 AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1813 "device_keys": {
1814 alice: {},
1815 bob: {},
1816 }
1817 }))),
1818 )
1819 .await?;
1820
1821 let outgoing_requests = olm_machine.outgoing_requests().await?;
1823
1824 assert_eq!(outgoing_requests.len(), 1);
1825 assert_matches!(outgoing_requests[0].request(), AnyOutgoingRequest::KeysQuery(_));
1826
1827 olm_machine
1828 .mark_request_as_sent(
1829 outgoing_requests[0].request_id(),
1830 AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1831 "device_keys": {
1832 me: {},
1833 }
1834 }))),
1835 )
1836 .await?;
1837
1838 let outgoing_requests = olm_machine.outgoing_requests().await?;
1840
1841 assert!(outgoing_requests.is_empty());
1842 }
1843
1844 let sync = client
1845 .sliding_sync("test-slidingsync")?
1846 .add_list(SlidingSyncList::builder("new_list"))
1847 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
1848 .build()
1849 .await?;
1850
1851 let (_request, _, _) = sync.generate_sync_request().await?;
1853
1854 {
1856 let olm_machine = client.olm_machine().await;
1857 let olm_machine = olm_machine.as_ref().unwrap();
1858
1859 let outgoing_requests = olm_machine.outgoing_requests().await?;
1861
1862 assert_eq!(outgoing_requests.len(), 1);
1863 assert_matches!(
1864 outgoing_requests[0].request(),
1865 AnyOutgoingRequest::KeysQuery(request) => {
1866 assert!(request.device_keys.contains_key(alice));
1867 assert!(request.device_keys.contains_key(bob));
1868 assert!(request.device_keys.contains_key(me));
1869 }
1870 );
1871
1872 olm_machine
1874 .mark_request_as_sent(
1875 outgoing_requests[0].request_id(),
1876 AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1877 "device_keys": {
1878 alice: {},
1879 bob: {},
1880 me: {},
1881 }
1882 }))),
1883 )
1884 .await?;
1885 }
1886
1887 sync.set_pos("chocolat".to_owned()).await;
1889
1890 let (_request, _, _) = sync.generate_sync_request().await?;
1891
1892 {
1894 let olm_machine = client.olm_machine().await;
1895 let olm_machine = olm_machine.as_ref().unwrap();
1896
1897 let outgoing_requests = olm_machine.outgoing_requests().await?;
1899
1900 assert!(outgoing_requests.is_empty());
1901 }
1902
1903 Ok(())
1904 }
1905
1906 #[cfg(feature = "e2e-encryption")]
1907 #[async_test]
1908 async fn test_sliding_sync_doesnt_remember_pos() -> Result<()> {
1909 let server = MockServer::start().await;
1910
1911 #[derive(Deserialize)]
1912 struct PartialRequest {
1913 txn_id: Option<String>,
1914 }
1915
1916 let server_pos = Arc::new(Mutex::new(0));
1917 let _mock_guard = Mock::given(SlidingSyncMatcher)
1918 .respond_with(move |request: &Request| {
1919 let request: PartialRequest = request.body_json().unwrap();
1921 let pos = {
1922 let mut pos = server_pos.lock().unwrap();
1923 let prev = *pos;
1924 *pos += 1;
1925 prev
1926 };
1927
1928 ResponseTemplate::new(200).set_body_json(json!({
1929 "txn_id": request.txn_id,
1930 "pos": pos.to_string(),
1931 }))
1932 })
1933 .mount_as_scoped(&server)
1934 .await;
1935
1936 let client = logged_in_client(Some(server.uri())).await;
1937
1938 let sliding_sync = client.sliding_sync("forgetful-sync")?.build().await?;
1939
1940 {
1942 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1943
1944 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1945 assert!(request.pos.is_none());
1946 }
1947
1948 let sync = sliding_sync.sync();
1949 pin_mut!(sync);
1950
1951 let next = sync.next().await;
1954 assert_matches!(next, Some(Ok(_update_summary)));
1955
1956 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("0"));
1957
1958 let restored_fields = restore_sliding_sync_state(&client, &sliding_sync.inner.storage_key)
1959 .await?
1960 .expect("must have restored fields");
1961
1962 assert_eq!(restored_fields.pos.as_deref(), Some("0"));
1965
1966 {
1970 let other_sync = client.sliding_sync("forgetful-sync")?.build().await?;
1971
1972 let mut position_guard = other_sync.inner.position.lock().await;
1973 position_guard.pos = Some("yolo".to_owned());
1974
1975 other_sync.cache_to_storage(&position_guard).await?;
1976 }
1977
1978 {
1980 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("0"));
1981 let (request, _, _) = sliding_sync.generate_sync_request().await?;
1982 assert_eq!(request.pos.as_deref(), Some("0"));
1983 }
1984
1985 {
1988 let sliding_sync = client.sliding_sync("forgetful-sync")?.build().await?;
1989 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1990 }
1991
1992 Ok(())
1993 }
1994
1995 #[cfg(feature = "e2e-encryption")]
1996 #[async_test]
1997 async fn test_sliding_sync_does_remember_pos() -> Result<()> {
1998 let server = MockServer::start().await;
1999
2000 #[derive(Deserialize)]
2001 struct PartialRequest {
2002 txn_id: Option<String>,
2003 }
2004
2005 let server_pos = Arc::new(Mutex::new(0));
2006 let _mock_guard = Mock::given(SlidingSyncMatcher)
2007 .respond_with(move |request: &Request| {
2008 let request: PartialRequest = request.body_json().unwrap();
2010 let pos = {
2011 let mut pos = server_pos.lock().unwrap();
2012 let prev = *pos;
2013 *pos += 1;
2014 prev
2015 };
2016
2017 ResponseTemplate::new(200).set_body_json(json!({
2018 "txn_id": request.txn_id,
2019 "pos": pos.to_string(),
2020 }))
2021 })
2022 .mount_as_scoped(&server)
2023 .await;
2024
2025 let client = logged_in_client(Some(server.uri())).await;
2026
2027 let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
2028
2029 {
2031 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2032
2033 assert!(request.pos.is_none());
2034 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
2035 }
2036
2037 let sync = sliding_sync.sync();
2038 pin_mut!(sync);
2039
2040 let next = sync.next().await;
2043 assert_matches!(next, Some(Ok(_update_summary)));
2044
2045 assert_eq!(sliding_sync.inner.position.lock().await.pos, Some("0".to_owned()));
2046
2047 let restored_fields = restore_sliding_sync_state(&client, &sliding_sync.inner.storage_key)
2048 .await?
2049 .expect("must have restored fields");
2050
2051 assert_eq!(restored_fields.pos.as_deref(), Some("0"));
2054
2055 {
2057 let other_sync = client.sliding_sync("elephant-sync")?.build().await?;
2058
2059 let mut position_guard = other_sync.inner.position.lock().await;
2060 position_guard.pos = Some("42".to_owned());
2061
2062 other_sync.cache_to_storage(&position_guard).await?;
2063 }
2064
2065 {
2067 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2068 assert_eq!(request.pos.as_deref(), Some("42"));
2069 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("42"));
2070 }
2071
2072 {
2074 let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
2075 assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("42"));
2076
2077 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2078 assert_eq!(request.pos.as_deref(), Some("42"));
2079 }
2080
2081 sliding_sync.expire_session().await;
2084
2085 {
2086 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
2087
2088 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2089 assert!(request.pos.is_none());
2090 }
2091
2092 {
2094 let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
2095 assert!(sliding_sync.inner.position.lock().await.pos.is_none());
2096
2097 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2098 assert!(request.pos.is_none());
2099 }
2100
2101 Ok(())
2102 }
2103
2104 #[async_test]
2105 async fn test_stop_sync_loop() -> Result<()> {
2106 let (_server, sliding_sync) = new_sliding_sync(vec![
2107 SlidingSyncList::builder("foo")
2108 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2109 ])
2110 .await?;
2111
2112 let stream = sliding_sync.sync();
2114 pin_mut!(stream);
2115
2116 assert!(stream.next().await.is_some());
2118
2119 sliding_sync.stop_sync()?;
2121
2122 assert!(stream.next().await.is_none());
2124
2125 let stream = sliding_sync.sync();
2127 pin_mut!(stream);
2128
2129 assert!(stream.next().await.is_some());
2131
2132 Ok(())
2133 }
2134
2135 #[async_test]
2136 async fn test_process_read_receipts() -> Result<()> {
2137 let room = owned_room_id!("!pony:example.org");
2138
2139 let server = MockServer::start().await;
2140 let client = logged_in_client(Some(server.uri())).await;
2141 client.event_cache().subscribe().unwrap();
2142
2143 let sliding_sync = client
2144 .sliding_sync("test")?
2145 .with_receipt_extension(
2146 assign!(http::request::Receipts::default(), { enabled: Some(true) }),
2147 )
2148 .add_list(
2149 SlidingSyncList::builder("all")
2150 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2151 )
2152 .build()
2153 .await?;
2154
2155 {
2157 let server_response = assign!(http::Response::new("0".to_owned()), {
2158 rooms: BTreeMap::from([(
2159 room.clone(),
2160 http::response::Room::default(),
2161 )])
2162 });
2163
2164 let _summary = {
2165 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2166 sliding_sync
2167 .handle_response(
2168 server_response.clone(),
2169 &mut pos_guard,
2170 RequestedRequiredStates::default(),
2171 )
2172 .await?
2173 };
2174 }
2175
2176 let server_response = assign!(http::Response::new("1".to_owned()), {
2177 extensions: assign!(http::response::Extensions::default(), {
2178 receipts: assign!(http::response::Receipts::default(), {
2179 rooms: BTreeMap::from([
2180 (
2181 room.clone(),
2182 Raw::from_json_string(
2183 json!({
2184 "room_id": room,
2185 "type": "m.receipt",
2186 "content": {
2187 "$event:bar.org": {
2188 "m.read": {
2189 client.user_id().unwrap(): {
2190 "ts": 1436451550,
2191 }
2192 }
2193 }
2194 }
2195 })
2196 .to_string(),
2197 ).unwrap()
2198 )
2199 ])
2200 })
2201 })
2202 });
2203
2204 let summary = {
2205 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2206 sliding_sync
2207 .handle_response(
2208 server_response.clone(),
2209 &mut pos_guard,
2210 RequestedRequiredStates::default(),
2211 )
2212 .await?
2213 };
2214
2215 assert!(summary.rooms.contains(&room));
2216
2217 Ok(())
2218 }
2219
2220 #[async_test]
2221 async fn test_process_marked_unread_room_account_data() -> Result<()> {
2222 let room_id = owned_room_id!("!unicorn:example.org");
2223
2224 let server = MockServer::start().await;
2225 let client = logged_in_client(Some(server.uri())).await;
2226
2227 let sliding_sync = client
2230 .sliding_sync("test")?
2231 .with_account_data_extension(
2232 assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2233 )
2234 .add_list(
2235 SlidingSyncList::builder("all")
2236 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2237 )
2238 .build()
2239 .await?;
2240
2241 {
2243 let server_response = assign!(http::Response::new("0".to_owned()), {
2244 rooms: BTreeMap::from([(
2245 room_id.clone(),
2246 http::response::Room::default(),
2247 )])
2248 });
2249
2250 let _summary = {
2251 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2252 sliding_sync
2253 .handle_response(
2254 server_response.clone(),
2255 &mut pos_guard,
2256 RequestedRequiredStates::default(),
2257 )
2258 .await?
2259 };
2260 }
2261
2262 let server_response = make_mark_unread_response("1", room_id.clone(), true, false);
2266
2267 let update_summary = {
2268 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2269 sliding_sync
2270 .handle_response(
2271 server_response.clone(),
2272 &mut pos_guard,
2273 RequestedRequiredStates::default(),
2274 )
2275 .await?
2276 };
2277
2278 assert!(update_summary.rooms.contains(&room_id));
2281
2282 let room = client.get_room(&room_id).unwrap();
2283
2284 assert!(room.is_marked_unread());
2287
2288 let server_response = make_mark_unread_response("2", room_id.clone(), false, true);
2291
2292 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2293 sliding_sync
2294 .handle_response(
2295 server_response.clone(),
2296 &mut pos_guard,
2297 RequestedRequiredStates::default(),
2298 )
2299 .await?;
2300
2301 let room = client.get_room(&room_id).unwrap();
2302
2303 assert!(!room.is_marked_unread());
2304
2305 Ok(())
2306 }
2307
2308 fn make_mark_unread_response(
2309 response_number: &str,
2310 room_id: OwnedRoomId,
2311 unread: bool,
2312 add_rooms_section: bool,
2313 ) -> http::Response {
2314 let rooms = if add_rooms_section {
2315 BTreeMap::from([(room_id.clone(), http::response::Room::default())])
2316 } else {
2317 BTreeMap::new()
2318 };
2319
2320 let extensions = assign!(http::response::Extensions::default(), {
2321 account_data: assign!(http::response::AccountData::default(), {
2322 rooms: BTreeMap::from([
2323 (
2324 room_id,
2325 vec![
2326 Raw::from_json_string(
2327 json!({
2328 "content": {
2329 "unread": unread
2330 },
2331 "type": "m.marked_unread"
2332 })
2333 .to_string(),
2334 ).unwrap()
2335 ]
2336 )
2337 ])
2338 })
2339 });
2340
2341 assign!(http::Response::new(response_number.to_owned()), { rooms: rooms, extensions: extensions })
2342 }
2343
2344 #[async_test]
2345 async fn test_process_rooms_account_data() -> Result<()> {
2346 let room = owned_room_id!("!pony:example.org");
2347
2348 let server = MockServer::start().await;
2349 let client = logged_in_client(Some(server.uri())).await;
2350
2351 let sliding_sync = client
2352 .sliding_sync("test")?
2353 .with_account_data_extension(
2354 assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2355 )
2356 .add_list(
2357 SlidingSyncList::builder("all")
2358 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2359 )
2360 .build()
2361 .await?;
2362
2363 {
2365 let server_response = assign!(http::Response::new("0".to_owned()), {
2366 rooms: BTreeMap::from([(
2367 room.clone(),
2368 http::response::Room::default(),
2369 )])
2370 });
2371
2372 let _summary = {
2373 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2374 sliding_sync
2375 .handle_response(
2376 server_response.clone(),
2377 &mut pos_guard,
2378 RequestedRequiredStates::default(),
2379 )
2380 .await?
2381 };
2382 }
2383
2384 let server_response = assign!(http::Response::new("1".to_owned()), {
2385 extensions: assign!(http::response::Extensions::default(), {
2386 account_data: assign!(http::response::AccountData::default(), {
2387 rooms: BTreeMap::from([
2388 (
2389 room.clone(),
2390 vec![
2391 Raw::from_json_string(
2392 json!({
2393 "content": {
2394 "tags": {
2395 "u.work": {
2396 "order": 0.9
2397 }
2398 }
2399 },
2400 "type": "m.tag"
2401 })
2402 .to_string(),
2403 ).unwrap()
2404 ]
2405 )
2406 ])
2407 })
2408 })
2409 });
2410 let summary = {
2411 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2412 sliding_sync
2413 .handle_response(
2414 server_response.clone(),
2415 &mut pos_guard,
2416 RequestedRequiredStates::default(),
2417 )
2418 .await?
2419 };
2420
2421 assert!(summary.rooms.contains(&room));
2422
2423 Ok(())
2424 }
2425
2426 #[async_test]
2427 #[cfg(feature = "e2e-encryption")]
2428 async fn test_process_only_encryption_events() -> Result<()> {
2429 use ruma::OneTimeKeyAlgorithm;
2430
2431 let room = owned_room_id!("!croissant:example.org");
2432
2433 let server = MockServer::start().await;
2434 let client = logged_in_client(Some(server.uri())).await;
2435
2436 let server_response = assign!(http::Response::new("0".to_owned()), {
2437 rooms: BTreeMap::from([(
2438 room.clone(),
2439 assign!(http::response::Room::default(), {
2440 name: Some("Croissants lovers".to_owned()),
2441 timeline: Vec::new(),
2442 }),
2443 )]),
2444
2445 extensions: assign!(http::response::Extensions::default(), {
2446 e2ee: assign!(http::response::E2EE::default(), {
2447 device_one_time_keys_count: BTreeMap::from([(OneTimeKeyAlgorithm::SignedCurve25519, uint!(42))])
2448 }),
2449 to_device: Some(assign!(http::response::ToDevice::default(), {
2450 next_batch: "to-device-token".to_owned(),
2451 })),
2452 })
2453 });
2454
2455 let sliding_sync = client
2459 .sliding_sync("test")?
2460 .with_to_device_extension(
2461 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2462 )
2463 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2464 .build()
2465 .await?;
2466
2467 {
2468 let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2469
2470 sliding_sync
2471 .handle_response(
2472 server_response.clone(),
2473 &mut position_guard,
2474 RequestedRequiredStates::default(),
2475 )
2476 .await?;
2477 }
2478
2479 let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2481 assert_eq!(uploaded_key_count, 42);
2482
2483 {
2484 let olm_machine = &*client.olm_machine_for_testing().await;
2485 assert_eq!(
2486 olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2487 Some("to-device-token")
2488 );
2489 }
2490
2491 assert!(client.get_room(&room).is_none());
2493
2494 let client = logged_in_client(Some(server.uri())).await;
2497
2498 let sliding_sync = client
2499 .sliding_sync("test")?
2500 .add_list(SlidingSyncList::builder("thelist"))
2501 .build()
2502 .await?;
2503
2504 {
2505 let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2506
2507 sliding_sync
2508 .handle_response(
2509 server_response.clone(),
2510 &mut position_guard,
2511 RequestedRequiredStates::default(),
2512 )
2513 .await?;
2514 }
2515
2516 let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2518 assert_eq!(uploaded_key_count, 0);
2519
2520 {
2521 let olm_machine = &*client.olm_machine_for_testing().await;
2522 assert_eq!(
2523 olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2524 None
2525 );
2526 }
2527
2528 assert!(client.get_room(&room).is_some());
2530
2531 let client = logged_in_client(Some(server.uri())).await;
2533
2534 let sliding_sync = client
2535 .sliding_sync("test")?
2536 .add_list(SlidingSyncList::builder("thelist"))
2537 .with_to_device_extension(
2538 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2539 )
2540 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2541 .build()
2542 .await?;
2543
2544 {
2545 let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2546
2547 sliding_sync
2548 .handle_response(
2549 server_response.clone(),
2550 &mut position_guard,
2551 RequestedRequiredStates::default(),
2552 )
2553 .await?;
2554 }
2555
2556 let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2558 assert_eq!(uploaded_key_count, 42);
2559
2560 {
2561 let olm_machine = &*client.olm_machine_for_testing().await;
2562 assert_eq!(
2563 olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2564 Some("to-device-token")
2565 );
2566 }
2567
2568 assert!(client.get_room(&room).is_some());
2570
2571 Ok(())
2572 }
2573
2574 #[async_test]
2575 async fn test_lock_multiple_requests() -> Result<()> {
2576 let server = MockServer::start().await;
2577 let client = logged_in_client(Some(server.uri())).await;
2578
2579 let pos = Arc::new(Mutex::new(0));
2580 let _mock_guard = Mock::given(SlidingSyncMatcher)
2581 .respond_with(move |_: &Request| {
2582 let mut pos = pos.lock().unwrap();
2583 *pos += 1;
2584 ResponseTemplate::new(200).set_body_json(json!({
2585 "pos": pos.to_string(),
2586 "lists": {},
2587 "rooms": {}
2588 }))
2589 })
2590 .mount_as_scoped(&server)
2591 .await;
2592
2593 let sliding_sync = client
2594 .sliding_sync("test")?
2595 .with_to_device_extension(
2596 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2597 )
2598 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2599 .build()
2600 .await?;
2601
2602 let requests = join_all([sliding_sync.sync_once(), sliding_sync.sync_once()]);
2605
2606 for result in requests.await {
2607 result?;
2608 }
2609
2610 Ok(())
2611 }
2612
2613 #[async_test]
2614 async fn test_aborted_request_doesnt_update_future_requests() -> Result<()> {
2615 let server = MockServer::start().await;
2616 let client = logged_in_client(Some(server.uri())).await;
2617
2618 let pos = Arc::new(Mutex::new(0));
2619 let _mock_guard = Mock::given(SlidingSyncMatcher)
2620 .respond_with(move |_: &Request| {
2621 let mut pos = pos.lock().unwrap();
2622 *pos += 1;
2623 ResponseTemplate::new(200)
2625 .set_body_json(json!({
2626 "pos": pos.to_string(),
2627 "lists": {},
2628 "rooms": {}
2629 }))
2630 .set_delay(Duration::from_secs(2))
2631 })
2632 .mount_as_scoped(&server)
2633 .await;
2634
2635 let sliding_sync =
2636 client
2637 .sliding_sync("test")?
2638 .add_list(SlidingSyncList::builder("room-list").sync_mode(
2639 SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(100),
2640 ))
2641 .add_list(
2642 SlidingSyncList::builder("another-list")
2643 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2644 )
2645 .build()
2646 .await?;
2647
2648 let stream = sliding_sync.sync();
2649 pin_mut!(stream);
2650
2651 let cloned_sync = sliding_sync.clone();
2652 spawn(async move {
2653 tokio::time::sleep(Duration::from_millis(100)).await;
2654
2655 cloned_sync
2656 .on_list("another-list", |list| {
2657 list.set_sync_mode(SlidingSyncMode::new_selective().add_range(10..=20));
2658 ready(())
2659 })
2660 .await;
2661 });
2662
2663 assert_matches!(stream.next().await, Some(Ok(_)));
2664
2665 sliding_sync.stop_sync().unwrap();
2666
2667 assert_matches!(stream.next().await, None);
2668
2669 let mut num_requests = 0;
2670
2671 for request in server.received_requests().await.unwrap() {
2672 if !SlidingSyncMatcher.matches(&request) {
2673 continue;
2674 }
2675
2676 let another_list_ranges = if num_requests == 0 {
2677 json!([[0, 10]])
2679 } else {
2680 json!([[10, 20]])
2682 };
2683
2684 num_requests += 1;
2685 assert!(num_requests <= 2, "more than one request hit the server");
2686
2687 let json_value = serde_json::from_slice::<serde_json::Value>(&request.body).unwrap();
2688
2689 if let Err(err) = assert_json_diff::assert_json_matches_no_panic(
2690 &json_value,
2691 &json!({
2692 "conn_id": "test",
2693 "lists": {
2694 "room-list": {
2695 "ranges": [[0, 9]],
2696 "required_state": [
2697 ["m.room.encryption", ""],
2698 ["m.room.tombstone", ""]
2699 ],
2700 },
2701 "another-list": {
2702 "ranges": another_list_ranges,
2703 "required_state": [
2704 ["m.room.encryption", ""],
2705 ["m.room.tombstone", ""]
2706 ],
2707 },
2708 }
2709 }),
2710 assert_json_diff::Config::new(assert_json_diff::CompareMode::Inclusive),
2711 ) {
2712 dbg!(json_value);
2713 panic!("json differ: {err}");
2714 }
2715 }
2716
2717 assert_eq!(num_requests, 2);
2718
2719 Ok(())
2720 }
2721
2722 #[async_test]
2723 async fn test_timeout_zero_list() -> Result<()> {
2724 let (_server, sliding_sync) = new_sliding_sync(vec![]).await?;
2725
2726 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2727
2728 assert!(request.timeout.is_some());
2731
2732 Ok(())
2733 }
2734
2735 #[async_test]
2736 async fn test_timeout_one_list() -> Result<()> {
2737 let (_server, sliding_sync) = new_sliding_sync(vec![
2738 SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::new_growing(10)),
2739 ])
2740 .await?;
2741
2742 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2743
2744 assert!(request.timeout.is_none());
2746
2747 {
2749 let server_response = assign!(http::Response::new("0".to_owned()), {
2750 lists: BTreeMap::from([(
2751 "foo".to_owned(),
2752 assign!(http::response::List::default(), {
2753 count: uint!(7),
2754 })
2755 )])
2756 });
2757
2758 let _summary = {
2759 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2760 sliding_sync
2761 .handle_response(
2762 server_response.clone(),
2763 &mut pos_guard,
2764 RequestedRequiredStates::default(),
2765 )
2766 .await?
2767 };
2768 }
2769
2770 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2771
2772 assert!(request.timeout.is_some());
2774
2775 Ok(())
2776 }
2777
2778 #[async_test]
2779 async fn test_timeout_three_lists() -> Result<()> {
2780 let (_server, sliding_sync) = new_sliding_sync(vec![
2781 SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::new_growing(10)),
2782 SlidingSyncList::builder("bar").sync_mode(SlidingSyncMode::new_paging(10)),
2783 SlidingSyncList::builder("baz")
2784 .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2785 ])
2786 .await?;
2787
2788 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2789
2790 assert!(request.timeout.is_none());
2792
2793 {
2795 let server_response = assign!(http::Response::new("0".to_owned()), {
2796 lists: BTreeMap::from([(
2797 "foo".to_owned(),
2798 assign!(http::response::List::default(), {
2799 count: uint!(7),
2800 })
2801 )])
2802 });
2803
2804 let _summary = {
2805 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2806 sliding_sync
2807 .handle_response(
2808 server_response.clone(),
2809 &mut pos_guard,
2810 RequestedRequiredStates::default(),
2811 )
2812 .await?
2813 };
2814 }
2815
2816 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2817
2818 assert!(request.timeout.is_none());
2820
2821 {
2823 let server_response = assign!(http::Response::new("1".to_owned()), {
2824 lists: BTreeMap::from([(
2825 "bar".to_owned(),
2826 assign!(http::response::List::default(), {
2827 count: uint!(7),
2828 })
2829 )])
2830 });
2831
2832 let _summary = {
2833 let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2834 sliding_sync
2835 .handle_response(
2836 server_response.clone(),
2837 &mut pos_guard,
2838 RequestedRequiredStates::default(),
2839 )
2840 .await?
2841 };
2842 }
2843
2844 let (request, _, _) = sliding_sync.generate_sync_request().await?;
2845
2846 assert!(request.timeout.is_some());
2848
2849 Ok(())
2850 }
2851
2852 #[async_test]
2853 async fn test_sync_beat_is_notified_on_sync_response() -> Result<()> {
2854 let server = MockServer::start().await;
2855 let client = logged_in_client(Some(server.uri())).await;
2856
2857 let _mock_guard = Mock::given(SlidingSyncMatcher)
2858 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2859 "pos": "0",
2860 "lists": {},
2861 "rooms": {}
2862 })))
2863 .mount_as_scoped(&server)
2864 .await;
2865
2866 let sliding_sync = client
2867 .sliding_sync("test")?
2868 .with_to_device_extension(
2869 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2870 )
2871 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2872 .build()
2873 .await?;
2874
2875 let sliding_sync = Arc::new(sliding_sync);
2876
2877 let sync_beat_listener = client.inner.sync_beat.listen();
2879 sliding_sync.sync_once().await?;
2880
2881 assert!(sync_beat_listener.wait_timeout(Duration::from_secs(1)).is_some());
2883 Ok(())
2884 }
2885
2886 #[async_test]
2887 async fn test_sync_beat_is_not_notified_on_sync_failure() -> Result<()> {
2888 let server = MockServer::start().await;
2889 let client = logged_in_client(Some(server.uri())).await;
2890
2891 let _mock_guard = Mock::given(SlidingSyncMatcher)
2892 .respond_with(ResponseTemplate::new(404))
2893 .mount_as_scoped(&server)
2894 .await;
2895
2896 let sliding_sync = client
2897 .sliding_sync("test")?
2898 .with_to_device_extension(
2899 assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2900 )
2901 .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2902 .build()
2903 .await?;
2904
2905 let sliding_sync = Arc::new(sliding_sync);
2906
2907 let sync_beat_listener = client.inner.sync_beat.listen();
2909 let sync_result = sliding_sync.sync_once().await;
2910 assert!(sync_result.is_err());
2911
2912 assert!(sync_beat_listener.wait_timeout(Duration::from_secs(1)).is_none());
2914
2915 Ok(())
2916 }
2917
2918 #[async_test]
2919 async fn test_state_store_lock_is_released_before_calling_handlers() -> Result<()> {
2920 let server = MatrixMockServer::new().await;
2921 let client = server.client_builder().build().await;
2922 let room_id = room_id!("!mu5hr00m:example.org");
2923
2924 let _sync_mock_guard = Mock::given(SlidingSyncMatcher)
2925 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2926 "pos": "0",
2927 "lists": {},
2928 "extensions": {
2929 "account_data": {
2930 "global": [
2931 {
2932 "type": "m.direct",
2933 "content": {
2934 "@de4dlockh0lmes:example.org": [
2935 "!mu5hr00m:example.org"
2936 ]
2937 }
2938 }
2939 ]
2940 }
2941 },
2942 "rooms": {
2943 room_id: {
2944 "name": "Mario Bros Fanbase Room",
2945 "initial": true,
2946 },
2947 }
2948 })))
2949 .mount_as_scoped(server.server())
2950 .await;
2951
2952 let f = EventFactory::new().room(room_id);
2953
2954 Mock::given(method("GET"))
2955 .and(wiremock::matchers::path_regex(r"/_matrix/client/v3/rooms/.*/members"))
2956 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2957 "chunk": [
2958 f.member(&ALICE).membership(MembershipState::Join).into_raw_timeline(),
2959 ]
2960 })))
2961 .mount(server.server())
2962 .await;
2963
2964 let (tx, rx) = tokio::sync::oneshot::channel();
2965
2966 let tx = Arc::new(Mutex::new(Some(tx)));
2967 client.add_event_handler(move |_: DirectEvent, client: Client| async move {
2968 let members =
2970 client.get_room(room_id).unwrap().members(RoomMemberships::JOIN).await.unwrap();
2971 assert_eq!(members.len(), 1);
2972 tx.lock().unwrap().take().expect("sender consumed multiple times").send(()).unwrap();
2973 });
2974
2975 let sliding_sync = client
2976 .sliding_sync("test")?
2977 .add_list(SlidingSyncList::builder("thelist"))
2978 .with_account_data_extension(
2979 assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2980 )
2981 .build()
2982 .await?;
2983
2984 tokio::time::timeout(Duration::from_secs(5), sliding_sync.sync_once())
2985 .await
2986 .expect("Sync did not complete in time")
2987 .expect("Sync failed");
2988
2989 tokio::time::timeout(Duration::from_secs(5), rx)
2991 .await
2992 .expect("Event handler did not complete in time")
2993 .expect("Event handler failed");
2994
2995 Ok(())
2996 }
2997
2998 #[cfg(feature = "e2e-encryption")]
2999 #[async_test]
3000 async fn test_syncing_one_time_key_counts_updates() -> Result<()> {
3001 macro_rules! assert_key_count {
3002 ($client: ident, $count:literal) => {{
3003 let machine = $client.olm_machine().await;
3004 let uploaded_key_counts =
3005 machine.as_ref().unwrap().uploaded_key_count().await.unwrap();
3006 assert_eq!(uploaded_key_counts, $count)
3007 }};
3008 }
3009
3010 macro_rules! sync_with_key_count {
3011 ($client: ident, $server:ident, $count:literal) => {
3012 let count = Some($count);
3013 sync_with_key_count!($client, $server, count);
3014 };
3015 ($client: ident, $server:ident, $count:ident) => {{
3016 let count: Option<u32> = $count;
3017
3018 let template = if let Some(count) = count {
3019 ResponseTemplate::new(200).set_body_json(json!({
3020 "pos": "0",
3021 "lists": {},
3022 "extensions": {
3023 "e2ee": {
3024 "device_one_time_keys_count": {
3025 "signed_curve25519": count,
3026 }
3027 }
3028 },
3029 }))
3030 } else {
3031 ResponseTemplate::new(200).set_body_json(json!({
3032 "pos": "0",
3033 "lists": {},
3034 "extensions": {
3035 "e2ee": {}
3036 },
3037 }))
3038 };
3039
3040 let _sync_mock_guard = Mock::given(SlidingSyncMatcher)
3041 .respond_with(template)
3042 .mount_as_scoped($server.server())
3043 .await;
3044
3045 let sliding_sync = $client
3046 .sliding_sync("test")?
3047 .with_e2ee_extension(
3048 assign!(http::request::E2EE::default(), { enabled: Some(true)}),
3049 )
3050 .build()
3051 .await?;
3052
3053 tokio::time::timeout(Duration::from_secs(5), sliding_sync.sync_once())
3054 .await
3055 .expect("Sync did not complete in time")
3056 .expect("Sync failed");
3057 }}
3058 }
3059
3060 let server = MatrixMockServer::new().await;
3061 let client = server.client_builder().build().await;
3062
3063 server.mock_upload_keys().ok_with_signed_curve_key_count(50).mock_once().mount().await;
3064
3065 assert_key_count!(client, 0);
3067
3068 sync_with_key_count!(client, server, None);
3070 assert_key_count!(client, 50);
3071
3072 sync_with_key_count!(client, server, None);
3074 assert_key_count!(client, 50);
3075
3076 sync_with_key_count!(client, server, 10);
3078 assert_key_count!(client, 10);
3079
3080 Ok(())
3081 }
3082}