Skip to main content

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}