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