Skip to main content

matrix_sdk/sliding_sync/
mod.rs

1// Copyright 2022-2023 Benjamin Kampmann
2// Copyright 2022 The Matrix.org Foundation C.I.C.
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for that specific language governing permissions and
14// limitations under the License.
15
16#![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/// The Sliding Sync instance.
55///
56/// It is OK to clone this type as much as you need: cloning it is cheap.
57#[derive(Clone, Debug)]
58pub struct SlidingSync {
59    /// The Sliding Sync data.
60    inner: Arc<SlidingSyncInner>,
61}
62
63#[derive(Debug)]
64pub(super) struct SlidingSyncInner {
65    /// A unique identifier for this instance of sliding sync.
66    ///
67    /// Used to distinguish different connections to sliding sync.
68    id: String,
69
70    /// The HTTP Matrix client.
71    client: Client,
72
73    /// Long-polling timeout that appears in sliding sync request.
74    poll_timeout: Duration,
75
76    /// Extra duration for the sliding sync request to timeout. This is added to
77    /// the [`Self::poll_timeout`].
78    network_timeout: Duration,
79
80    /// The storage key to keep this cache at and load it from.
81    storage_key: String,
82
83    /// Should this sliding sync instance try to restore its sync position
84    /// from the database?
85    ///
86    /// Note: in non-cfg(e2e-encryption) builds, it's always set to false. We
87    /// keep it even so, to avoid sparkling cfg statements everywhere
88    /// throughout this file.
89    share_pos: bool,
90
91    /// Position markers.
92    ///
93    /// The `pos` marker represents a progression when exchanging requests and
94    /// responses with the server: the server acknowledges the request by
95    /// responding with a new `pos`. If the client sends two non-necessarily
96    /// consecutive requests with the same `pos`, the server has to reply with
97    /// the same identical response.
98    ///
99    /// `position` is behind a mutex so that a new request starts after the
100    /// previous request trip has fully ended (successfully or not). This
101    /// mechanism exists to wait for the response to be handled and to see the
102    /// `position` being updated, before sending a new request.
103    position: Arc<AsyncMutex<SlidingSyncPositionMarkers>>,
104
105    /// The lists of this Sliding Sync instance.
106    lists: AsyncRwLock<BTreeMap<String, SlidingSyncList>>,
107
108    /// Room subscriptions, i.e. rooms that may be out-of-scope of all lists
109    /// but one wants to receive updates.
110    room_subscriptions: StdRwLock<BTreeMap<OwnedRoomId, http::request::RoomSubscription>>,
111
112    /// The intended state of the extensions being supplied to sliding /sync
113    /// calls.
114    extensions: http::request::Extensions,
115
116    /// Internal channel used to pass messages between Sliding Sync and other
117    /// types.
118    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    /// Create a new [`SlidingSyncBuilder`].
131    pub fn builder(id: String, client: Client) -> Result<SlidingSyncBuilder, Error> {
132        SlidingSyncBuilder::new(id, client)
133    }
134
135    /// Add a subscription for each room of `room_ids`, and keep the existing
136    /// subscriptions to other rooms.
137    ///
138    /// If the associated `Room`s exist, they will be marked as members are
139    /// missing, so that it ensures to re-fetch all members.
140    ///
141    /// A subscription to an already subscribed room only updates its
142    /// `settings`, and only if they differ. In particular, its members are
143    /// not marked as missing again.
144    pub fn add_room_subscriptions(
145        &self,
146        room_ids: &[&RoomId],
147        settings: Option<http::request::RoomSubscription>,
148        cancel_in_flight_request: bool,
149    ) {
150        let subscriptions_have_changed = upsert_room_subscriptions(
151            &mut self.inner.room_subscriptions.write().unwrap(),
152            &self.inner.client,
153            room_ids,
154            settings,
155        );
156
157        if cancel_in_flight_request && subscriptions_have_changed {
158            self.inner.cancel_in_flight_request();
159        }
160    }
161
162    /// Remove the subscription of each room of `room_ids`.
163    pub fn remove_room_subscriptions(&self, room_ids: &[&RoomId], cancel_in_flight_request: bool) {
164        let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
165        let mut subscriptions_have_changed = false;
166
167        for room_id in room_ids {
168            if room_subscriptions.remove(*room_id).is_some() {
169                subscriptions_have_changed = true;
170            }
171        }
172
173        if cancel_in_flight_request && subscriptions_have_changed {
174            self.inner.cancel_in_flight_request();
175        }
176    }
177
178    /// Set the room subscriptions to exactly `room_ids`.
179    ///
180    /// This is similar to [`Self::reset_and_add_room_subscriptions`] but
181    /// doesn't clear and then recreate all subscriptions. Instead, it will
182    /// perform a delta-like update which involves:
183    ///
184    /// - refreshing the `settings` of existing subscriptions if the room is
185    ///   contained in `room_ids`, and only if they differ
186    /// - adding new subscriptions for rooms in `room_ids` that are currently
187    ///   unsubscribed
188    /// - removing existing subscriptions for rooms that are not contained in
189    ///   `room_ids`
190    ///
191    /// Note that unlike [`Self::reset_and_add_room_subscriptions`], this method
192    /// will not mark members as unsynced (which would cause them to be
193    /// refetched) for subscriptions that already exist.
194    pub fn set_room_subscriptions(
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        // Remove the subscriptions to the rooms that are not in `room_ids` anymore.
203        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        // Add the subscriptions to the rooms that aren't subscribed yet, and refresh
209        // the settings of the ones that already are.
210        let a_subscription_has_been_added_or_updated = upsert_room_subscriptions(
211            &mut room_subscriptions,
212            &self.inner.client,
213            room_ids,
214            settings,
215        );
216
217        // The in-flight request must be cancelled as soon as the set of subscriptions
218        // has changed.
219        if cancel_in_flight_request
220            && (a_subscription_has_been_added_or_updated || a_subscription_has_been_removed)
221        {
222            self.inner.cancel_in_flight_request();
223        }
224    }
225
226    /// Remove all the room subscriptions, then add a subscription for each room
227    /// of `room_ids`.
228    ///
229    /// If the associated `Room`s exist, they will be marked as members are
230    /// missing, so that it ensures to re-fetch all members.
231    pub fn reset_and_add_room_subscriptions(
232        &self,
233        room_ids: &[&RoomId],
234        settings: Option<http::request::RoomSubscription>,
235        cancel_in_flight_request: bool,
236    ) {
237        let mut room_subscriptions = self.inner.room_subscriptions.write().unwrap();
238
239        let a_subscription_has_been_removed = !room_subscriptions.is_empty();
240        room_subscriptions.clear();
241
242        let a_subscription_has_been_added = upsert_room_subscriptions(
243            &mut room_subscriptions,
244            &self.inner.client,
245            room_ids,
246            settings,
247        );
248
249        if cancel_in_flight_request
250            && (a_subscription_has_been_added || a_subscription_has_been_removed)
251        {
252            self.inner.cancel_in_flight_request();
253        }
254    }
255
256    /// Find a list by its name, and do something on it if it exists.
257    pub async fn on_list<Function, FunctionOutput, R>(
258        &self,
259        list_name: &str,
260        function: Function,
261    ) -> Option<R>
262    where
263        Function: FnOnce(&SlidingSyncList) -> FunctionOutput,
264        FunctionOutput: Future<Output = R>,
265    {
266        let lists = self.inner.lists.read().await;
267
268        match lists.get(list_name) {
269            Some(list) => Some(function(list).await),
270            None => None,
271        }
272    }
273
274    /// Add the list to the list of lists.
275    ///
276    /// As lists need to have a unique `.name`, if a list with the same name
277    /// is found the new list will replace the old one and the return it or
278    /// `None`.
279    pub async fn add_list(
280        &self,
281        list_builder: SlidingSyncListBuilder,
282    ) -> Result<Option<SlidingSyncList>> {
283        let list = list_builder.build(self.inner.internal_channel.clone());
284
285        let old_list = self.inner.lists.write().await.insert(list.name().to_owned(), list);
286
287        self.inner.internal_channel_send_if_possible(
288            SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
289        );
290
291        Ok(old_list)
292    }
293
294    /// Add a list that will be cached and reloaded from the cache.
295    ///
296    /// This will raise an error if a storage key was not set, or if there
297    /// was a I/O error reading from the cache.
298    ///
299    /// The rest of the semantics is the same as [`Self::add_list`].
300    pub async fn add_cached_list(
301        &self,
302        mut list_builder: SlidingSyncListBuilder,
303    ) -> Result<Option<SlidingSyncList>> {
304        let _timer = timer!(format!("restoring (loading+processing) list {}", list_builder.name));
305
306        list_builder.set_cached_and_reload(&self.inner.client, &self.inner.storage_key).await?;
307
308        self.add_list(list_builder).await
309    }
310
311    /// Handle the HTTP response.
312    #[instrument(skip_all)]
313    async fn handle_response(
314        &self,
315        mut sliding_sync_response: http::Response,
316        position: &mut SlidingSyncPositionMarkers,
317        requested_required_states: RequestedRequiredStates,
318    ) -> Result<UpdateSummary, crate::Error> {
319        let pos = Some(sliding_sync_response.pos.clone());
320
321        let must_process_rooms_response = self.must_process_rooms_response().await;
322
323        trace!(yes = must_process_rooms_response, "Must process rooms response?");
324
325        // Transform a Sliding Sync Response to a `SyncResponse`.
326        //
327        // We may not need the `sync_response` in the future (once `SyncResponse` will
328        // move to Sliding Sync, i.e. to `http::Response`), but processing the
329        // `sliding_sync_response` is vital, so it must be done somewhere; for now it
330        // happens here.
331
332        let sync_response = {
333            let _timer = timer!("response processor");
334
335            let response_processor = {
336                // Take the lock to synchronise accesses to the state store, to avoid concurrent
337                // sliding syncs overwriting each other's room infos.
338                let state_store_guard = {
339                    let _timer = timer!("acquiring the `state_store_lock`");
340
341                    self.inner.client.base_client().state_store_lock().lock().await
342                };
343
344                let mut response_processor =
345                    SlidingSyncResponseProcessor::new(self.inner.client.clone());
346
347                // Process thread subscriptions if they're available.
348                //
349                // It's important to do this *before* handling the room responses, so that
350                // notifications can be properly generated based on the thread subscriptions,
351                // for the events in threads we've subscribed to.
352                if self.is_thread_subscriptions_enabled() {
353                    response_processor
354                        .handle_thread_subscriptions(
355                            position.pos.as_deref(),
356                            std::mem::take(
357                                &mut sliding_sync_response.extensions.thread_subscriptions,
358                            ),
359                        )
360                        .await?;
361                }
362
363                #[cfg(feature = "e2e-encryption")]
364                if self.is_e2ee_enabled() {
365                    response_processor
366                        .handle_encryption(&sliding_sync_response.extensions, &state_store_guard)
367                        .await?
368                }
369
370                // Only handle the room's subsection of the response, if this sliding sync was
371                // configured to do so.
372                if must_process_rooms_response {
373                    response_processor
374                        .handle_room_response(
375                            &sliding_sync_response,
376                            &requested_required_states,
377                            &state_store_guard,
378                        )
379                        .await?;
380                }
381
382                response_processor
383            };
384
385            // Release the lock before calling event handlers
386            response_processor.process_and_take_response().await?
387        };
388
389        debug!("Sliding Sync response has been handled by the client");
390        trace!(?sync_response);
391
392        let update_summary = {
393            // Update the rooms.
394            let updated_rooms = {
395                let mut updated_rooms = Vec::with_capacity(
396                    sliding_sync_response.rooms.len() + sync_response.rooms.joined.len(),
397                );
398
399                updated_rooms.extend(sliding_sync_response.rooms.keys().cloned());
400
401                // There might be other rooms that were only mentioned in the sliding sync
402                // extensions part of the response, and thus would result in rooms present in
403                // the `sync_response.joined`. Mark them as updated too.
404                //
405                // Since we've removed rooms that were in the room subsection from
406                // `sync_response.rooms.joined`, the remaining ones aren't already present in
407                // `updated_rooms` and wouldn't cause any duplicates.
408                updated_rooms.extend(sync_response.rooms.joined.keys().cloned());
409
410                updated_rooms
411            };
412
413            // Update the lists.
414            let updated_lists = {
415                debug!(
416                    lists = ?sliding_sync_response.lists,
417                    "Update lists"
418                );
419
420                let mut updated_lists = Vec::with_capacity(sliding_sync_response.lists.len());
421                let mut lists = self.inner.lists.write().await;
422
423                // Iterate on known lists, not on lists in the response. Rooms may have been
424                // updated that were not involved in any list update.
425                for (name, list) in lists.iter_mut() {
426                    if let Some(updates) = sliding_sync_response.lists.get(name) {
427                        let maximum_number_of_rooms: u32 =
428                            updates.count.try_into().expect("failed to convert `count` to `u32`");
429
430                        if list.update(Some(maximum_number_of_rooms))? {
431                            updated_lists.push(name.clone());
432                        }
433                    } else if list.update(None)? {
434                        updated_lists.push(name.clone());
435                    }
436                }
437
438                // Report about unknown lists.
439                for name in sliding_sync_response.lists.keys() {
440                    if !lists.contains_key(name) {
441                        error!("Response for list `{name}` - unknown to us; skipping");
442                    }
443                }
444
445                updated_lists
446            };
447
448            UpdateSummary { lists: updated_lists, rooms: updated_rooms }
449        };
450
451        // Everything went well, we can update the position markers.
452        //
453        // Save the new position markers.
454        debug!(previous_pos = position.pos, new_pos = pos, "Updating `pos`");
455
456        position.pos = pos;
457
458        Ok(update_summary)
459    }
460
461    async fn generate_sync_request(
462        &self,
463    ) -> Result<(http::Request, RequestConfig, OwnedMutexGuard<SlidingSyncPositionMarkers>)> {
464        // Collect requests for lists.
465        let mut requests_lists = BTreeMap::new();
466
467        let timeout = {
468            let lists = self.inner.lists.read().await;
469
470            // Start at `Default` in case there is zero list.
471            let mut timeout = PollTimeout::Default;
472
473            for (name, list) in lists.iter() {
474                requests_lists.insert(name.clone(), list.next_request()?);
475                timeout = timeout.min(list.requires_timeout());
476            }
477
478            timeout
479        };
480
481        // Collect the `pos`.
482        //
483        // Wait on the `position` mutex to be available. It means no request nor
484        // response is running. The `position` mutex is released whether the response
485        // has been fully handled successfully, in this case the `pos` is updated, or
486        // the response handling has failed, in this case the `pos` hasn't been updated
487        // and the same `pos` will be used for this new request.
488        let mut position_guard = {
489            debug!("Waiting to acquire the `position` lock");
490
491            let _timer = timer!("acquiring the `position` lock");
492
493            self.inner.position.clone().lock_owned().await
494        };
495
496        debug!(pos = ?position_guard.pos, "Got a position");
497
498        let to_device_enabled = self.inner.extensions.to_device.enabled == Some(true);
499
500        let restored_fields = if self.inner.share_pos || to_device_enabled {
501            restore_sliding_sync_state(&self.inner.client, &self.inner.storage_key).await?
502        } else {
503            None
504        };
505
506        // Update pos: either the one restored from the database, if any and the sliding
507        // sync was configured so, or read it from the memory cache.
508        let pos = if self.inner.share_pos {
509            if let Some(fields) = &restored_fields {
510                // Override the memory one with the database one, for consistency.
511                if fields.pos != position_guard.pos {
512                    info!(
513                        "Pos from previous request ('{:?}') was different from \
514                         pos in database ('{:?}').",
515                        position_guard.pos, fields.pos
516                    );
517                    position_guard.pos = fields.pos.clone();
518                }
519                fields.pos.clone()
520            } else {
521                position_guard.pos.clone()
522            }
523        } else {
524            position_guard.pos.clone()
525        };
526
527        // When the client sends a request with no `pos`, MSC4186 returns no device
528        // lists updates, as it only returns changes since the provided `pos`
529        // (which is `null` in this case); this is in line with sync v2.
530        //
531        // Therefore, with MSC4186, the device list cache must be marked as to be
532        // re-downloaded if the `since` token is `None`, otherwise it's easy to miss
533        // device lists updates that happened between the previous request and the new
534        // “initial” request.
535        #[cfg(feature = "e2e-encryption")]
536        if pos.is_none() && self.is_e2ee_enabled() {
537            info!("Marking all tracked users as dirty");
538
539            let olm_machine = self.inner.client.olm_machine().await;
540            let olm_machine = olm_machine.as_ref().ok_or(Error::NoOlmMachine)?;
541            olm_machine.mark_all_tracked_users_as_dirty().await?;
542        }
543
544        // Configure the timeout.
545        //
546        // The `timeout` query is necessary when all lists require it. Please see
547        // [`SlidingSyncList::requires_timeout`].
548        let timeout = match timeout {
549            PollTimeout::None => None,
550            PollTimeout::Some(timeout) => Some(Duration::from_secs(timeout.into())),
551            PollTimeout::Default => Some(self.inner.poll_timeout),
552        };
553
554        Span::current()
555            .record("pos", &pos)
556            .record("timeout", timeout.map(|duration| duration.as_millis()));
557
558        let mut request = assign!(http::Request::new(), {
559            conn_id: Some(self.inner.id.clone()),
560            pos,
561            set_presence: self.inner.client.sync_presence(),
562            timeout,
563            lists: requests_lists,
564        });
565
566        // Add room subscriptions.
567        request.room_subscriptions = self.inner.room_subscriptions.read().unwrap().clone();
568
569        // Add extensions.
570        request.extensions = self.inner.extensions.clone();
571
572        // Override the to-device token if the extension is enabled.
573        if to_device_enabled {
574            request.extensions.to_device.since =
575                restored_fields.and_then(|fields| fields.to_device_token);
576        }
577
578        Ok((
579            // The request itself.
580            request,
581            // Configure long-polling. We need some time for the long-poll itself,
582            // and extra time for the network delays.
583            RequestConfig::default()
584                .timeout(self.inner.poll_timeout + self.inner.network_timeout)
585                .retry_limit(3),
586            position_guard,
587        ))
588    }
589
590    /// Send a sliding sync request.
591    ///
592    /// This method contains the sending logic.
593    async fn send_sync_request(
594        &self,
595        request: http::Request,
596        request_config: RequestConfig,
597        mut position_guard: OwnedMutexGuard<SlidingSyncPositionMarkers>,
598    ) -> Result<UpdateSummary> {
599        debug!("Sending request");
600
601        // Prepare the request.
602        let requested_required_states = RequestedRequiredStates::from(&request);
603        let request = self.inner.client.send(request).with_request_config(request_config);
604
605        // Send the request and get a response with end-to-end encryption support.
606        //
607        // Sending the `/sync` request out when end-to-end encryption is enabled means
608        // that we need to also send out any outgoing e2ee related request out
609        // coming from the `OlmMachine::outgoing_requests()` method.
610
611        #[cfg(feature = "e2e-encryption")]
612        let response = {
613            if self.is_e2ee_enabled() {
614                // Here, we need to run 2 things:
615                //
616                // 1. Send the sliding sync request and get a response,
617                // 2. Send the E2EE requests.
618                //
619                // We don't want to use a `join` or `try_join` because we want to fail if and
620                // only if sending the sliding sync request fails. Failing to send the E2EE
621                // requests should just result in a log.
622                //
623                // We also want to give the priority to sliding sync request. E2EE requests are
624                // sent concurrently to the sliding sync request, but the priority is on waiting
625                // a sliding sync response.
626                //
627                // If sending sliding sync request fails, the sending of E2EE requests must be
628                // aborted as soon as possible.
629
630                let client = self.inner.client.clone();
631                let e2ee_uploads = spawn(
632                    async move {
633                        if let Err(error) = client.send_outgoing_requests().await {
634                            error!(?error, "Error while sending outgoing E2EE requests");
635                        }
636                    }
637                    .instrument(Span::current()),
638                )
639                // Ensure that the task is not running in detached mode. It is aborted when it's
640                // dropped.
641                .abort_on_drop();
642
643                // Wait on the sliding sync request success or failure early.
644                let response = request.await?;
645
646                // At this point, if `request` has been resolved successfully, we wait on
647                // `e2ee_uploads`. It did run concurrently, so it should not be blocking for too
648                // long. Otherwise —if `request` has failed— `e2ee_uploads` has
649                // been dropped, so aborted.
650                e2ee_uploads.await.map_err(|error| Error::JoinError {
651                    task_description: "e2ee_uploads".to_owned(),
652                    error,
653                })?;
654
655                response
656            } else {
657                request.await?
658            }
659        };
660
661        // Send the request and get a response _without_ end-to-end encryption support.
662        #[cfg(not(feature = "e2e-encryption"))]
663        let response = request.await?;
664
665        debug!("Received response");
666
667        // At this point, the request has been sent, and a response has been received.
668        //
669        // We must ensure the handling of the response cannot be stopped/
670        // cancelled. It must be done entirely, otherwise we can have
671        // corrupted/incomplete states for Sliding Sync and other parts of
672        // the code.
673        //
674        // That's why we are running the handling of the response in a spawned
675        // future that cannot be cancelled by anything.
676        let this = self.clone();
677
678        // Spawn a new future to ensure that the code inside this future cannot be
679        // cancelled if this method is cancelled.
680        let future = async move {
681            debug!("Start handling response");
682
683            // In case the task running this future is detached, we must
684            // ensure responses are handled one at a time. At this point we still own
685            // `position_guard`, so we're fine.
686
687            // Handle the response.
688            let updates = this
689                .handle_response(response, &mut position_guard, requested_required_states)
690                .await?;
691
692            this.cache_to_storage(&position_guard).await?;
693
694            // Release the position guard lock.
695            // It means that other responses can be generated and then handled later.
696            drop(position_guard);
697
698            debug!("Done handling response");
699
700            Ok(updates)
701        };
702
703        spawn(future.instrument(Span::current())).await.map_err(|error| Error::JoinError {
704            task_description: "handle_response".to_owned(),
705            error,
706        })?
707    }
708
709    /// Is the e2ee extension enabled for this sliding sync instance?
710    #[cfg(feature = "e2e-encryption")]
711    fn is_e2ee_enabled(&self) -> bool {
712        self.inner.extensions.e2ee.enabled == Some(true)
713    }
714
715    /// Is the thread subscriptions extension enabled for this sliding sync
716    /// instance?
717    fn is_thread_subscriptions_enabled(&self) -> bool {
718        self.inner.extensions.thread_subscriptions.enabled == Some(true)
719    }
720
721    #[cfg(not(feature = "e2e-encryption"))]
722    fn is_e2ee_enabled(&self) -> bool {
723        false
724    }
725
726    /// Should we process the room's subpart of a response?
727    async fn must_process_rooms_response(&self) -> bool {
728        // We consider that we must, if there's any room subscription or there's any
729        // list.
730        !self.inner.room_subscriptions.read().unwrap().is_empty()
731            || !self.inner.lists.read().await.is_empty()
732    }
733
734    /// Send a single sliding sync request, and returns the response summary.
735    ///
736    /// Public for testing purposes only.
737    #[doc(hidden)]
738    #[instrument(skip_all, fields(conn_id = self.inner.id, pos, timeout))]
739    pub async fn sync_once(&self) -> Result<UpdateSummary> {
740        let (request, request_config, position_guard) = self.generate_sync_request().await?;
741
742        // Send the request.
743        let summaries = self.send_sync_request(request, request_config, position_guard).await?;
744
745        // Notify a new sync was received.
746        self.inner.client.inner.sync_beat.notify(usize::MAX);
747
748        Ok(summaries)
749    }
750
751    /// Create a _new_ Sliding Sync sync loop.
752    ///
753    /// This method returns a `Stream`, which will send requests and will handle
754    /// responses automatically. Lists and rooms are updated automatically.
755    ///
756    /// This function returns `Ok(…)` if everything went well, otherwise it will
757    /// return `Err(…)`. An `Err` will _always_ lead to the `Stream`
758    /// termination.
759    #[allow(unknown_lints, clippy::let_with_type_underscore)] // triggered by instrument macro
760    #[instrument(name = "sync_stream", skip_all, fields(conn_id = self.inner.id, with_e2ee = self.is_e2ee_enabled()))]
761    pub fn sync(&self) -> impl Stream<Item = Result<UpdateSummary, crate::Error>> + '_ {
762        debug!("Starting sync stream");
763
764        let mut internal_channel_receiver = self.inner.internal_channel.subscribe();
765
766        stream! {
767            loop {
768                debug!("Sync stream is running");
769
770                select! {
771                    biased;
772
773                    internal_message = internal_channel_receiver.recv() => {
774                        use SlidingSyncInternalMessage::*;
775
776                        debug!(?internal_message, "Sync stream has received an internal message");
777
778                        match internal_message {
779                            Err(_) | Ok(SyncLoopStop) => {
780                                break;
781                            }
782
783                            Ok(SyncLoopSkipOverCurrentIteration) => {
784                                continue;
785                            }
786                        }
787                    }
788
789                    update_summary = self.sync_once() => {
790                        match update_summary {
791                            Ok(updates) => {
792                                yield Ok(updates);
793                            }
794
795                            // Here, errors we **cannot** ignore, and that must stop the sync loop.
796                            Err(error) => {
797                                if error.client_api_error_kind() == Some(&ErrorKind::UnknownPos) {
798                                    // The Sliding Sync session has expired. Let's reset `pos`.
799                                    self.expire_session().await;
800                                }
801
802                                yield Err(error);
803
804                                // Terminates the loop, and terminates the stream.
805                                break;
806                            }
807                        }
808                    }
809                }
810            }
811
812            debug!("Sync stream has exited.");
813        }
814    }
815
816    /// Force to stop the sync loop ([`Self::sync`]) if it's running.
817    ///
818    /// Usually, dropping the `Stream` returned by [`Self::sync`] should be
819    /// enough to “stop” it, but depending of how this `Stream` is used, it
820    /// might not be obvious to drop it immediately (thinking of using this API
821    /// over FFI; the foreign-language might not be able to drop a value
822    /// immediately). Thus, calling this method will ensure that the sync loop
823    /// stops gracefully and as soon as it returns.
824    pub fn stop_sync(&self) -> Result<()> {
825        Ok(self.inner.internal_channel_send(SlidingSyncInternalMessage::SyncLoopStop)?)
826    }
827
828    /// Expire the current Sliding Sync session on the client-side.
829    ///
830    /// Expiring a Sliding Sync session means: resetting `pos`.
831    ///
832    /// This should only be used when it's clear that this session was about to
833    /// expire anyways, and should be used only in very specific cases (e.g.
834    /// multiple sliding syncs being run in parallel, and one of them has
835    /// expired).
836    ///
837    /// This method **MUST** be called when the sync loop is stopped.
838    #[doc(hidden)]
839    pub async fn expire_session(&self) {
840        info!("Session expired; resetting `pos`");
841
842        {
843            let lists = self.inner.lists.read().await;
844
845            for list in lists.values() {
846                // Invalidate in-memory data that would be persisted on disk.
847                list.set_maximum_number_of_rooms(None);
848            }
849        }
850
851        // Remove the cached sliding sync state as well.
852        {
853            let mut position = self.inner.position.lock().await;
854
855            // Invalidate in memory.
856            position.pos = None;
857
858            // Propagate to disk.
859            // Note: this propagates both the sliding sync state and the cached lists'
860            // state to disk.
861            if let Err(err) = self.cache_to_storage(&position).await {
862                warn!("Failed to invalidate cached sliding sync state: {err}");
863            }
864        }
865
866        {
867            // Clear all room subscriptions: we don't want to resend all room subscriptions
868            // when the session will restart.
869            self.inner.room_subscriptions.write().unwrap().clear();
870        }
871    }
872}
873
874/// Add a subscription for each room of `room_ids` that isn't subscribed yet,
875/// and refresh the `settings` of the ones that already are.
876///
877/// It returns whether the set of subscriptions has changed, i.e. a subscription
878/// has been added, or the settings of an existing one have been updated. It is
879/// up to the caller to decide whether this warrants cancelling the in-flight
880/// request: a caller can have other reasons to cancel it, e.g. having removed a
881/// subscription.
882fn upsert_room_subscriptions(
883    room_subscriptions: &mut BTreeMap<OwnedRoomId, http::request::RoomSubscription>,
884    client: &Client,
885    room_ids: &[&RoomId],
886    settings: Option<http::request::RoomSubscription>,
887) -> bool {
888    let settings = settings.unwrap_or_default();
889    let mut subscriptions_have_changed = false;
890
891    for room_id in room_ids {
892        match room_subscriptions.entry((*room_id).to_owned()) {
893            Entry::Vacant(entry) => {
894                if let Some(room) = client.get_room(room_id) {
895                    room.mark_members_missing();
896                }
897
898                entry.insert(settings.clone());
899
900                subscriptions_have_changed = true;
901            }
902
903            // The room is already subscribed but its settings might need to be
904            // refreshed
905            Entry::Occupied(mut entry) => {
906                if room_subscriptions_differ(entry.get(), &settings) {
907                    entry.insert(settings.clone());
908
909                    subscriptions_have_changed = true;
910                }
911            }
912        }
913    }
914
915    subscriptions_have_changed
916}
917
918/// Compare two [`http::request::RoomSubscription`].
919fn room_subscriptions_differ(
920    left: &http::request::RoomSubscription,
921    right: &http::request::RoomSubscription,
922) -> bool {
923    left.timeline_limit != right.timeline_limit || left.required_state != right.required_state
924}
925
926impl SlidingSyncInner {
927    /// Send a message over the internal channel.
928    #[instrument]
929    fn internal_channel_send(&self, message: SlidingSyncInternalMessage) -> Result<(), Error> {
930        self.internal_channel.send(message).map(|_| ()).map_err(|_| Error::InternalChannelIsBroken)
931    }
932
933    /// Send a message over the internal channel if there is a receiver, i.e. if
934    /// the sync loop is running.
935    #[instrument]
936    fn internal_channel_send_if_possible(&self, message: SlidingSyncInternalMessage) {
937        // If there is no receiver, the send will fail, but that's OK here.
938        let _ = self.internal_channel.send(message);
939    }
940
941    /// Cancel the in-flight request (if any) so that the sync loop immediately
942    /// starts a new iteration, with a fresh request.
943    fn cancel_in_flight_request(&self) {
944        self.internal_channel_send_if_possible(
945            SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration,
946        );
947    }
948}
949
950#[derive(Copy, Clone, Debug, PartialEq)]
951enum SlidingSyncInternalMessage {
952    /// Instruct the sync loop to stop.
953    SyncLoopStop,
954
955    /// Instruct the sync loop to skip over any remaining work in its iteration,
956    /// and to jump to the next iteration.
957    SyncLoopSkipOverCurrentIteration,
958}
959
960#[cfg(any(test, feature = "testing"))]
961impl SlidingSync {
962    /// Set a new value for `pos`.
963    pub async fn set_pos(&self, new_pos: String) {
964        let mut position_lock = self.inner.position.lock().await;
965        position_lock.pos = Some(new_pos);
966    }
967}
968
969#[derive(Clone, Debug)]
970pub(super) struct SlidingSyncPositionMarkers {
971    /// An ephemeral position in the current stream, as received from the
972    /// previous `/sync` response, or `None` for the first request.
973    pos: Option<String>,
974}
975
976/// A summary of the updates received after a sync (like in
977/// [`SlidingSync::sync`]).
978#[derive(Debug, Clone)]
979pub struct UpdateSummary {
980    /// The names of the lists that have seen an update.
981    pub lists: Vec<String>,
982    /// The rooms that have seen updates
983    pub rooms: Vec<OwnedRoomId>,
984}
985
986/// Define what kind of poll timeout [`SlidingSync`] must use.
987///
988/// [The spec says about `timeout`][spec]:
989///
990/// > How long to wait for new events […] If omitted the response is always
991/// > returned immediately, even if there are no changes.
992///
993/// [spec]: https://github.com/matrix-org/matrix-spec-proposals/blob/erikj/sss/proposals/4186-simplified-sliding-sync.md#top-level
994#[derive(Debug)]
995pub enum PollTimeout {
996    /// No `timeout` must be present.
997    None,
998
999    /// A `timeout=X` must be present, where `X` is in seconds and
1000    /// represents how long to wait for new events.
1001    Some(u32),
1002
1003    /// A `timeout=X` must be present, where `X` is the default value passed to
1004    /// [`SlidingSyncBuilder::poll_timeout`].
1005    Default,
1006}
1007
1008impl PollTimeout {
1009    /// Computes the smallest `PollTimeout` between two of them.
1010    ///
1011    /// The rules are the following:
1012    ///
1013    /// * `None` < `Some`,
1014    /// * `Some(x) < Some(y)` if and only if `x < y`,
1015    /// * `Some < Default`.
1016    ///
1017    /// The `Default` value is unknown at this step but is assumed to be the
1018    /// largest.
1019    fn min(self, left: Self) -> Self {
1020        match (self, left) {
1021            (Self::None, _) => Self::None,
1022
1023            (Self::Some(_), Self::None) => Self::None,
1024            (Self::Some(right), Self::Some(left)) => Self::Some(right.min(left)),
1025            (Self::Some(right), Self::Default) => Self::Some(right),
1026
1027            (Self::Default, Self::None) => Self::None,
1028            (Self::Default, Self::Some(left)) => Self::Some(left),
1029            (Self::Default, Self::Default) => Self::Default,
1030        }
1031    }
1032}
1033
1034#[cfg(all(test, not(target_family = "wasm")))]
1035#[allow(clippy::dbg_macro)]
1036mod tests {
1037    use std::{
1038        collections::BTreeMap,
1039        future::ready,
1040        ops::Not,
1041        sync::{Arc, Mutex},
1042        time::Duration,
1043    };
1044
1045    use assert_matches::assert_matches;
1046    use event_listener::Listener;
1047    use futures_util::{StreamExt, future::join_all, pin_mut};
1048    use matrix_sdk_base::{RequestedRequiredStates, RoomMemberships};
1049    use matrix_sdk_common::executor::spawn;
1050    use matrix_sdk_test::{ALICE, async_test, event_factory::EventFactory};
1051    use ruma::{
1052        OwnedRoomId, assign,
1053        events::{direct::DirectEvent, room::member::MembershipState},
1054        owned_room_id,
1055        presence::PresenceState,
1056        profile::{
1057            AvatarUrl, DisplayName, ProfileFieldName, UserProfileChanges, UserProfileUpdate,
1058        },
1059        room_id,
1060        serde::Raw,
1061        uint,
1062    };
1063    use serde::Deserialize;
1064    use serde_json::json;
1065    use stream_assert::assert_pending;
1066    use wiremock::{
1067        Match, Mock, MockServer, Request, ResponseTemplate, http::Method, matchers::method,
1068    };
1069
1070    use super::{
1071        SlidingSync, SlidingSyncBuilder, SlidingSyncInternalMessage, SlidingSyncList,
1072        SlidingSyncListBuilder, SlidingSyncMode, cache::restore_sliding_sync_state, http,
1073    };
1074    use crate::{
1075        Client, Result,
1076        test_utils::{logged_in_client, mocks::MatrixMockServer},
1077    };
1078
1079    #[derive(Copy, Clone)]
1080    struct SlidingSyncMatcher;
1081
1082    impl Match for SlidingSyncMatcher {
1083        fn matches(&self, request: &Request) -> bool {
1084            request.url.path() == "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync"
1085                && request.method == Method::POST
1086        }
1087    }
1088
1089    async fn new_sliding_sync(
1090        lists: Vec<SlidingSyncListBuilder>,
1091    ) -> Result<(MockServer, SlidingSync)> {
1092        let server = MockServer::start().await;
1093        let client = logged_in_client(Some(server.uri())).await;
1094
1095        let mut sliding_sync_builder = client.sliding_sync("test-slidingsync")?;
1096
1097        for list in lists {
1098            sliding_sync_builder = sliding_sync_builder.add_list(list);
1099        }
1100
1101        let sliding_sync = sliding_sync_builder.build().await?;
1102
1103        Ok((server, sliding_sync))
1104    }
1105
1106    #[async_test]
1107    async fn test_subscribe_to_own_profile() {
1108        let client = logged_in_client(None).await;
1109        let own_user_id = client.user_id().expect("client should be logged in").to_owned();
1110
1111        // Given a stored global profile for the current user, received through a
1112        // previous sync.
1113        let mut response = http::Response::new("0".to_owned());
1114
1115        let mut profile_changes = UserProfileChanges::new();
1116        profile_changes.updated.insert(ProfileFieldName::DisplayName, json!("Example"));
1117
1118        response
1119            .extensions
1120            .profiles
1121            .users
1122            .insert(own_user_id.clone(), UserProfileUpdate::Updated(profile_changes));
1123        client
1124            .process_sliding_sync_test_helper(&response, &RequestedRequiredStates::default())
1125            .await
1126            .expect("Failed to process sync");
1127
1128        // Subscribing emits the currently stored value immediately.
1129        let stream = client.subscribe_to_own_profile().expect("client should be logged in");
1130        pin_mut!(stream);
1131
1132        let profile = stream.next().await.expect("should emit the initial profile");
1133        assert_eq!(profile.get_static::<DisplayName>().unwrap().as_deref(), Some("Example"));
1134
1135        // An update for another user only is ignored: nothing is emitted.
1136        let mut response = http::Response::new("1".to_owned());
1137
1138        let mut profile_changes = UserProfileChanges::new();
1139        profile_changes.updated.insert(ProfileFieldName::DisplayName, json!("Alice"));
1140        response
1141            .extensions
1142            .profiles
1143            .users
1144            .insert(ALICE.to_owned(), UserProfileUpdate::Updated(profile_changes));
1145        client
1146            .process_sliding_sync_test_helper(&response, &RequestedRequiredStates::default())
1147            .await
1148            .expect("Failed to process sync");
1149
1150        assert_pending!(stream);
1151
1152        // An update for the current user is emitted with the merged value.
1153        let mut response = http::Response::new("2".to_owned());
1154
1155        let mut profile_changes = UserProfileChanges::new();
1156        profile_changes
1157            .updated
1158            .insert(ProfileFieldName::AvatarUrl, json!("mxc://example.org/avatar"));
1159
1160        response
1161            .extensions
1162            .profiles
1163            .users
1164            .insert(own_user_id.clone(), UserProfileUpdate::Updated(profile_changes));
1165        client
1166            .process_sliding_sync_test_helper(&response, &RequestedRequiredStates::default())
1167            .await
1168            .expect("Failed to process sync");
1169
1170        let profile = stream.next().await.expect("should emit the updated profile");
1171        assert_eq!(profile.get_static::<DisplayName>().unwrap().as_deref(), Some("Example"));
1172        assert_eq!(
1173            profile.get_static::<AvatarUrl>().unwrap().map(|url| url.to_string()).as_deref(),
1174            Some("mxc://example.org/avatar")
1175        );
1176    }
1177
1178    #[async_test]
1179    async fn test_sliding_sync_request_uses_client_sync_presence() -> Result<()> {
1180        let (_server, sliding_sync) = new_sliding_sync(vec![]).await?;
1181        let client = sliding_sync.inner.client.clone();
1182
1183        {
1184            let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1185
1186            assert_eq!(request.set_presence, PresenceState::Online);
1187        }
1188
1189        client.set_presence(PresenceState::Unavailable, None, false).await?;
1190
1191        {
1192            let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1193
1194            assert_eq!(request.set_presence, PresenceState::Unavailable);
1195        }
1196
1197        client.set_presence(PresenceState::Offline, None, false).await?;
1198
1199        {
1200            let (request, _, _position_guard) = sliding_sync.generate_sync_request().await?;
1201
1202            assert_eq!(request.set_presence, PresenceState::Offline);
1203        }
1204
1205        Ok(())
1206    }
1207
1208    #[async_test]
1209    async fn test_add_room_subscriptions() -> Result<()> {
1210        let (server, sliding_sync) = new_sliding_sync(vec![
1211            SlidingSyncList::builder("foo")
1212                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1213        ])
1214        .await?;
1215
1216        let stream = sliding_sync.sync();
1217        pin_mut!(stream);
1218
1219        let room_id_0 = room_id!("!r0:bar.org");
1220        let room_id_1 = room_id!("!r1:bar.org");
1221        let room_id_2 = room_id!("!r2:bar.org");
1222
1223        {
1224            let _mock_guard = Mock::given(SlidingSyncMatcher)
1225                .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1226                    "pos": "1",
1227                    "lists": {},
1228                    "rooms": {
1229                        room_id_0: {
1230                            "name": "Room #0",
1231                            "initial": true,
1232                        },
1233                        room_id_1: {
1234                            "name": "Room #1",
1235                            "initial": true,
1236                        },
1237                        room_id_2: {
1238                            "name": "Room #2",
1239                            "initial": true,
1240                        },
1241                    }
1242                })))
1243                .mount_as_scoped(&server)
1244                .await;
1245
1246            let _ = stream.next().await.unwrap()?;
1247        }
1248
1249        let room0 = sliding_sync.inner.client.get_room(room_id_0).unwrap();
1250
1251        // Members aren't synced.
1252        // We need to make them synced, so that we can test that subscribing to a room
1253        // make members not synced. That's a desired feature.
1254        assert!(room0.are_members_synced().not());
1255
1256        {
1257            struct MemberMatcher(OwnedRoomId);
1258
1259            impl Match for MemberMatcher {
1260                fn matches(&self, request: &Request) -> bool {
1261                    request.url.path()
1262                        == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1263                        && request.method == Method::GET
1264                }
1265            }
1266
1267            let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1268                .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1269                    "chunk": [],
1270                })))
1271                .mount_as_scoped(&server)
1272                .await;
1273
1274            assert_matches!(room0.request_members().await, Ok(()));
1275        }
1276
1277        // Members are now synced! We can start subscribing and see how it goes.
1278        assert!(room0.are_members_synced());
1279
1280        sliding_sync.add_room_subscriptions(&[room_id_0, room_id_1], None, true);
1281
1282        // OK, we have subscribed to some rooms. Let's check on `room0` if members are
1283        // now marked as not synced.
1284        assert!(room0.are_members_synced().not());
1285
1286        {
1287            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1288
1289            assert!(room_subscriptions.contains_key(room_id_0));
1290            assert!(room_subscriptions.contains_key(room_id_1));
1291            assert!(!room_subscriptions.contains_key(room_id_2));
1292        }
1293
1294        // Subscribing to the same room doesn't reset the member sync state.
1295
1296        {
1297            struct MemberMatcher(OwnedRoomId);
1298
1299            impl Match for MemberMatcher {
1300                fn matches(&self, request: &Request) -> bool {
1301                    request.url.path()
1302                        == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1303                        && request.method == Method::GET
1304                }
1305            }
1306
1307            let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1308                .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1309                    "chunk": [],
1310                })))
1311                .mount_as_scoped(&server)
1312                .await;
1313
1314            assert_matches!(room0.request_members().await, Ok(()));
1315        }
1316
1317        // Members are synced, good, good.
1318        assert!(room0.are_members_synced());
1319
1320        sliding_sync.add_room_subscriptions(&[room_id_0], None, false);
1321
1322        // Members are still synced: because we have already subscribed to the
1323        // room, the members aren't marked as unsynced.
1324        assert!(room0.are_members_synced());
1325
1326        Ok(())
1327    }
1328
1329    #[async_test]
1330    async fn test_add_remove_and_reset_room_subscriptions() -> Result<()> {
1331        let (_server, sliding_sync) = new_sliding_sync(vec![
1332            SlidingSyncList::builder("foo")
1333                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1334        ])
1335        .await?;
1336
1337        let room_id_0 = room_id!("!r0:bar.org");
1338        let room_id_1 = room_id!("!r1:bar.org");
1339        let room_id_2 = room_id!("!r2:bar.org");
1340        let room_id_3 = room_id!("!r3:bar.org");
1341
1342        // Initially empty.
1343        {
1344            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1345
1346            assert!(room_subscriptions.is_empty());
1347        }
1348
1349        // Add 2 rooms.
1350        sliding_sync.add_room_subscriptions(&[room_id_0, room_id_1], Default::default(), false);
1351
1352        {
1353            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1354
1355            assert_eq!(room_subscriptions.len(), 2);
1356            assert!(room_subscriptions.contains_key(room_id_0));
1357            assert!(room_subscriptions.contains_key(room_id_1));
1358        }
1359
1360        // Remove 1 room.
1361        sliding_sync.remove_room_subscriptions(&[room_id_0], false);
1362
1363        {
1364            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1365
1366            assert_eq!(room_subscriptions.len(), 1);
1367            assert!(room_subscriptions.contains_key(room_id_1));
1368        }
1369
1370        // Add 2 rooms, but one already exists.
1371        sliding_sync.add_room_subscriptions(&[room_id_0, room_id_1], Default::default(), false);
1372
1373        {
1374            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1375
1376            assert_eq!(room_subscriptions.len(), 2);
1377            assert!(room_subscriptions.contains_key(room_id_0));
1378            assert!(room_subscriptions.contains_key(room_id_1));
1379        }
1380
1381        // Replace all rooms with 2 other rooms.
1382        sliding_sync.reset_and_add_room_subscriptions(
1383            &[room_id_2, room_id_3],
1384            Default::default(),
1385            false,
1386        );
1387
1388        {
1389            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1390
1391            assert_eq!(room_subscriptions.len(), 2);
1392            assert!(room_subscriptions.contains_key(room_id_2));
1393            assert!(room_subscriptions.contains_key(room_id_3));
1394        }
1395
1396        Ok(())
1397    }
1398
1399    #[async_test]
1400    async fn test_set_room_subscriptions_refreshes_the_settings() -> Result<()> {
1401        let (_server, sliding_sync) = new_sliding_sync(vec![
1402            SlidingSyncList::builder("foo")
1403                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1404        ])
1405        .await?;
1406
1407        let room_id_0 = room_id!("!r0:bar.org");
1408
1409        let settings = |timeline_limit: u32| {
1410            Some(assign!(http::request::RoomSubscription::default(), {
1411                timeline_limit: timeline_limit.into(),
1412            }))
1413        };
1414        let timeline_limit_of_room_0 = || {
1415            sliding_sync
1416                .inner
1417                .room_subscriptions
1418                .read()
1419                .unwrap()
1420                .get(room_id_0)
1421                .map(|subscription| subscription.timeline_limit)
1422        };
1423
1424        let mut internal_channel = sliding_sync.inner.internal_channel.subscribe();
1425
1426        // Subscribe for the first time.
1427        sliding_sync.set_room_subscriptions(&[room_id_0], settings(10), true);
1428
1429        assert_eq!(timeline_limit_of_room_0(), Some(10u32.into()));
1430        assert_matches!(
1431            internal_channel.try_recv(),
1432            Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1433        );
1434
1435        // Resubscribe with the same settings: nothing changes, no cancellation.
1436        sliding_sync.set_room_subscriptions(&[room_id_0], settings(10), true);
1437
1438        assert_eq!(timeline_limit_of_room_0(), Some(10u32.into()));
1439        assert!(internal_channel.try_recv().is_err());
1440
1441        // Resubscribe with new settings: they must be applied, and the in-flight
1442        // request must be cancelled so that they are sent right away.
1443        sliding_sync.set_room_subscriptions(&[room_id_0], settings(42), true);
1444
1445        assert_eq!(timeline_limit_of_room_0(), Some(42u32.into()));
1446        assert_matches!(
1447            internal_channel.try_recv(),
1448            Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1449        );
1450
1451        Ok(())
1452    }
1453
1454    #[async_test]
1455    async fn test_set_room_subscriptions_cancels_the_in_flight_request() -> Result<()> {
1456        let (_server, sliding_sync) = new_sliding_sync(vec![
1457            SlidingSyncList::builder("foo")
1458                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1459        ])
1460        .await?;
1461
1462        let room_id_0 = room_id!("!r0:bar.org");
1463        let room_id_1 = room_id!("!r1:bar.org");
1464
1465        let mut internal_channel = sliding_sync.inner.internal_channel.subscribe();
1466
1467        // A first-ever subscription: nothing is removed, but a subscription is added,
1468        // so the in-flight request must be cancelled.
1469        sliding_sync.set_room_subscriptions(&[room_id_0], None, true);
1470
1471        assert_matches!(
1472            internal_channel.try_recv(),
1473            Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1474        );
1475
1476        // Resubscribing to the same room: nothing is added nor removed, no
1477        // cancellation.
1478        sliding_sync.set_room_subscriptions(&[room_id_0], None, true);
1479
1480        assert!(internal_channel.try_recv().is_err());
1481
1482        // A subscription is removed: the in-flight request must be cancelled.
1483        sliding_sync.set_room_subscriptions(&[room_id_1], None, true);
1484
1485        assert_matches!(
1486            internal_channel.try_recv(),
1487            Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1488        );
1489
1490        // Finally, no cancellation is asked: no cancellation happens.
1491        sliding_sync.set_room_subscriptions(&[room_id_0], None, false);
1492
1493        assert!(internal_channel.try_recv().is_err());
1494
1495        Ok(())
1496    }
1497
1498    #[async_test]
1499    async fn test_reset_and_add_room_subscriptions_cancels_the_in_flight_request() -> Result<()> {
1500        let (_server, sliding_sync) = new_sliding_sync(vec![
1501            SlidingSyncList::builder("foo")
1502                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1503        ])
1504        .await?;
1505
1506        let room_id_0 = room_id!("!r0:bar.org");
1507
1508        let mut internal_channel = sliding_sync.inner.internal_channel.subscribe();
1509
1510        // Nothing is removed, nothing is added: no cancellation.
1511        sliding_sync.reset_and_add_room_subscriptions(&[], None, true);
1512
1513        assert!(internal_channel.try_recv().is_err());
1514
1515        // A subscription is added: the in-flight request must be cancelled.
1516        sliding_sync.reset_and_add_room_subscriptions(&[room_id_0], None, true);
1517
1518        assert_matches!(
1519            internal_channel.try_recv(),
1520            Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1521        );
1522
1523        // All the subscriptions are removed: the in-flight request must be cancelled.
1524        sliding_sync.reset_and_add_room_subscriptions(&[], None, true);
1525
1526        assert!(sliding_sync.inner.room_subscriptions.read().unwrap().is_empty());
1527        assert_matches!(
1528            internal_channel.try_recv(),
1529            Ok(SlidingSyncInternalMessage::SyncLoopSkipOverCurrentIteration)
1530        );
1531
1532        // Finally, no cancellation is asked: no cancellation happens.
1533        sliding_sync.reset_and_add_room_subscriptions(&[room_id_0], None, false);
1534
1535        assert!(internal_channel.try_recv().is_err());
1536
1537        Ok(())
1538    }
1539
1540    #[async_test]
1541    async fn test_set_room_subscriptions() -> Result<()> {
1542        let (server, sliding_sync) = new_sliding_sync(vec![
1543            SlidingSyncList::builder("foo")
1544                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1545        ])
1546        .await?;
1547
1548        let stream = sliding_sync.sync();
1549        pin_mut!(stream);
1550
1551        let room_id_0 = room_id!("!r0:bar.org");
1552        let room_id_1 = room_id!("!r1:bar.org");
1553        let room_id_2 = room_id!("!r2:bar.org");
1554
1555        {
1556            let _mock_guard = Mock::given(SlidingSyncMatcher)
1557                .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1558                    "pos": "1",
1559                    "lists": {},
1560                    "rooms": {
1561                        room_id_0: {
1562                            "name": "Room #0",
1563                            "initial": true,
1564                        },
1565                        room_id_1: {
1566                            "name": "Room #1",
1567                            "initial": true,
1568                        },
1569                        room_id_2: {
1570                            "name": "Room #2",
1571                            "initial": true,
1572                        },
1573                    }
1574                })))
1575                .mount_as_scoped(&server)
1576                .await;
1577
1578            let _ = stream.next().await.unwrap()?;
1579        }
1580
1581        let room0 = sliding_sync.inner.client.get_room(room_id_0).unwrap();
1582
1583        // Members aren't synced.
1584        // We need to make them synced, so that we can test that subscribing to a room
1585        // make members not synced. That's a desired feature.
1586        assert!(room0.are_members_synced().not());
1587
1588        {
1589            struct MemberMatcher(OwnedRoomId);
1590
1591            impl Match for MemberMatcher {
1592                fn matches(&self, request: &Request) -> bool {
1593                    request.url.path()
1594                        == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1595                        && request.method == Method::GET
1596                }
1597            }
1598
1599            let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1600                .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1601                    "chunk": [],
1602                })))
1603                .mount_as_scoped(&server)
1604                .await;
1605
1606            assert_matches!(room0.request_members().await, Ok(()));
1607        }
1608
1609        // Members are now synced! We can start subscribing and see how it goes.
1610        assert!(room0.are_members_synced());
1611
1612        sliding_sync.set_room_subscriptions(&[room_id_0, room_id_1], None, true);
1613
1614        // OK, we have subscribed to some rooms. Let's check on `room0` if members are
1615        // now marked as not synced.
1616        assert!(room0.are_members_synced().not());
1617
1618        // Both resubscribed rooms are subscribed.
1619        {
1620            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1621
1622            assert!(room_subscriptions.contains_key(room_id_0));
1623            assert!(room_subscriptions.contains_key(room_id_1));
1624            assert!(!room_subscriptions.contains_key(room_id_2));
1625        }
1626
1627        // Subscribing to the same room doesn't reset the member sync state.
1628
1629        {
1630            struct MemberMatcher(OwnedRoomId);
1631
1632            impl Match for MemberMatcher {
1633                fn matches(&self, request: &Request) -> bool {
1634                    request.url.path()
1635                        == format!("/_matrix/client/r0/rooms/{room_id}/members", room_id = self.0)
1636                        && request.method == Method::GET
1637                }
1638            }
1639
1640            let _mock_guard = Mock::given(MemberMatcher(room_id_0.to_owned()))
1641                .respond_with(ResponseTemplate::new(200).set_body_json(json!({
1642                    "chunk": [],
1643                })))
1644                .mount_as_scoped(&server)
1645                .await;
1646
1647            assert_matches!(room0.request_members().await, Ok(()));
1648        }
1649
1650        // Members are synced, good, good.
1651        assert!(room0.are_members_synced());
1652
1653        sliding_sync.set_room_subscriptions(&[room_id_0], None, false);
1654
1655        // Members are still synced: because we have already subscribed to the
1656        // room, the members aren't marked as unsynced.
1657        assert!(room0.are_members_synced());
1658
1659        // Only the resubscribed room is subscribed.
1660        {
1661            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1662
1663            assert!(room_subscriptions.contains_key(room_id_0));
1664            assert!(!room_subscriptions.contains_key(room_id_1));
1665            assert!(!room_subscriptions.contains_key(room_id_2));
1666        }
1667        Ok(())
1668    }
1669
1670    #[async_test]
1671    async fn test_room_subscriptions_are_reset_when_session_expires() -> Result<()> {
1672        let (_server, sliding_sync) = new_sliding_sync(vec![
1673            SlidingSyncList::builder("foo")
1674                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1675        ])
1676        .await?;
1677
1678        let room_id_0 = room_id!("!r0:bar.org");
1679        let room_id_1 = room_id!("!r1:bar.org");
1680        let room_id_2 = room_id!("!r2:bar.org");
1681
1682        // Subscribe to two rooms.
1683        sliding_sync.add_room_subscriptions(&[room_id_0, room_id_1], None, false);
1684
1685        {
1686            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1687
1688            assert!(room_subscriptions.contains_key(room_id_0));
1689            assert!(room_subscriptions.contains_key(room_id_1));
1690            assert!(room_subscriptions.contains_key(room_id_2).not());
1691        }
1692
1693        // Subscribe to one more room.
1694        sliding_sync.add_room_subscriptions(&[room_id_2], None, false);
1695
1696        {
1697            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1698
1699            assert!(room_subscriptions.contains_key(room_id_0));
1700            assert!(room_subscriptions.contains_key(room_id_1));
1701            assert!(room_subscriptions.contains_key(room_id_2));
1702        }
1703
1704        // Suddenly, the session expires!
1705        sliding_sync.expire_session().await;
1706
1707        {
1708            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1709
1710            assert!(room_subscriptions.is_empty());
1711        }
1712
1713        // Subscribe to one room again.
1714        sliding_sync.add_room_subscriptions(&[room_id_2], None, false);
1715
1716        {
1717            let room_subscriptions = sliding_sync.inner.room_subscriptions.read().unwrap();
1718
1719            assert!(room_subscriptions.contains_key(room_id_0).not());
1720            assert!(room_subscriptions.contains_key(room_id_1).not());
1721            assert!(room_subscriptions.contains_key(room_id_2));
1722        }
1723
1724        Ok(())
1725    }
1726
1727    #[async_test]
1728    async fn test_add_list() -> Result<()> {
1729        let (_server, sliding_sync) = new_sliding_sync(vec![
1730            SlidingSyncList::builder("foo")
1731                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
1732        ])
1733        .await?;
1734
1735        let _stream = sliding_sync.sync();
1736        pin_mut!(_stream);
1737
1738        sliding_sync
1739            .add_list(
1740                SlidingSyncList::builder("bar")
1741                    .sync_mode(SlidingSyncMode::new_selective().add_range(50..=60)),
1742            )
1743            .await?;
1744
1745        let lists = sliding_sync.inner.lists.read().await;
1746
1747        assert!(lists.contains_key("foo"));
1748        assert!(lists.contains_key("bar"));
1749
1750        // this test also ensures that Tokio is not panicking when calling `add_list`.
1751
1752        Ok(())
1753    }
1754
1755    #[cfg(feature = "e2e-encryption")]
1756    #[async_test]
1757    async fn test_extensions_to_device_since_is_set() {
1758        use matrix_sdk_base::crypto::store::types::Changes;
1759
1760        let client = logged_in_client(None).await;
1761        let sliding_sync = SlidingSyncBuilder::new("foo".to_owned(), client.clone())
1762            .unwrap()
1763            .with_to_device_extension(assign!(
1764                http::request::ToDevice::default(),
1765                {
1766                    enabled: Some(true),
1767                }
1768            ))
1769            .build()
1770            .await
1771            .unwrap();
1772
1773        // Test `SlidingSyncInner::extensions`.
1774        {
1775            let to_device = &sliding_sync.inner.extensions.to_device;
1776
1777            assert_eq!(to_device.enabled, Some(true));
1778            assert!(to_device.since.is_none());
1779        }
1780
1781        // Test `Request::extensions`.
1782        {
1783            let (request, _, _) = sliding_sync.generate_sync_request().await.unwrap();
1784
1785            let to_device = &request.extensions.to_device;
1786
1787            assert_eq!(to_device.enabled, Some(true));
1788            assert!(to_device.since.is_none());
1789        }
1790
1791        // Define a `since` token.
1792        let since_token = "depuis".to_owned();
1793
1794        {
1795            if let Some(olm_machine) = &*client.olm_machine().await {
1796                olm_machine
1797                    .store()
1798                    .save_changes(Changes {
1799                        next_batch_token: Some(since_token.clone()),
1800                        ..Default::default()
1801                    })
1802                    .await
1803                    .unwrap();
1804            } else {
1805                panic!("Where is the Olm machine?");
1806            }
1807        }
1808
1809        // Test `Request::extensions` again.
1810        {
1811            let (request, _, _) = sliding_sync.generate_sync_request().await.unwrap();
1812
1813            let to_device = &request.extensions.to_device;
1814
1815            assert_eq!(to_device.enabled, Some(true));
1816            assert_eq!(to_device.since, Some(since_token));
1817        }
1818    }
1819
1820    // With MSC4186, with the `e2ee` extension enabled, if a request has no `pos`,
1821    // all the tracked users by the `OlmMachine` must be marked as dirty, i.e.
1822    // `/key/query` requests must be sent. See the code to see the details.
1823    //
1824    // This test is asserting that.
1825    #[async_test]
1826    #[cfg(feature = "e2e-encryption")]
1827    async fn test_no_pos_with_e2ee_marks_all_tracked_users_as_dirty() -> anyhow::Result<()> {
1828        use matrix_sdk_base::crypto::types::requests::{AnyIncomingResponse, AnyOutgoingRequest};
1829        use matrix_sdk_test::ruma_response_from_json;
1830        use ruma::user_id;
1831
1832        let server = MockServer::start().await;
1833        let client = logged_in_client(Some(server.uri())).await;
1834
1835        let alice = user_id!("@alice:localhost");
1836        let bob = user_id!("@bob:localhost");
1837        let me = user_id!("@example:localhost");
1838
1839        // Track and mark users are not dirty, so that we can check they are “dirty”
1840        // after that. Dirty here means that a `/key/query` must be sent.
1841        {
1842            let olm_machine = client.olm_machine().await;
1843            let olm_machine = olm_machine.as_ref().unwrap();
1844
1845            olm_machine.update_tracked_users([alice, bob]).await?;
1846
1847            // Assert requests.
1848            let outgoing_requests = olm_machine.outgoing_requests().await?;
1849
1850            assert_eq!(outgoing_requests.len(), 2);
1851            assert_matches!(outgoing_requests[0].request(), AnyOutgoingRequest::KeysUpload(_));
1852            assert_matches!(outgoing_requests[1].request(), AnyOutgoingRequest::KeysQuery(_));
1853
1854            // Fake responses.
1855            olm_machine
1856                .mark_request_as_sent(
1857                    outgoing_requests[0].request_id(),
1858                    AnyIncomingResponse::KeysUpload(&ruma_response_from_json(&json!({
1859                        "one_time_key_counts": {}
1860                    }))),
1861                )
1862                .await?;
1863
1864            olm_machine
1865                .mark_request_as_sent(
1866                    outgoing_requests[1].request_id(),
1867                    AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1868                        "device_keys": {
1869                            alice: {},
1870                            bob: {},
1871                        }
1872                    }))),
1873                )
1874                .await?;
1875
1876            // Once more.
1877            let outgoing_requests = olm_machine.outgoing_requests().await?;
1878
1879            assert_eq!(outgoing_requests.len(), 1);
1880            assert_matches!(outgoing_requests[0].request(), AnyOutgoingRequest::KeysQuery(_));
1881
1882            olm_machine
1883                .mark_request_as_sent(
1884                    outgoing_requests[0].request_id(),
1885                    AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1886                        "device_keys": {
1887                            me: {},
1888                        }
1889                    }))),
1890                )
1891                .await?;
1892
1893            // No more.
1894            let outgoing_requests = olm_machine.outgoing_requests().await?;
1895
1896            assert!(outgoing_requests.is_empty());
1897        }
1898
1899        let sync = client
1900            .sliding_sync("test-slidingsync")?
1901            .add_list(SlidingSyncList::builder("new_list"))
1902            .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
1903            .build()
1904            .await?;
1905
1906        // First request: no `pos`.
1907        let (_request, _, _) = sync.generate_sync_request().await?;
1908
1909        // Now, tracked users must be dirty.
1910        {
1911            let olm_machine = client.olm_machine().await;
1912            let olm_machine = olm_machine.as_ref().unwrap();
1913
1914            // Assert requests.
1915            let outgoing_requests = olm_machine.outgoing_requests().await?;
1916
1917            assert_eq!(outgoing_requests.len(), 1);
1918            assert_matches!(
1919                outgoing_requests[0].request(),
1920                AnyOutgoingRequest::KeysQuery(request) => {
1921                    assert!(request.device_keys.contains_key(alice));
1922                    assert!(request.device_keys.contains_key(bob));
1923                    assert!(request.device_keys.contains_key(me));
1924                }
1925            );
1926
1927            // Fake responses.
1928            olm_machine
1929                .mark_request_as_sent(
1930                    outgoing_requests[0].request_id(),
1931                    AnyIncomingResponse::KeysQuery(&ruma_response_from_json(&json!({
1932                        "device_keys": {
1933                            alice: {},
1934                            bob: {},
1935                            me: {},
1936                        }
1937                    }))),
1938                )
1939                .await?;
1940        }
1941
1942        // Second request: with a `pos` this time.
1943        sync.set_pos("chocolat".to_owned()).await;
1944
1945        let (_request, _, _) = sync.generate_sync_request().await?;
1946
1947        // Tracked users are not marked as dirty.
1948        {
1949            let olm_machine = client.olm_machine().await;
1950            let olm_machine = olm_machine.as_ref().unwrap();
1951
1952            // Assert requests.
1953            let outgoing_requests = olm_machine.outgoing_requests().await?;
1954
1955            assert!(outgoing_requests.is_empty());
1956        }
1957
1958        Ok(())
1959    }
1960
1961    #[cfg(feature = "e2e-encryption")]
1962    #[async_test]
1963    async fn test_sliding_sync_doesnt_remember_pos() -> Result<()> {
1964        let server = MockServer::start().await;
1965
1966        #[derive(Deserialize)]
1967        struct PartialRequest {
1968            txn_id: Option<String>,
1969        }
1970
1971        let server_pos = Arc::new(Mutex::new(0));
1972        let _mock_guard = Mock::given(SlidingSyncMatcher)
1973            .respond_with(move |request: &Request| {
1974                // Repeat the txn_id in the response, if set.
1975                let request: PartialRequest = request.body_json().unwrap();
1976                let pos = {
1977                    let mut pos = server_pos.lock().unwrap();
1978                    let prev = *pos;
1979                    *pos += 1;
1980                    prev
1981                };
1982
1983                ResponseTemplate::new(200).set_body_json(json!({
1984                    "txn_id": request.txn_id,
1985                    "pos": pos.to_string(),
1986                }))
1987            })
1988            .mount_as_scoped(&server)
1989            .await;
1990
1991        let client = logged_in_client(Some(server.uri())).await;
1992
1993        let sliding_sync = client.sliding_sync("forgetful-sync")?.build().await?;
1994
1995        // `pos` is `None` to start with.
1996        {
1997            assert!(sliding_sync.inner.position.lock().await.pos.is_none());
1998
1999            let (request, _, _) = sliding_sync.generate_sync_request().await?;
2000            assert!(request.pos.is_none());
2001        }
2002
2003        let sync = sliding_sync.sync();
2004        pin_mut!(sync);
2005
2006        // Sync goes well, and then the position is saved both into the internal memory
2007        // and the database.
2008        let next = sync.next().await;
2009        assert_matches!(next, Some(Ok(_update_summary)));
2010
2011        assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("0"));
2012
2013        let restored_fields = restore_sliding_sync_state(&client, &sliding_sync.inner.storage_key)
2014            .await?
2015            .expect("must have restored fields");
2016
2017        // While it has been saved into the database, it's not necessarily going to be
2018        // used later!
2019        assert_eq!(restored_fields.pos.as_deref(), Some("0"));
2020
2021        // Now, even if we mess with the position stored in the database, the sliding
2022        // sync instance isn't configured to reload the stream position from the
2023        // database, so it won't be changed.
2024        {
2025            let other_sync = client.sliding_sync("forgetful-sync")?.build().await?;
2026
2027            let mut position_guard = other_sync.inner.position.lock().await;
2028            position_guard.pos = Some("yolo".to_owned());
2029
2030            other_sync.cache_to_storage(&position_guard).await?;
2031        }
2032
2033        // It's still 0, not "yolo".
2034        {
2035            assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("0"));
2036            let (request, _, _) = sliding_sync.generate_sync_request().await?;
2037            assert_eq!(request.pos.as_deref(), Some("0"));
2038        }
2039
2040        // Recreating a sliding sync with the same ID doesn't preload the pos, if not
2041        // asked to.
2042        {
2043            let sliding_sync = client.sliding_sync("forgetful-sync")?.build().await?;
2044            assert!(sliding_sync.inner.position.lock().await.pos.is_none());
2045        }
2046
2047        Ok(())
2048    }
2049
2050    #[cfg(feature = "e2e-encryption")]
2051    #[async_test]
2052    async fn test_sliding_sync_does_remember_pos() -> Result<()> {
2053        let server = MockServer::start().await;
2054
2055        #[derive(Deserialize)]
2056        struct PartialRequest {
2057            txn_id: Option<String>,
2058        }
2059
2060        let server_pos = Arc::new(Mutex::new(0));
2061        let _mock_guard = Mock::given(SlidingSyncMatcher)
2062            .respond_with(move |request: &Request| {
2063                // Repeat the txn_id in the response, if set.
2064                let request: PartialRequest = request.body_json().unwrap();
2065                let pos = {
2066                    let mut pos = server_pos.lock().unwrap();
2067                    let prev = *pos;
2068                    *pos += 1;
2069                    prev
2070                };
2071
2072                ResponseTemplate::new(200).set_body_json(json!({
2073                    "txn_id": request.txn_id,
2074                    "pos": pos.to_string(),
2075                }))
2076            })
2077            .mount_as_scoped(&server)
2078            .await;
2079
2080        let client = logged_in_client(Some(server.uri())).await;
2081
2082        let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
2083
2084        // `pos` is `None` to start with.
2085        {
2086            let (request, _, _) = sliding_sync.generate_sync_request().await?;
2087
2088            assert!(request.pos.is_none());
2089            assert!(sliding_sync.inner.position.lock().await.pos.is_none());
2090        }
2091
2092        let sync = sliding_sync.sync();
2093        pin_mut!(sync);
2094
2095        // Sync goes well, and then the position is saved both into the internal memory
2096        // and the database.
2097        let next = sync.next().await;
2098        assert_matches!(next, Some(Ok(_update_summary)));
2099
2100        assert_eq!(sliding_sync.inner.position.lock().await.pos, Some("0".to_owned()));
2101
2102        let restored_fields = restore_sliding_sync_state(&client, &sliding_sync.inner.storage_key)
2103            .await?
2104            .expect("must have restored fields");
2105
2106        // While it has been saved into the database, it's not necessarily going to be
2107        // used later!
2108        assert_eq!(restored_fields.pos.as_deref(), Some("0"));
2109
2110        // Another process modifies the stream position under our feet...
2111        {
2112            let other_sync = client.sliding_sync("elephant-sync")?.build().await?;
2113
2114            let mut position_guard = other_sync.inner.position.lock().await;
2115            position_guard.pos = Some("42".to_owned());
2116
2117            other_sync.cache_to_storage(&position_guard).await?;
2118        }
2119
2120        // It's alright, the next request will load it from the database.
2121        {
2122            let (request, _, _) = sliding_sync.generate_sync_request().await?;
2123            assert_eq!(request.pos.as_deref(), Some("42"));
2124            assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("42"));
2125        }
2126
2127        // Recreating a sliding sync with the same ID will reload it too.
2128        {
2129            let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
2130            assert_eq!(sliding_sync.inner.position.lock().await.pos.as_deref(), Some("42"));
2131
2132            let (request, _, _) = sliding_sync.generate_sync_request().await?;
2133            assert_eq!(request.pos.as_deref(), Some("42"));
2134        }
2135
2136        // Invalidating the session will remove the in-memory value AND the database
2137        // value.
2138        sliding_sync.expire_session().await;
2139
2140        {
2141            assert!(sliding_sync.inner.position.lock().await.pos.is_none());
2142
2143            let (request, _, _) = sliding_sync.generate_sync_request().await?;
2144            assert!(request.pos.is_none());
2145        }
2146
2147        // And new sliding syncs with the same ID won't find it either.
2148        {
2149            let sliding_sync = client.sliding_sync("elephant-sync")?.share_pos().build().await?;
2150            assert!(sliding_sync.inner.position.lock().await.pos.is_none());
2151
2152            let (request, _, _) = sliding_sync.generate_sync_request().await?;
2153            assert!(request.pos.is_none());
2154        }
2155
2156        Ok(())
2157    }
2158
2159    #[async_test]
2160    async fn test_stop_sync_loop() -> Result<()> {
2161        let (_server, sliding_sync) = new_sliding_sync(vec![
2162            SlidingSyncList::builder("foo")
2163                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2164        ])
2165        .await?;
2166
2167        // Start the sync loop.
2168        let stream = sliding_sync.sync();
2169        pin_mut!(stream);
2170
2171        // The sync loop is actually running.
2172        assert!(stream.next().await.is_some());
2173
2174        // Stop the sync loop.
2175        sliding_sync.stop_sync()?;
2176
2177        // The sync loop is actually stopped.
2178        assert!(stream.next().await.is_none());
2179
2180        // Start a new sync loop.
2181        let stream = sliding_sync.sync();
2182        pin_mut!(stream);
2183
2184        // The sync loop is actually running.
2185        assert!(stream.next().await.is_some());
2186
2187        Ok(())
2188    }
2189
2190    #[async_test]
2191    async fn test_process_read_receipts() -> Result<()> {
2192        let room = owned_room_id!("!pony:example.org");
2193
2194        let server = MockServer::start().await;
2195        let client = logged_in_client(Some(server.uri())).await;
2196        client.event_cache().subscribe().unwrap();
2197
2198        let sliding_sync = client
2199            .sliding_sync("test")?
2200            .with_receipt_extension(
2201                assign!(http::request::Receipts::default(), { enabled: Some(true) }),
2202            )
2203            .add_list(
2204                SlidingSyncList::builder("all")
2205                    .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2206            )
2207            .build()
2208            .await?;
2209
2210        // Initial state.
2211        {
2212            let server_response = assign!(http::Response::new("0".to_owned()), {
2213                rooms: BTreeMap::from([(
2214                    room.clone(),
2215                    http::response::Room::default(),
2216                )])
2217            });
2218
2219            let _summary = {
2220                let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2221                sliding_sync
2222                    .handle_response(
2223                        server_response.clone(),
2224                        &mut pos_guard,
2225                        RequestedRequiredStates::default(),
2226                    )
2227                    .await?
2228            };
2229        }
2230
2231        let server_response = assign!(http::Response::new("1".to_owned()), {
2232            extensions: assign!(http::response::Extensions::default(), {
2233                receipts: assign!(http::response::Receipts::default(), {
2234                    rooms: BTreeMap::from([
2235                        (
2236                            room.clone(),
2237                            Raw::from_json_string(
2238                                json!({
2239                                    "room_id": room,
2240                                    "type": "m.receipt",
2241                                    "content": {
2242                                        "$event:bar.org": {
2243                                            "m.read": {
2244                                                client.user_id().unwrap(): {
2245                                                    "ts": 1436451550,
2246                                                }
2247                                            }
2248                                        }
2249                                    }
2250                                })
2251                                .to_string(),
2252                            ).unwrap()
2253                        )
2254                    ])
2255                })
2256            })
2257        });
2258
2259        let summary = {
2260            let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2261            sliding_sync
2262                .handle_response(
2263                    server_response.clone(),
2264                    &mut pos_guard,
2265                    RequestedRequiredStates::default(),
2266                )
2267                .await?
2268        };
2269
2270        assert!(summary.rooms.contains(&room));
2271
2272        Ok(())
2273    }
2274
2275    #[async_test]
2276    async fn test_process_marked_unread_room_account_data() -> Result<()> {
2277        let room_id = owned_room_id!("!unicorn:example.org");
2278
2279        let server = MockServer::start().await;
2280        let client = logged_in_client(Some(server.uri())).await;
2281
2282        // Setup sliding sync with with one room and one list
2283
2284        let sliding_sync = client
2285            .sliding_sync("test")?
2286            .with_account_data_extension(
2287                assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2288            )
2289            .add_list(
2290                SlidingSyncList::builder("all")
2291                    .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2292            )
2293            .build()
2294            .await?;
2295
2296        // Initial state.
2297        {
2298            let server_response = assign!(http::Response::new("0".to_owned()), {
2299                rooms: BTreeMap::from([(
2300                    room_id.clone(),
2301                    http::response::Room::default(),
2302                )])
2303            });
2304
2305            let _summary = {
2306                let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2307                sliding_sync
2308                    .handle_response(
2309                        server_response.clone(),
2310                        &mut pos_guard,
2311                        RequestedRequiredStates::default(),
2312                    )
2313                    .await?
2314            };
2315        }
2316
2317        // Simulate a response that only changes the marked unread state of the room to
2318        // true
2319
2320        let server_response = make_mark_unread_response("1", room_id.clone(), true, false);
2321
2322        let update_summary = {
2323            let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2324            sliding_sync
2325                .handle_response(
2326                    server_response.clone(),
2327                    &mut pos_guard,
2328                    RequestedRequiredStates::default(),
2329                )
2330                .await?
2331        };
2332
2333        // Check that the list list and entry received the update
2334
2335        assert!(update_summary.rooms.contains(&room_id));
2336
2337        let room = client.get_room(&room_id).unwrap();
2338
2339        // Check the actual room data, this powers RoomInfo
2340
2341        assert!(room.is_marked_unread());
2342
2343        // Change it back to false and check if it updates
2344
2345        let server_response = make_mark_unread_response("2", room_id.clone(), false, true);
2346
2347        let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2348        sliding_sync
2349            .handle_response(
2350                server_response.clone(),
2351                &mut pos_guard,
2352                RequestedRequiredStates::default(),
2353            )
2354            .await?;
2355
2356        let room = client.get_room(&room_id).unwrap();
2357
2358        assert!(!room.is_marked_unread());
2359
2360        Ok(())
2361    }
2362
2363    fn make_mark_unread_response(
2364        response_number: &str,
2365        room_id: OwnedRoomId,
2366        unread: bool,
2367        add_rooms_section: bool,
2368    ) -> http::Response {
2369        let rooms = if add_rooms_section {
2370            BTreeMap::from([(room_id.clone(), http::response::Room::default())])
2371        } else {
2372            BTreeMap::new()
2373        };
2374
2375        let extensions = assign!(http::response::Extensions::default(), {
2376            account_data: assign!(http::response::AccountData::default(), {
2377                rooms: BTreeMap::from([
2378                    (
2379                        room_id,
2380                        vec![
2381                            Raw::from_json_string(
2382                                json!({
2383                                    "content": {
2384                                        "unread": unread
2385                                    },
2386                                    "type": "m.marked_unread"
2387                                })
2388                                .to_string(),
2389                            ).unwrap()
2390                        ]
2391                    )
2392                ])
2393            })
2394        });
2395
2396        assign!(http::Response::new(response_number.to_owned()), { rooms: rooms, extensions: extensions })
2397    }
2398
2399    #[async_test]
2400    async fn test_process_rooms_account_data() -> Result<()> {
2401        let room = owned_room_id!("!pony:example.org");
2402
2403        let server = MockServer::start().await;
2404        let client = logged_in_client(Some(server.uri())).await;
2405
2406        let sliding_sync = client
2407            .sliding_sync("test")?
2408            .with_account_data_extension(
2409                assign!(http::request::AccountData::default(), { enabled: Some(true) }),
2410            )
2411            .add_list(
2412                SlidingSyncList::builder("all")
2413                    .sync_mode(SlidingSyncMode::new_selective().add_range(0..=100)),
2414            )
2415            .build()
2416            .await?;
2417
2418        // Initial state.
2419        {
2420            let server_response = assign!(http::Response::new("0".to_owned()), {
2421                rooms: BTreeMap::from([(
2422                    room.clone(),
2423                    http::response::Room::default(),
2424                )])
2425            });
2426
2427            let _summary = {
2428                let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2429                sliding_sync
2430                    .handle_response(
2431                        server_response.clone(),
2432                        &mut pos_guard,
2433                        RequestedRequiredStates::default(),
2434                    )
2435                    .await?
2436            };
2437        }
2438
2439        let server_response = assign!(http::Response::new("1".to_owned()), {
2440            extensions: assign!(http::response::Extensions::default(), {
2441                account_data: assign!(http::response::AccountData::default(), {
2442                    rooms: BTreeMap::from([
2443                        (
2444                            room.clone(),
2445                            vec![
2446                                Raw::from_json_string(
2447                                    json!({
2448                                        "content": {
2449                                            "tags": {
2450                                                "u.work": {
2451                                                    "order": 0.9
2452                                                }
2453                                            }
2454                                        },
2455                                        "type": "m.tag"
2456                                    })
2457                                    .to_string(),
2458                                ).unwrap()
2459                            ]
2460                        )
2461                    ])
2462                })
2463            })
2464        });
2465        let summary = {
2466            let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2467            sliding_sync
2468                .handle_response(
2469                    server_response.clone(),
2470                    &mut pos_guard,
2471                    RequestedRequiredStates::default(),
2472                )
2473                .await?
2474        };
2475
2476        assert!(summary.rooms.contains(&room));
2477
2478        Ok(())
2479    }
2480
2481    #[async_test]
2482    #[cfg(feature = "e2e-encryption")]
2483    async fn test_process_only_encryption_events() -> Result<()> {
2484        use ruma::OneTimeKeyAlgorithm;
2485
2486        let room = owned_room_id!("!croissant:example.org");
2487
2488        let server = MockServer::start().await;
2489        let client = logged_in_client(Some(server.uri())).await;
2490
2491        let server_response = assign!(http::Response::new("0".to_owned()), {
2492            rooms: BTreeMap::from([(
2493                room.clone(),
2494                assign!(http::response::Room::default(), {
2495                    name: Some("Croissants lovers".to_owned()),
2496                    timeline: Vec::new(),
2497                }),
2498            )]),
2499
2500            extensions: assign!(http::response::Extensions::default(), {
2501                e2ee: assign!(http::response::E2EE::default(), {
2502                    device_one_time_keys_count: BTreeMap::from([(OneTimeKeyAlgorithm::SignedCurve25519, uint!(42))])
2503                }),
2504                to_device: Some(assign!(http::response::ToDevice::default(), {
2505                    next_batch: "to-device-token".to_owned(),
2506                })),
2507            })
2508        });
2509
2510        // Don't process non-encryption events if the sliding sync is configured for
2511        // encryption only.
2512
2513        let sliding_sync = client
2514            .sliding_sync("test")?
2515            .with_to_device_extension(
2516                assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2517            )
2518            .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2519            .build()
2520            .await?;
2521
2522        {
2523            let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2524
2525            sliding_sync
2526                .handle_response(
2527                    server_response.clone(),
2528                    &mut position_guard,
2529                    RequestedRequiredStates::default(),
2530                )
2531                .await?;
2532        }
2533
2534        // E2EE has been properly handled.
2535        let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2536        assert_eq!(uploaded_key_count, 42);
2537
2538        {
2539            let olm_machine = &*client.olm_machine_for_testing().await;
2540            assert_eq!(
2541                olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2542                Some("to-device-token")
2543            );
2544        }
2545
2546        // Room events haven't.
2547        assert!(client.get_room(&room).is_none());
2548
2549        // Conversely, only process room lists events if the sliding sync was configured
2550        // as so.
2551        let client = logged_in_client(Some(server.uri())).await;
2552
2553        let sliding_sync = client
2554            .sliding_sync("test")?
2555            .add_list(SlidingSyncList::builder("thelist"))
2556            .build()
2557            .await?;
2558
2559        {
2560            let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2561
2562            sliding_sync
2563                .handle_response(
2564                    server_response.clone(),
2565                    &mut position_guard,
2566                    RequestedRequiredStates::default(),
2567                )
2568                .await?;
2569        }
2570
2571        // E2EE response has been ignored.
2572        let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2573        assert_eq!(uploaded_key_count, 0);
2574
2575        {
2576            let olm_machine = &*client.olm_machine_for_testing().await;
2577            assert_eq!(
2578                olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2579                None
2580            );
2581        }
2582
2583        // The room is now known.
2584        assert!(client.get_room(&room).is_some());
2585
2586        // And it's also possible to set up both.
2587        let client = logged_in_client(Some(server.uri())).await;
2588
2589        let sliding_sync = client
2590            .sliding_sync("test")?
2591            .add_list(SlidingSyncList::builder("thelist"))
2592            .with_to_device_extension(
2593                assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2594            )
2595            .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2596            .build()
2597            .await?;
2598
2599        {
2600            let mut position_guard = sliding_sync.inner.position.clone().lock_owned().await;
2601
2602            sliding_sync
2603                .handle_response(
2604                    server_response.clone(),
2605                    &mut position_guard,
2606                    RequestedRequiredStates::default(),
2607                )
2608                .await?;
2609        }
2610
2611        // E2EE has been properly handled.
2612        let uploaded_key_count = client.encryption().uploaded_key_count().await?;
2613        assert_eq!(uploaded_key_count, 42);
2614
2615        {
2616            let olm_machine = &*client.olm_machine_for_testing().await;
2617            assert_eq!(
2618                olm_machine.as_ref().unwrap().store().next_batch_token().await?.as_deref(),
2619                Some("to-device-token")
2620            );
2621        }
2622
2623        // The room is now known.
2624        assert!(client.get_room(&room).is_some());
2625
2626        Ok(())
2627    }
2628
2629    #[async_test]
2630    async fn test_lock_multiple_requests() -> Result<()> {
2631        let server = MockServer::start().await;
2632        let client = logged_in_client(Some(server.uri())).await;
2633
2634        let pos = Arc::new(Mutex::new(0));
2635        let _mock_guard = Mock::given(SlidingSyncMatcher)
2636            .respond_with(move |_: &Request| {
2637                let mut pos = pos.lock().unwrap();
2638                *pos += 1;
2639                ResponseTemplate::new(200).set_body_json(json!({
2640                    "pos": pos.to_string(),
2641                    "lists": {},
2642                    "rooms": {}
2643                }))
2644            })
2645            .mount_as_scoped(&server)
2646            .await;
2647
2648        let sliding_sync = client
2649            .sliding_sync("test")?
2650            .with_to_device_extension(
2651                assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2652            )
2653            .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2654            .build()
2655            .await?;
2656
2657        // Spawn two requests in parallel. Before #2430, this lead to a deadlock and the
2658        // test would never terminate.
2659        let requests = join_all([sliding_sync.sync_once(), sliding_sync.sync_once()]);
2660
2661        for result in requests.await {
2662            result?;
2663        }
2664
2665        Ok(())
2666    }
2667
2668    #[async_test]
2669    async fn test_aborted_request_doesnt_update_future_requests() -> Result<()> {
2670        let server = MockServer::start().await;
2671        let client = logged_in_client(Some(server.uri())).await;
2672
2673        let pos = Arc::new(Mutex::new(0));
2674        let _mock_guard = Mock::given(SlidingSyncMatcher)
2675            .respond_with(move |_: &Request| {
2676                let mut pos = pos.lock().unwrap();
2677                *pos += 1;
2678                // Respond slowly enough that we can skip one iteration.
2679                ResponseTemplate::new(200)
2680                    .set_body_json(json!({
2681                        "pos": pos.to_string(),
2682                        "lists": {},
2683                        "rooms": {}
2684                    }))
2685                    .set_delay(Duration::from_secs(2))
2686            })
2687            .mount_as_scoped(&server)
2688            .await;
2689
2690        let sliding_sync =
2691            client
2692                .sliding_sync("test")?
2693                .add_list(SlidingSyncList::builder("room-list").sync_mode(
2694                    SlidingSyncMode::new_growing(10).maximum_number_of_rooms_to_fetch(100),
2695                ))
2696                .add_list(
2697                    SlidingSyncList::builder("another-list")
2698                        .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2699                )
2700                .build()
2701                .await?;
2702
2703        let stream = sliding_sync.sync();
2704        pin_mut!(stream);
2705
2706        let cloned_sync = sliding_sync.clone();
2707        spawn(async move {
2708            tokio::time::sleep(Duration::from_millis(100)).await;
2709
2710            cloned_sync
2711                .on_list("another-list", |list| {
2712                    list.set_sync_mode(SlidingSyncMode::new_selective().add_range(10..=20));
2713                    ready(())
2714                })
2715                .await;
2716        });
2717
2718        assert_matches!(stream.next().await, Some(Ok(_)));
2719
2720        sliding_sync.stop_sync().unwrap();
2721
2722        assert_matches!(stream.next().await, None);
2723
2724        let mut num_requests = 0;
2725
2726        for request in server.received_requests().await.unwrap() {
2727            if !SlidingSyncMatcher.matches(&request) {
2728                continue;
2729            }
2730
2731            let another_list_ranges = if num_requests == 0 {
2732                // First request
2733                json!([[0, 10]])
2734            } else {
2735                // Second request
2736                json!([[10, 20]])
2737            };
2738
2739            num_requests += 1;
2740            assert!(num_requests <= 2, "more than one request hit the server");
2741
2742            let json_value = serde_json::from_slice::<serde_json::Value>(&request.body).unwrap();
2743
2744            if let Err(err) = assert_json_diff::assert_json_matches_no_panic(
2745                &json_value,
2746                &json!({
2747                    "conn_id": "test",
2748                    "lists": {
2749                        "room-list": {
2750                            "ranges": [[0, 9]],
2751                            "required_state": [
2752                                ["m.room.encryption", ""],
2753                                ["m.room.tombstone", ""]
2754                            ],
2755                        },
2756                        "another-list": {
2757                            "ranges": another_list_ranges,
2758                            "required_state": [
2759                                ["m.room.encryption", ""],
2760                                ["m.room.tombstone", ""]
2761                            ],
2762                        },
2763                    }
2764                }),
2765                assert_json_diff::Config::new(assert_json_diff::CompareMode::Inclusive),
2766            ) {
2767                dbg!(json_value);
2768                panic!("json differ: {err}");
2769            }
2770        }
2771
2772        assert_eq!(num_requests, 2);
2773
2774        Ok(())
2775    }
2776
2777    #[async_test]
2778    async fn test_timeout_zero_list() -> Result<()> {
2779        let (_server, sliding_sync) = new_sliding_sync(vec![]).await?;
2780
2781        let (request, _, _) = sliding_sync.generate_sync_request().await?;
2782
2783        // Zero list means sliding sync is fully loaded, so there is a timeout to wait
2784        // on new update to pop.
2785        assert!(request.timeout.is_some());
2786
2787        Ok(())
2788    }
2789
2790    #[async_test]
2791    async fn test_timeout_one_list() -> Result<()> {
2792        let (_server, sliding_sync) = new_sliding_sync(vec![
2793            SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::new_growing(10)),
2794        ])
2795        .await?;
2796
2797        let (request, _, _) = sliding_sync.generate_sync_request().await?;
2798
2799        // The list does not require a timeout.
2800        assert!(request.timeout.is_none());
2801
2802        // Simulate a response.
2803        {
2804            let server_response = assign!(http::Response::new("0".to_owned()), {
2805                lists: BTreeMap::from([(
2806                    "foo".to_owned(),
2807                    assign!(http::response::List::default(), {
2808                        count: uint!(7),
2809                    })
2810                 )])
2811            });
2812
2813            let _summary = {
2814                let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2815                sliding_sync
2816                    .handle_response(
2817                        server_response.clone(),
2818                        &mut pos_guard,
2819                        RequestedRequiredStates::default(),
2820                    )
2821                    .await?
2822            };
2823        }
2824
2825        let (request, _, _) = sliding_sync.generate_sync_request().await?;
2826
2827        // The list is now fully loaded, so it requires a timeout.
2828        assert!(request.timeout.is_some());
2829
2830        Ok(())
2831    }
2832
2833    #[async_test]
2834    async fn test_timeout_three_lists() -> Result<()> {
2835        let (_server, sliding_sync) = new_sliding_sync(vec![
2836            SlidingSyncList::builder("foo").sync_mode(SlidingSyncMode::new_growing(10)),
2837            SlidingSyncList::builder("bar").sync_mode(SlidingSyncMode::new_paging(10)),
2838            SlidingSyncList::builder("baz")
2839                .sync_mode(SlidingSyncMode::new_selective().add_range(0..=10)),
2840        ])
2841        .await?;
2842
2843        let (request, _, _) = sliding_sync.generate_sync_request().await?;
2844
2845        // Two lists don't require a timeout.
2846        assert!(request.timeout.is_none());
2847
2848        // Simulate a response.
2849        {
2850            let server_response = assign!(http::Response::new("0".to_owned()), {
2851                lists: BTreeMap::from([(
2852                    "foo".to_owned(),
2853                    assign!(http::response::List::default(), {
2854                        count: uint!(7),
2855                    })
2856                 )])
2857            });
2858
2859            let _summary = {
2860                let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2861                sliding_sync
2862                    .handle_response(
2863                        server_response.clone(),
2864                        &mut pos_guard,
2865                        RequestedRequiredStates::default(),
2866                    )
2867                    .await?
2868            };
2869        }
2870
2871        let (request, _, _) = sliding_sync.generate_sync_request().await?;
2872
2873        // One don't require a timeout.
2874        assert!(request.timeout.is_none());
2875
2876        // Simulate a response.
2877        {
2878            let server_response = assign!(http::Response::new("1".to_owned()), {
2879                lists: BTreeMap::from([(
2880                    "bar".to_owned(),
2881                    assign!(http::response::List::default(), {
2882                        count: uint!(7),
2883                    })
2884                 )])
2885            });
2886
2887            let _summary = {
2888                let mut pos_guard = sliding_sync.inner.position.clone().lock_owned().await;
2889                sliding_sync
2890                    .handle_response(
2891                        server_response.clone(),
2892                        &mut pos_guard,
2893                        RequestedRequiredStates::default(),
2894                    )
2895                    .await?
2896            };
2897        }
2898
2899        let (request, _, _) = sliding_sync.generate_sync_request().await?;
2900
2901        // All lists require a timeout.
2902        assert!(request.timeout.is_some());
2903
2904        Ok(())
2905    }
2906
2907    #[async_test]
2908    async fn test_sync_beat_is_notified_on_sync_response() -> Result<()> {
2909        let server = MockServer::start().await;
2910        let client = logged_in_client(Some(server.uri())).await;
2911
2912        let _mock_guard = Mock::given(SlidingSyncMatcher)
2913            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2914                "pos": "0",
2915                "lists": {},
2916                "rooms": {}
2917            })))
2918            .mount_as_scoped(&server)
2919            .await;
2920
2921        let sliding_sync = client
2922            .sliding_sync("test")?
2923            .with_to_device_extension(
2924                assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2925            )
2926            .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2927            .build()
2928            .await?;
2929
2930        let sliding_sync = Arc::new(sliding_sync);
2931
2932        // Create the listener and perform a sync request
2933        let sync_beat_listener = client.inner.sync_beat.listen();
2934        sliding_sync.sync_once().await?;
2935
2936        // The sync beat listener should be notified shortly after
2937        assert!(sync_beat_listener.wait_timeout(Duration::from_secs(1)).is_some());
2938        Ok(())
2939    }
2940
2941    #[async_test]
2942    async fn test_sync_beat_is_not_notified_on_sync_failure() -> Result<()> {
2943        let server = MockServer::start().await;
2944        let client = logged_in_client(Some(server.uri())).await;
2945
2946        let _mock_guard = Mock::given(SlidingSyncMatcher)
2947            .respond_with(ResponseTemplate::new(404))
2948            .mount_as_scoped(&server)
2949            .await;
2950
2951        let sliding_sync = client
2952            .sliding_sync("test")?
2953            .with_to_device_extension(
2954                assign!(http::request::ToDevice::default(), { enabled: Some(true)}),
2955            )
2956            .with_e2ee_extension(assign!(http::request::E2EE::default(), { enabled: Some(true)}))
2957            .build()
2958            .await?;
2959
2960        let sliding_sync = Arc::new(sliding_sync);
2961
2962        // Create the listener and perform a sync request
2963        let sync_beat_listener = client.inner.sync_beat.listen();
2964        let sync_result = sliding_sync.sync_once().await;
2965        assert!(sync_result.is_err());
2966
2967        // The sync beat listener won't be notified in this case
2968        assert!(sync_beat_listener.wait_timeout(Duration::from_secs(1)).is_none());
2969
2970        Ok(())
2971    }
2972
2973    #[async_test]
2974    async fn test_state_store_lock_is_released_before_calling_handlers() -> Result<()> {
2975        let server = MatrixMockServer::new().await;
2976        let client = server.client_builder().build().await;
2977        let room_id = room_id!("!mu5hr00m:example.org");
2978
2979        let _sync_mock_guard = Mock::given(SlidingSyncMatcher)
2980            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
2981                "pos": "0",
2982                "lists": {},
2983                "extensions": {
2984                    "account_data": {
2985                        "global": [
2986                            {
2987                                "type": "m.direct",
2988                                "content": {
2989                                    "@de4dlockh0lmes:example.org": [
2990                                        "!mu5hr00m:example.org"
2991                                    ]
2992                                }
2993                            }
2994                        ]
2995                    }
2996                },
2997                "rooms": {
2998                    room_id: {
2999                        "name": "Mario Bros Fanbase Room",
3000                        "initial": true,
3001                    },
3002                }
3003            })))
3004            .mount_as_scoped(server.server())
3005            .await;
3006
3007        let f = EventFactory::new().room(room_id);
3008
3009        Mock::given(method("GET"))
3010            .and(wiremock::matchers::path_regex(r"/_matrix/client/v3/rooms/.*/members"))
3011            .respond_with(ResponseTemplate::new(200).set_body_json(json!({
3012                "chunk": [
3013                    f.member(&ALICE).membership(MembershipState::Join).into_raw_timeline(),
3014                ]
3015            })))
3016            .mount(server.server())
3017            .await;
3018
3019        let (tx, rx) = tokio::sync::oneshot::channel();
3020
3021        let tx = Arc::new(Mutex::new(Some(tx)));
3022        client.add_event_handler(move |_: DirectEvent, client: Client| async move {
3023            // Try to run a /members query while in a event handler.
3024            let members =
3025                client.get_room(room_id).unwrap().members(RoomMemberships::JOIN).await.unwrap();
3026            assert_eq!(members.len(), 1);
3027            tx.lock().unwrap().take().expect("sender consumed multiple times").send(()).unwrap();
3028        });
3029
3030        let sliding_sync = client
3031            .sliding_sync("test")?
3032            .add_list(SlidingSyncList::builder("thelist"))
3033            .with_account_data_extension(
3034                assign!(http::request::AccountData::default(), { enabled: Some(true) }),
3035            )
3036            .build()
3037            .await?;
3038
3039        tokio::time::timeout(Duration::from_secs(5), sliding_sync.sync_once())
3040            .await
3041            .expect("Sync did not complete in time")
3042            .expect("Sync failed");
3043
3044        // Wait for the event handler to complete.
3045        tokio::time::timeout(Duration::from_secs(5), rx)
3046            .await
3047            .expect("Event handler did not complete in time")
3048            .expect("Event handler failed");
3049
3050        Ok(())
3051    }
3052
3053    #[cfg(feature = "e2e-encryption")]
3054    #[async_test]
3055    async fn test_syncing_one_time_key_counts_updates() -> Result<()> {
3056        macro_rules! assert_key_count {
3057            ($client: ident, $count:literal) => {{
3058                let machine = $client.olm_machine().await;
3059                let uploaded_key_counts =
3060                    machine.as_ref().unwrap().uploaded_key_count().await.unwrap();
3061                assert_eq!(uploaded_key_counts, $count)
3062            }};
3063        }
3064
3065        macro_rules! sync_with_key_count {
3066            ($client: ident, $server:ident, $count:literal) => {
3067                let count = Some($count);
3068                sync_with_key_count!($client, $server, count);
3069            };
3070            ($client: ident, $server:ident, $count:ident) => {{
3071                let count: Option<u32> = $count;
3072
3073                let template = if let Some(count) = count {
3074                    ResponseTemplate::new(200).set_body_json(json!({
3075                                        "pos": "0",
3076                                        "lists": {},
3077                                        "extensions": {
3078                                            "e2ee": {
3079                                                "device_one_time_keys_count": {
3080                                                    "signed_curve25519": count,
3081                                                }
3082                                            }
3083                                        },
3084                    }))
3085                } else {
3086                    ResponseTemplate::new(200).set_body_json(json!({
3087                                        "pos": "0",
3088                                        "lists": {},
3089                                        "extensions": {
3090                                            "e2ee": {}
3091                                        },
3092                    }))
3093                };
3094
3095                let _sync_mock_guard = Mock::given(SlidingSyncMatcher)
3096                    .respond_with(template)
3097                    .mount_as_scoped($server.server())
3098                    .await;
3099
3100                let sliding_sync = $client
3101                    .sliding_sync("test")?
3102                    .with_e2ee_extension(
3103                        assign!(http::request::E2EE::default(), { enabled: Some(true)}),
3104                    )
3105                    .build()
3106                    .await?;
3107
3108                tokio::time::timeout(Duration::from_secs(5), sliding_sync.sync_once())
3109                    .await
3110                    .expect("Sync did not complete in time")
3111                    .expect("Sync failed");
3112            }}
3113        }
3114
3115        let server = MatrixMockServer::new().await;
3116        let client = server.client_builder().build().await;
3117
3118        server.mock_upload_keys().ok_with_signed_curve_key_count(50).mock_once().mount().await;
3119
3120        // In the beginning there were no uploaded keys.
3121        assert_key_count!(client, 0);
3122
3123        // The first sync will upload 50 one-time keys.
3124        sync_with_key_count!(client, server, None);
3125        assert_key_count!(client, 50);
3126
3127        // Syncing with no key count will not modify the local key count.
3128        sync_with_key_count!(client, server, None);
3129        assert_key_count!(client, 50);
3130
3131        // Syncing with a key count, will update the key count.
3132        sync_with_key_count!(client, server, 10);
3133        assert_key_count!(client, 10);
3134
3135        Ok(())
3136    }
3137}