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