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