arminius_lib_sync/syncv4/handler.rs
1// SPDX-FileCopyrightText: 2026 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Implements the actual handler for sync v4 (Simplified Sliding Sync).
6//!
7//! It contains checks for the request for potential invalid data (such as
8//! overstepped limits).
9//!
10//! See this module's [top-level](crate::syncv4) for a higher overview of the
11//! whole architecture of sync v4.
12
13mod tests;
14
15use arminius_lib_authentication::AuthenticatedClaims;
16use arminius_lib_component::{
17 ServerState,
18 ruma_requests::{ArminiusResponse, RumaRequest},
19};
20use axum::extract::State;
21use ruma::api::client::sync::sync_events;
22
23use crate::syncv4::{
24 connect::ConnectionKey,
25 error::{MAX_LISTS_LEN, MAX_SUBSCRIPTIONS_LEN, SyncV4Error},
26 transitions::SyncV4Handler,
27};
28
29/// Performs a few checks on the given request.
30///
31/// These are mentioned by the MSC itself and as such should be enforced.
32fn check_syncv4_request(request: &sync_events::v5::Request) -> Result<(), SyncV4Error> {
33 // Check that the number of lists in the request is not higher than allowed.
34 // See the `lists` argument to the Request body here:
35 // <https://github.com/matrix-org/matrix-spec-proposals/blob/erikj/sss/proposals/4186-simplified-sliding-sync.md#top-level>
36 if request.lists.len() > MAX_LISTS_LEN {
37 return Err(SyncV4Error::TooManyLists);
38 }
39
40 // Check that the number of subscriptions in the request is not higher than
41 // allowed. See the `room_subscriptions` argument to the Request body here:
42 // <https://github.com/matrix-org/matrix-spec-proposals/blob/erikj/sss/proposals/4186-simplified-sliding-sync.md#top-level>
43 if request.room_subscriptions.len() > MAX_SUBSCRIPTIONS_LEN {
44 return Err(SyncV4Error::TooManySubscriptions);
45 }
46
47 // Make sure every ranges `[a; b]` respect `b >= a`.
48 if request.lists.values().any(|list| list.ranges.iter().any(|(start, end)| start > end)) {
49 return Err(SyncV4Error::ReversedRange);
50 }
51
52 Ok(())
53}
54
55/// Handler for `POST /_matrix/client/unstable/org.matrix.simplified_msc3575/sync` (See <https://github.com/matrix-org/matrix-spec-proposals/blob/erikj/sss/proposals/4186-simplified-sliding-sync.md>).
56///
57/// Endpoint implementing Simplified Sliding Sync (See
58/// <https://bundesmessenger.gitlab.io/backend/neo/arminius/arminius/adrs/simplified-sliding-sync.html>).
59///
60/// NOTE: This endpoint is unstable.
61pub(crate) async fn sync_events_handler(
62 mut state: State<ServerState>,
63 AuthenticatedClaims(claims): AuthenticatedClaims,
64 RumaRequest(request): RumaRequest<sync_events::v5::Request>,
65) -> Result<ArminiusResponse<sync_events::v5::Response>, SyncV4Error> {
66 check_syncv4_request(&request)?;
67
68 let key = ConnectionKey::new(claims.user_id, claims.device_id, request.conn_id.clone());
69
70 let sync_plan = SyncV4Handler::connect(&mut state.database, key, request.pos.clone())
71 .await?
72 .gather()
73 .await?
74 .plan(&request)?;
75
76 // If we already found some data to sync, we can early-return by constructing a response from
77 // it.
78 if sync_plan.has_data_to_sync() {
79 let response = sync_plan.execute().await?.assemble(request.txn_id)?;
80
81 return Ok(response.into());
82 }
83 // Likewise, if the request does not have a timeout set (i.e. no
84 // long-polling), we can early-return with an empty response.
85 else if request.timeout.is_none() {
86 let empty_response =
87 sync_plan.persist_without_position().await?.assemble(request.txn_id)?;
88
89 return Ok(empty_response.into());
90 }
91
92 // We now have to perform long polling.
93 let Some(_timeout) = request.timeout else { unreachable!() };
94
95 // Drop the transaction before entering long-polling
96 let persisted = sync_plan.persist_without_position().await?;
97
98 // TODEL: This is just a temporary patch to throttle clients requests and prevent them from
99 // spamming us.
100 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
101
102 Ok(persisted.assemble(request.txn_id)?.into())
103}