Skip to main content

arminius_lib_sync/
sync.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Implements APIs for client sync API
6//!
7//! See <https://bundesmessenger.gitlab.io/backend/neo/arminius/arminius/architecture/sync.html> for information
8//! about how the stream IDs work
9//!
10//! For limited timelines: We generally fetch up to `limit+1` events from the
11//! database, where `limit` is the amount of events the user asked for, or a
12//! sensible default. If there are actually `limit+1` events we can sync to the
13//! user, we throw away the last event, and mark the timeline as `limited`,
14//! indicating to the user that if they use the `/messages` endpoint, there are
15//! more events they could fetch.
16
17#![allow(
18	clippy::allow_attributes,
19	reason = "This source file contains enums which allow `clippy::missing_docs_in_private_items`. But an exception to this lint will mistakenly report that it's unfulfilled when used with expect, even though it is not."
20)]
21
22use std::{
23	collections::{BTreeMap, HashMap, HashSet},
24	time::Duration,
25};
26
27use arminius_lib_authentication::{AuthenticatedClaims, SessionInfo};
28use arminius_lib_component::{
29	MessagesToken, ServerState,
30	error::{InternalServerError, MatrixApiError, ResultExt as _},
31	ruma_requests::{ArminiusResponse, RumaRequest},
32};
33use arminius_lib_db::{Database as _, DefaultDb, TransactedDb};
34use arminius_lib_db_account_data::{
35	AccountDataQueries as _, SyncGlobalAccountDataEvent, SyncRoomAccountDataEvent,
36};
37use arminius_lib_db_filters::FilterQueries as _;
38use arminius_lib_db_keys::{ChangedUserKeys, FallbackKeysQueries as _, UserKeysQueries as _};
39use arminius_lib_db_read_receipts::{ReadReceiptsQueries as _, SyncRoomReceipts};
40use arminius_lib_db_room::{EventQueries as _, StateQueries as _};
41use arminius_lib_db_stripped_state::{FederatedInvite, StrippedStateQueries as _};
42use arminius_lib_db_sync::{SyncEvent, SyncQueries as _};
43use arminius_lib_db_todevice::{SyncToDeviceEvent, ToDeviceQueries as _};
44use arminius_lib_events::{
45	ClientContext, ContextualizedFields as _, DraftFields as _, RoomEvent, Verified,
46};
47use arminius_lib_macros::{FromDbError, FromNatsError};
48use arminius_lib_model::{read_receipts::SyncReceiptsMap, types::StreamId};
49use axum::{extract::State, response::IntoResponse};
50use futures::StreamExt as _;
51use reqwest::StatusCode;
52use ruma::{
53	OneTimeKeyAlgorithm, OwnedEventId, OwnedRoomId, OwnedUserId, UInt, UserId,
54	api::{
55		client::{
56			filter::{EventFormat, FilterDefinition},
57			sync::sync_events::{self, v3::Filter},
58		},
59		error::ErrorKind,
60	},
61	events::{
62		AnyGlobalAccountDataEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent,
63		AnySyncTimelineEvent, StateEventType, SyncEphemeralRoomEvent, TimelineEventType,
64		receipt::{Receipt, ReceiptEventContent, ReceiptType},
65		room::member::{MembershipState, RoomMemberEventContent},
66	},
67	serde::Raw,
68};
69use serde_json::{json, value::to_raw_value};
70use ulid::Ulid;
71
72use crate::sync_token::{SyncToken, SyncTokenParseError};
73
74/// A room that is part of the sync response.
75#[derive(Debug)]
76enum Room {
77	/// The requesting user is currently a member of the room.
78	Join(sync_events::v3::JoinedRoom),
79	/// The requesting user has left the room.
80	Leave(sync_events::v3::LeftRoom),
81	/// The requesting user is invited to the room.
82	Invite {
83		/// The user inviting us to a room
84		inviter: Option<OwnedUserId>,
85		/// The actual room information
86		room: sync_events::v3::InvitedRoom,
87	},
88	/// The requesting user is knocking on the room.
89	Knock(sync_events::v3::KnockedRoom),
90}
91
92/// A synchronized room
93#[derive(Debug)]
94struct SyncedRoom {
95	/// The actual sync response room. See [`Room`].
96	room: Room,
97	/// For rooms that contain timelines, we also store these timelines
98	/// minimal stream ID, so that we can put it in the response's `prev_batch`.
99	min_stream_id: StreamId,
100	/// The list of state events that were already added to that room. This is
101	/// used to not send duplicates back.
102	state_event_ids: HashSet<OwnedEventId>,
103	/// The user performing the sync
104	user: OwnedUserId,
105}
106
107impl SyncedRoom {
108	/// Creates a new [`SyncedRoom`] which [`Room`] is of the correct type.
109	pub fn new(user: OwnedUserId, state: &MembershipState) -> Result<Self, InternalServerError> {
110		let room = match state {
111			MembershipState::Invite => {
112				Room::Invite { inviter: None, room: sync_events::v3::InvitedRoom::new() }
113			}
114			MembershipState::Join => Room::Join(sync_events::v3::JoinedRoom::new()),
115			MembershipState::Leave | MembershipState::Ban => {
116				Room::Leave(sync_events::v3::LeftRoom::new())
117			}
118			MembershipState::Knock => Room::Knock(sync_events::v3::KnockedRoom::new()),
119			_ => None.unrecoverable_error("Unknown membership state")?,
120		};
121
122		Ok(Self { room, min_stream_id: StreamId::MAX, state_event_ids: HashSet::new(), user })
123	}
124
125	/// Adds the given `event` to the room.
126	#[expect(
127		clippy::expect_used,
128		reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
129	)]
130	pub fn add_event(
131		&mut self,
132		event: SyncEvent,
133		client_context: ClientContext,
134		max_synced_events: usize,
135		event_format: &EventFormat,
136	) -> Result<(), InternalServerError> {
137		fn add_event_to_timeline(
138			timeline: &mut sync_events::v3::Timeline,
139			min_stream_id: &mut i64,
140			event: SyncEvent,
141			client_context: ClientContext,
142			max_synced_events: usize,
143			event_format: &EventFormat,
144		) -> Result<(), InternalServerError> {
145			if timeline.events.len() < max_synced_events - 1 {
146				timeline.prev_batch = Some(String::from(&MessagesToken(event.event_id)));
147				*min_stream_id = std::cmp::min(*min_stream_id, event.stream_id);
148				let formatted_event = event.event.format_as(event_format, client_context);
149				timeline.events.insert(0, Raw::<AnySyncTimelineEvent>::from_json(formatted_event));
150			}
151			// In case we reached the `arminius_lib_db_sync::MAX_SYNCED_EVENTS` limit, we set
152			// the `prev_batch` of the response.
153			else {
154				timeline.limited = true;
155				// SAFETY:  We just verified that we have several elements in
156				// the timeline
157				timeline.prev_batch = Some(String::from(&MessagesToken(
158					timeline
159						.events
160						.first()
161						.expect("We just verified that we have several elements in the timeline")
162						.deserialize()
163						.unrecoverable_error("Could not parse event in database")?
164						.event_id()
165						.to_owned(),
166				)));
167			}
168
169			Ok(())
170		}
171
172		match &mut self.room {
173			Room::Join(joined_room) => {
174				add_event_to_timeline(
175					&mut joined_room.timeline,
176					&mut self.min_stream_id,
177					event,
178					client_context,
179					max_synced_events,
180					event_format,
181				)?;
182			}
183			Room::Leave(left_room) => {
184				add_event_to_timeline(
185					&mut left_room.timeline,
186					&mut self.min_stream_id,
187					event,
188					client_context,
189					max_synced_events,
190					event_format,
191				)?;
192			}
193			Room::Invite { inviter, room } => {
194				if event.event.event_type() == &TimelineEventType::RoomMember
195					&& event
196						.event
197						.state_key()
198						.is_some_and(|state_key| state_key == self.user.as_str())
199				{
200					*inviter = Some(event.event.sender().to_owned());
201				}
202				if event.event.state_key().is_some()
203					&& ruma::events::RECOMMENDED_STRIPPED_STATE_EVENT_TYPES
204						.contains(&StateEventType::from(event.event.event_type().to_string()))
205				{
206					room.invite_state.events.push(
207						event
208							.event
209							.to_stripped_state_event()
210							.expect("This must be a state event, because we just checked this"),
211					);
212				}
213			}
214			Room::Knock(knocked_room) => {
215				if event.event.state_key().is_some()
216					&& ruma::events::RECOMMENDED_STRIPPED_STATE_EVENT_TYPES
217						.contains(&StateEventType::from(event.event.event_type().to_string()))
218				{
219					knocked_room.knock_state.events.push(
220						event
221							.event
222							.to_stripped_state_event()
223							.expect("This must be a state event, because we just checked this"),
224					);
225				}
226			}
227		}
228
229		Ok(())
230	}
231
232	/// Adds the given `state_event` to the room.
233	pub fn add_state_event(
234		&mut self,
235		event: &RoomEvent<Verified>,
236		event_format: &EventFormat,
237	) -> Result<(), InternalServerError> {
238		if self.state_event_ids.contains(event.event_id()) {
239			return Ok(());
240		}
241		let client_event = event.format_as(event_format, ClientContext::default());
242
243		match &mut self.room {
244			Room::Join(joined_room) => match &mut joined_room.state {
245				sync_events::v3::State::Before(state_events) => {
246					joined_room.timeline.limited = true;
247					let event = Raw::<AnySyncStateEvent>::from_json(client_event);
248					state_events.events.push(event);
249				}
250				sync_events::v3::State::After(_after) => {
251					tracing::warn!("Parameter `use_state_after` not yet supported, skipping.");
252				}
253				_unknown => {
254					tracing::error!("Unknown state changes type, skipping.");
255				}
256			},
257			Room::Leave(left_room) => match &mut left_room.state {
258				sync_events::v3::State::Before(state_events) => {
259					left_room.timeline.limited = true;
260					let event = Raw::<AnySyncStateEvent>::from_json(client_event);
261					state_events.events.push(event);
262				}
263				sync_events::v3::State::After(_after) => {
264					tracing::warn!("Parameter `use_state_after` not yet supported, skipping.");
265				}
266				_unknown => {
267					tracing::error!("Unknown state changes type, skipping.");
268				}
269			},
270			Room::Invite { inviter, room } => {
271				if let Some(state_key) = event.state_key() {
272					let is_recommended_state_event =
273						ruma::events::RECOMMENDED_STRIPPED_STATE_EVENT_TYPES
274							.contains(&StateEventType::from(event.event_type().to_string()));
275
276					let state_key_is_user = state_key == self.user.as_str();
277					let state_key_is_inviter =
278						inviter.as_ref().is_some_and(|inviter| state_key == inviter);
279					let is_recommended_member_event = event.event_type()
280						== &TimelineEventType::RoomMember
281						&& (state_key_is_user || state_key_is_inviter);
282
283					if is_recommended_state_event || is_recommended_member_event {
284						#[expect(
285							clippy::expect_used,
286							reason = "`to_stripped_state_event()` fails on non-state events. But we check for a state key directly above, so we know that this call is infallible"
287						)]
288						room.invite_state.events.push(
289							event
290								.to_stripped_state_event()
291								.expect("This must be a state event, because we just checked this"),
292						);
293					}
294				}
295			}
296			Room::Knock(knocked_room) => {
297				if event.state_key().is_some()
298					&& ruma::events::RECOMMENDED_STRIPPED_STATE_EVENT_TYPES
299						.contains(&StateEventType::from(event.event_type().to_string()))
300					|| (event.state_key() == Some(self.user.as_str())
301						&& event.event_type() == &TimelineEventType::RoomMember)
302				{
303					#[expect(
304						clippy::expect_used,
305						reason = "`to_stripped_state_event()` fails on non-state events. But we check for a state key directly above, so we know that this call is infallible"
306					)]
307					knocked_room.knock_state.events.push(
308						event
309							.to_stripped_state_event()
310							.expect("This must be a state event, because we just checked this"),
311					);
312				}
313			}
314		}
315
316		self.state_event_ids.insert(event.event_id().to_owned());
317
318		Ok(())
319	}
320
321	/// Adds the given room account data event to the room.
322	///
323	/// This throws an error if the rooms in self is neither a `Joined` nor a
324	/// `Left` room (as such rooms do cannot contain account data in sync
325	/// responses), or if we fail to deserialize the given event into a
326	/// [`ruma::events::AnyRoomAccountDataEvent`].
327	pub fn add_account_data_event(
328		&mut self,
329		event: &SyncRoomAccountDataEvent,
330	) -> Result<(), InternalServerError> {
331		match &mut self.room {
332			Room::Join(joined_room) => {
333				joined_room.account_data.events.push(Raw::from_json(
334					to_raw_value(&event.event).unrecoverable_error(
335						"Could not parse room account data event from database",
336					)?,
337				));
338			}
339			Room::Leave(left_room) => {
340				left_room.account_data.events.push(Raw::from_json(
341					to_raw_value(&event.event).unrecoverable_error(
342						"Could not parse room account data event from database",
343					)?,
344				));
345			}
346			_ => {
347				None.unrecoverable_error(&format!(
348					"Impossible to add room account data events to '{}' rooms",
349					event.membership
350				))?;
351			}
352		}
353
354		Ok(())
355	}
356
357	/// Adds the given read receipts to the room.
358	pub fn add_read_receipts(
359		&mut self,
360		receipts: BTreeMap<OwnedEventId, BTreeMap<ReceiptType, BTreeMap<OwnedUserId, Receipt>>>,
361	) -> Result<(), InternalServerError> {
362		let Room::Join(joined_room) = &mut self.room else {
363			return None.unrecoverable_error("Cannot add read receipts to non-joined rooms.")?;
364		};
365
366		let any_ephemeral: AnySyncEphemeralRoomEvent = AnySyncEphemeralRoomEvent::Receipt(
367			SyncEphemeralRoomEvent::new(ReceiptEventContent(receipts)),
368		);
369
370		let event = json!({
371			"type": any_ephemeral.event_type(),
372			"content": any_ephemeral.content(),
373		});
374
375		joined_room.ephemeral.events.push(Raw::from_json(
376			to_raw_value(&event).unrecoverable_error("Failed to convert to raw value")?,
377		));
378
379		Ok(())
380	}
381}
382
383/// A wrapper around a sync response
384#[derive(Debug)]
385pub(crate) struct SyncResponse {
386	/// The sync token to return
387	sync_token: SyncToken,
388	/// The user for who we are syncing data
389	user: OwnedUserId,
390	/// Mapping of all the rooms involved in the `/sync` request.
391	rooms: HashMap<OwnedRoomId, SyncedRoom>,
392	/// The global account data events of the user.
393	account_data_events: Vec<Raw<AnyGlobalAccountDataEvent>>,
394	/// To device messages
395	to_device_messages: sync_events::v3::ToDevice,
396	/// List of users who changed their keys or newly share a room since the
397	/// last sync.
398	changed_user_keys: Vec<OwnedUserId>,
399	/// List of users who no longer share any encrypted room since the last
400	/// sync.
401	left_user_keys: Vec<OwnedUserId>,
402	/// For each key algorithm, the number of unclaimed one-time keys currently
403	/// held on the server for this device
404	device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
405	/// The unused fallback key algorithms.
406	device_unused_fallback_key_types: Vec<OneTimeKeyAlgorithm>,
407}
408
409impl IntoResponse for SyncResponse {
410	fn into_response(self) -> axum::response::Response {
411		let mut response = sync_events::v3::Response::new(String::from(&self.sync_token));
412
413		response.account_data.events = self.account_data_events;
414
415		for (room_id, synced_room) in self.rooms {
416			match synced_room.room {
417				Room::Join(joined_room) => {
418					response.rooms.join.insert(room_id, joined_room);
419				}
420				Room::Leave(left_room) => {
421					response.rooms.leave.insert(room_id, left_room);
422				}
423				Room::Invite { room, .. } => {
424					response.rooms.invite.insert(room_id, room);
425				}
426				Room::Knock(knocked_room) => {
427					response.rooms.knock.insert(room_id, knocked_room);
428				}
429			}
430		}
431
432		response.to_device = self.to_device_messages;
433
434		response.device_lists.changed = self.changed_user_keys;
435		response.device_lists.left = self.left_user_keys;
436		response.device_one_time_keys_count = self.device_one_time_keys_count;
437		response.device_unused_fallback_key_types = Some(self.device_unused_fallback_key_types);
438
439		ArminiusResponse(response).into_response()
440	}
441}
442
443impl SyncResponse {
444	/// Constructor
445	fn new(sync_token: SyncToken, user: OwnedUserId) -> Self {
446		Self {
447			sync_token,
448			user,
449			rooms: HashMap::new(),
450			account_data_events: Vec::new(),
451			to_device_messages: sync_events::v3::ToDevice::new(),
452			changed_user_keys: Vec::new(),
453			left_user_keys: Vec::new(),
454			device_one_time_keys_count: BTreeMap::new(),
455			device_unused_fallback_key_types: Vec::new(),
456		}
457	}
458
459	/// Add an event to the sync response
460	pub fn add_event(
461		&mut self,
462		event: SyncEvent,
463		client_context: ClientContext,
464		max_synced_events: usize,
465		event_format: &EventFormat,
466	) -> Result<(), InternalServerError> {
467		self.sync_token.event_stream_position =
468			std::cmp::max(self.sync_token.event_stream_position, event.stream_id);
469
470		let room = self
471			.rooms
472			.entry(event.room_id.clone())
473			.or_insert(SyncedRoom::new(self.user.clone(), &event.membership)?);
474
475		room.add_event(event, client_context, max_synced_events, event_format)
476	}
477
478	/// This adds the given room account data to the sync response.
479	///
480	/// Throws an error if the room type is not supported.
481	pub fn add_room_account_data_event(
482		&mut self,
483		event: &SyncRoomAccountDataEvent,
484	) -> Result<(), InternalServerError> {
485		let room = self
486			.rooms
487			.entry(event.room_id.clone())
488			.or_insert(SyncedRoom::new(self.user.clone(), &event.membership)?);
489
490		room.add_account_data_event(event)
491	}
492
493	/// Add a to_device message to the sync response
494	pub fn add_to_device_message(
495		&mut self,
496		event: &SyncToDeviceEvent,
497	) -> Result<(), InternalServerError> {
498		self.sync_token.todevice_stream_position =
499			std::cmp::max(event.stream_id, self.sync_token.todevice_stream_position);
500
501		let event = json!({
502			"type": event.event_type.to_string(),
503			"sender": event.sender,
504			"content": event.content,
505		});
506
507		self.to_device_messages.events.push(
508			Raw::from_json_string(event.to_string())
509				.unrecoverable_error("Could not parse to-device message in database")?,
510		);
511
512		Ok(())
513	}
514
515	/// Adds the given read receipts map to all their corresponding rooms of the
516	/// sync response.
517	pub fn add_read_receipts(
518		&mut self,
519		receipts: SyncReceiptsMap,
520	) -> Result<(), InternalServerError> {
521		for (room_id, room_receipts) in receipts {
522			let room = self.rooms.entry(room_id.clone()).or_insert(SyncedRoom::new(
523				self.user.clone(),
524				// Read receipts are only for joined rooms.
525				&MembershipState::Join,
526			)?);
527
528			room.add_read_receipts(room_receipts)?;
529		}
530
531		Ok(())
532	}
533}
534
535#[allow(
536	missing_docs,
537	clippy::missing_docs_in_private_items,
538	reason = "TODO(#2003): This exception predates us enabling `clippy::allow_attributes_without_reason`, so we don't know why it was enabled"
539)]
540#[derive(thiserror::Error, Debug, MatrixApiError, FromDbError, FromNatsError)]
541pub(crate) enum SyncError {
542	#[error(transparent)]
543	InternalServerError(#[from] InternalServerError),
544	#[error(transparent)]
545	SyncTokenParseError(#[from] SyncTokenParseError),
546	/// Filter ID on our homeserver is the nid of the row which is `i64` number.
547	/// if it fails to parse, it means that the provided filter ID is not a
548	/// valid one.
549	#[matrix_api_error(status_code = StatusCode::BAD_REQUEST, error_kind = ErrorKind::InvalidParam)]
550	#[error("The filter ID provided is not a valid filter ID")]
551	InvalidFilterId,
552	/// Filter that was requested cannot be found.
553	#[error("Filter not found")]
554	#[matrix_api_error(status_code = StatusCode::NOT_FOUND, error_kind = ErrorKind::NotFound)]
555	UnknownFilter,
556}
557
558/// This structure contains any data that needs to be synchronized.
559#[derive(Debug)]
560pub struct SyncData {
561	/// The global account data that needs to be synchronized.
562	pub global_account_data_events: Vec<SyncGlobalAccountDataEvent>,
563	/// The rooms account data events that needs to be synchronized.
564	pub room_account_data_events: Vec<SyncRoomAccountDataEvent>,
565	/// The room events that need to be synchronized.
566	pub room_events: Vec<SyncEvent>,
567	/// The to-device events that needs to be synchronized.
568	pub todevice_events: Vec<SyncToDeviceEvent>,
569	/// The list of users who either changed their keys or newly joined a room
570	/// shared with the synchronizing user.
571	pub changed_user_keys: Vec<ChangedUserKeys>,
572	/// The list of users who either changed their keys or newly joined a room
573	/// shared with the synchronizing user.
574	pub left_user_keys: Vec<OwnedUserId>,
575	/// The new and forward read receipts that were received since the last
576	/// sync.
577	pub read_receipts: SyncRoomReceipts,
578	/// The invites the syncing user has received over federation
579	pub federated_invites: Vec<(StreamId, FederatedInvite)>,
580	/// The invites the syncing user has received over federation
581	pub federated_knocks: Vec<(StreamId, FederatedInvite)>,
582}
583
584impl SyncData {
585	/// Builds a [`SyncResponse`] from a pair of a [`SyncToken`] and some
586	/// [`SyncData`].
587	///
588	/// It also fetches any needed additional data from the transaction (e.g.
589	/// events state history, etc)
590	async fn build_sync_response<'c>(
591		self,
592		transaction: &mut TransactedDb<'c>,
593		user_id: &UserId,
594		device_id: &ruma::DeviceId,
595		sync_token: SyncToken,
596		filter_definition: Option<&FilterDefinition>,
597	) -> Result<SyncResponse, InternalServerError> {
598		let mut sync_response = SyncResponse::new(sync_token.clone(), user_id.to_owned());
599
600		for account_data_event in self.global_account_data_events {
601			sync_response.sync_token.account_data_stream_position = sync_response
602				.sync_token
603				.account_data_stream_position
604				.max(account_data_event.stream_id);
605
606			let raw_event = Raw::from_json(
607				to_raw_value(&account_data_event.event)
608					.unrecoverable_error("Converting global account data to raw")?,
609			);
610			sync_response.account_data_events.push(raw_event);
611		}
612
613		let max_fetched_timeline_events: usize = filter_definition
614			.and_then(|fd| {
615				fd.room
616					.timeline
617					.limit
618					.and_then(|limit| usize::try_from(limit).map(|limit| limit + 1).ok())
619			})
620			.unwrap_or(arminius_lib_db_sync::MAX_SYNCED_EVENTS);
621
622		let event_format = filter_definition
623			.map(|filter_definition| filter_definition.event_format.clone())
624			.unwrap_or_default();
625
626		for room_event in self.room_events {
627			let client_context = transaction
628				.fetch_client_context(&room_event.room_id, &room_event.event_id, user_id, device_id)
629				.await?;
630			sync_response.add_event(
631				room_event,
632				client_context,
633				max_fetched_timeline_events,
634				&event_format,
635			)?;
636		}
637
638		for (room_id, synced_room) in &mut sync_response.rooms {
639			let state_events = SyncData::fetch_state_for_room(
640				transaction,
641				synced_room,
642				user_id,
643				room_id,
644				&sync_token,
645			)
646			.await?;
647			for state_event in state_events {
648				synced_room.add_state_event(&state_event, &event_format)?;
649			}
650		}
651
652		for account_data_event in self.room_account_data_events {
653			sync_response.sync_token.account_data_stream_position = sync_response
654				.sync_token
655				.account_data_stream_position
656				.max(account_data_event.stream_id);
657
658			sync_response.add_room_account_data_event(&account_data_event)?;
659		}
660
661		for todevice_event in &self.todevice_events {
662			sync_response.add_to_device_message(todevice_event)?;
663		}
664
665		for changed_user_keys in self.changed_user_keys {
666			sync_response.sync_token.user_keys_stream_position =
667				sync_response.sync_token.user_keys_stream_position.max(changed_user_keys.stream_id);
668
669			sync_response.changed_user_keys.push(changed_user_keys.user_id);
670		}
671
672		for (stream_id, invite) in self.federated_invites {
673			let mut room = sync_events::v3::InvitedRoom::new();
674			room.invite_state.events = invite.state_events.into_iter().map(Into::into).collect();
675			sync_response.rooms.insert(
676				invite.room_id,
677				SyncedRoom {
678					room: Room::Invite { inviter: None, room },
679					min_stream_id: stream_id,
680					state_event_ids: HashSet::new(),
681					user: invite.invited_user,
682				},
683			);
684			sync_response.sync_token.federated_invites_stream_position =
685				sync_token.federated_invites_stream_position.max(stream_id + 1);
686		}
687
688		for (stream_id, knock) in self.federated_knocks {
689			let mut room = sync_events::v3::KnockedRoom::new();
690			room.knock_state.events = knock.state_events.into_iter().map(Into::into).collect();
691			sync_response.rooms.insert(
692				knock.room_id,
693				SyncedRoom {
694					room: Room::Knock(room),
695					min_stream_id: stream_id,
696					state_event_ids: HashSet::new(),
697					user: knock.invited_user,
698				},
699			);
700			sync_response.sync_token.federated_knocks_stream_position =
701				sync_token.federated_knocks_stream_position.max(stream_id + 1);
702		}
703
704		sync_response.left_user_keys = self.left_user_keys;
705
706		sync_response.sync_token.read_receipts_stream_position =
707			sync_token.read_receipts_stream_position.max(self.read_receipts.max_stream_id);
708		sync_response.add_read_receipts(self.read_receipts.receipts)?;
709
710		Ok(sync_response)
711	}
712
713	/// Given a synced room, fill out the `state` field
714	///
715	/// If too many messages have been sent since the last `/sync` call, we need
716	/// to still send all the state events that the user will miss when the
717	/// returned timeline has a gap
718	///
719	/// If this is the first time this room is synced since we joined or were
720	/// invited to the room, we also need to get the entire state of the room
721	async fn fetch_state_for_room<'c>(
722		transaction: &mut TransactedDb<'c>,
723		synced_room: &SyncedRoom,
724		user_id: &UserId,
725		room_id: &ruma::RoomId,
726		sync_token: &SyncToken,
727	) -> Result<Vec<RoomEvent<Verified>>, InternalServerError> {
728		match synced_room.room {
729			// For a room we're invited to, just fetch the current state
730			Room::Invite { .. } | Room::Knock(_) => {
731				// FIXME(#2071): The stripped state fetched should only depend
732				// on the invite event found in the original sync events,
733				// and not on the current state of the room
734				let invite_event = transaction
735					.fetch_room_state(room_id)
736					.await?
737					.remove(&(StateEventType::RoomMember, user_id.to_string()))
738					.unrecoverable_error(
739						"Room state was recorded as invite, but the last membership event is not invite",
740					)?;
741				let mut state: Vec<_> = transaction
742					.fetch_state_before(room_id, invite_event.event_id())
743					.await?
744					.into_values()
745					// Manually remove the users previous member event, we only want the current
746					// member event
747					.filter(|event| {
748						event.event_type() != &TimelineEventType::RoomMember
749							|| event.state_key() != Some(user_id.as_str())
750					})
751					.collect();
752				// The actual membership event isn't in the `state_before` of
753				// the membership event, and must be added manually
754				state.push(invite_event);
755				Ok(state)
756			}
757			Room::Join(_) | Room::Leave(_) => {
758				// Checks whether we joined the room recently
759				//
760				// Specifically, check if we start the timeline with a join
761				// event, and the state before our timeline starts is not
762				// another join event. If we're overwriting a join event
763				// with another join event, we're not actually joining the room,
764				// just modifying a displayname, avatar, or similar
765				if let Some(first_membership_event) = transaction
766					.fetch_new_room_membership_event(
767						room_id,
768						user_id,
769						sync_token.event_stream_position,
770					)
771					.await?
772				{
773					let first_membership = serde_json::from_str::<RoomMemberEventContent>(
774						first_membership_event.content().get(),
775					)
776					.unrecoverable_error("Could not parse membership event in DB")?
777					.membership;
778					let previous_membership = transaction
779						.fetch_membership_before(
780							room_id,
781							first_membership_event.event_id(),
782							user_id,
783						)
784						.await?
785						.map(|event| {
786							serde_json::from_str::<RoomMemberEventContent>(event.content().get())
787								.unrecoverable_error("Invalid member event in database")
788						})
789						.transpose()?
790						.map(|event| event.membership);
791
792					if first_membership == MembershipState::Join
793						&& previous_membership != Some(MembershipState::Join)
794					{
795						// If we just joined the room since last sync, we return
796						// the complete room state, up until the timeline
797						// starts
798						return Ok(transaction
799							.fetch_incremental_room_state_for_user(
800								room_id,
801								0,
802								synced_room.min_stream_id,
803							)
804							.await?);
805					}
806				}
807				// If there's no new membership event, or the new membership
808				// event is just an update to a previous membership event
809				// with the same membership state, return the standard
810				// incremental room state
811				Ok(transaction
812					.fetch_incremental_room_state_for_user(
813						room_id,
814						sync_token.event_stream_position,
815						synced_room.min_stream_id,
816					)
817					.await?)
818			}
819		}
820	}
821}
822
823/// This function is a central point in which we query every possible data that
824/// needs synchronization.
825async fn fetch_sync_data<'c>(
826	transaction: &mut TransactedDb<'c>,
827	session: &SessionInfo,
828	initial: bool,
829	sync_token: &mut SyncToken,
830	filter_definition: Option<&FilterDefinition>,
831) -> Result<Option<SyncData>, InternalServerError> {
832	let room_filters = filter_definition.map(|filter_definition| &filter_definition.room);
833	let new_room_events = transaction
834		.fetch_sync_room_events(&session.user_id, sync_token.event_stream_position, room_filters)
835		.await?;
836
837	let new_global_account_data_events = transaction
838		.fetch_sync_global_account_data_events(
839			&session.user_id,
840			sync_token.account_data_stream_position,
841			filter_definition.map(|filter_definition| &filter_definition.account_data),
842		)
843		.await?;
844
845	let new_room_account_data_events = transaction
846		.fetch_sync_room_account_data_events(
847			&session.user_id,
848			sync_token.account_data_stream_position,
849			filter_definition.map(|filter_definition| &filter_definition.room.account_data),
850		)
851		.await?;
852
853	let new_todevice_events = transaction
854		.fetch_sync_todevice_messages(
855			&session.user_id,
856			&session.device_id,
857			sync_token.todevice_stream_position,
858		)
859		.await?;
860
861	// Only populate device lists changes on incremental sync
862	let (changed_user_keys, users_that_left_rooms) = match initial {
863		// If it's the first request, just need to push the sync token's user keys stream ID to the
864		// server's latest one.
865		true => {
866			sync_token.user_keys_stream_position =
867				transaction.fetch_current_user_keys_stream_id().await?;
868			Default::default()
869		}
870		// Otherwise, fetch the key changes that occurred since the last request's token
871		false => (
872			transaction
873				.fetch_sync_user_keys_changes(
874					&session.user_id,
875					sync_token.event_stream_position,
876					sync_token.user_keys_stream_position,
877				)
878				.await?,
879			transaction
880				.fetch_sync_user_keys_left(&session.user_id, sync_token.event_stream_position)
881				.await?,
882		),
883	};
884
885	let new_read_receipts = transaction
886		.fetch_sync_read_receipts(
887			&session.user_id,
888			sync_token.read_receipts_stream_position,
889			room_filters.map(|room_filter| &room_filter.ephemeral),
890		)
891		.await?;
892
893	let new_federated_invites = transaction
894		.fetch_federated_invites(&session.user_id, sync_token.federated_invites_stream_position)
895		.await?;
896
897	let new_federated_knocks = transaction
898		.fetch_federated_knocks(&session.user_id, sync_token.federated_knocks_stream_position)
899		.await?;
900
901	Ok((!new_room_events.is_empty()
902		|| !new_todevice_events.is_empty()
903		|| !new_global_account_data_events.is_empty()
904		|| !new_room_account_data_events.is_empty()
905		|| !changed_user_keys.is_empty()
906		|| !users_that_left_rooms.is_empty()
907		|| !new_read_receipts.receipts.is_empty()
908		|| !new_federated_invites.is_empty()
909		|| !new_federated_knocks.is_empty())
910	.then_some(SyncData {
911		global_account_data_events: new_global_account_data_events,
912		room_account_data_events: new_room_account_data_events,
913		room_events: new_room_events,
914		todevice_events: new_todevice_events,
915		changed_user_keys,
916		left_user_keys: users_that_left_rooms,
917		read_receipts: new_read_receipts,
918		federated_invites: new_federated_invites,
919		federated_knocks: new_federated_knocks,
920	}))
921}
922
923/// Given a [`arminius_lib_nats::Subscriber`] instanced from
924/// `sync_data_subscriber`, wait until it receives a NATS message about a new
925/// sync response.
926///
927/// Then, it checks in the database if there are any new data to synchronize
928/// whenever a new message is received by that subscriber, using a call to
929/// [`fetch_sync_data`].
930async fn wait_for_sync_data<'c>(
931	mut sync_update_subscriber: arminius_lib_nats::Subscriber,
932	transaction: &mut TransactedDb<'c>,
933	session: &SessionInfo,
934	initial: bool,
935	sync_token: &mut SyncToken,
936	filter_definition: Option<&FilterDefinition>,
937	timeout: Duration,
938) -> Result<Option<SyncData>, InternalServerError> {
939	match tokio::time::timeout(timeout, async {
940		loop {
941			if sync_update_subscriber.next().await.is_some() {
942				let sync_data =
943					fetch_sync_data(transaction, session, initial, sync_token, filter_definition)
944						.await?;
945
946				if sync_data.is_some() {
947					return Ok::<_, InternalServerError>(sync_data);
948				}
949			}
950		}
951	})
952	.await
953	{
954		// Whether we found something or we failed at it, return immediately
955		Ok(sync_data) => Ok(sync_data?),
956		// We still try one last time, just in case we missed a notification.
957		Err(_timeout) => {
958			fetch_sync_data(transaction, session, initial, sync_token, filter_definition).await
959		}
960	}
961}
962
963/// Since the client can send us a filter on the fly we need to normalize the
964/// the request.
965///
966/// We do not need this inside a transaction since we don't need the same sort
967/// of integrity as other sync queries.
968async fn normalize_filter(
969	database: &mut DefaultDb,
970	user_id: &UserId,
971	filter: Option<Filter>,
972) -> Result<Option<FilterDefinition>, SyncError> {
973	match filter {
974		Some(Filter::FilterDefinition(filter_definition)) => Ok(Some(filter_definition)),
975		Some(Filter::FilterId(filter_id_raw)) => {
976			let filter_id =
977				filter_id_raw.parse::<Ulid>().map_err(|_| SyncError::InvalidFilterId)?;
978
979			Ok(Some(
980				database
981					.fetch_filter_with_id(filter_id, user_id)
982					.await?
983					.ok_or(SyncError::UnknownFilter)?,
984			))
985		}
986		_ => Ok(None),
987	}
988}
989
990/// Handler for `GET /_matrix/client/v3/sync` (See
991/// <https://spec.matrix.org/v1.14/client-server-api/#get_matrixclientv3sync>).
992///
993/// Endpoint that returns state of the data and is used by clients to sync their
994/// data with the homeserver.
995///
996/// **Warning:** Whenever you add support for any other data to be synced in
997/// this endpoint (e.g account_data, keys, etc), make sure to call
998/// `arminius_lib_nats::connection::NatsClient::notify_new_sync_data` on
999/// the creator of such data that will get synced.
1000pub(crate) async fn sync_events_handler(
1001	state: State<ServerState>,
1002	AuthenticatedClaims(claims): AuthenticatedClaims,
1003	RumaRequest(request): RumaRequest<sync_events::v3::Request>,
1004) -> Result<SyncResponse, SyncError> {
1005	// FIXME(#680): Remove before release, very GDPR unfriendly
1006	tracing::info!(?request, "Received sync request");
1007	let initial = request.since.is_none();
1008	let mut sync_token =
1009		request.since.as_deref().map(SyncToken::try_from).transpose()?.unwrap_or_default();
1010
1011	let mut database = state.database.clone();
1012	let filter_definition =
1013		normalize_filter(&mut database, &claims.user_id, request.filter).await?;
1014
1015	let mut transaction = database.begin_transaction().await?;
1016
1017	// We can already delete past synced to-device messages (only called when it's not an initial
1018	// sync).
1019	if !initial {
1020		transaction
1021			.delete_synced_todevice_messages(
1022				&claims.user_id,
1023				&claims.device_id,
1024				sync_token.todevice_stream_position,
1025			)
1026			.await?;
1027	}
1028
1029	// Set up the subscriber that listens for sync notifications, before doing
1030	// the first DB call Doing it afterwards, we risk the following flow of
1031	// events:
1032	//  - We check the database for new stuff to sync, find nothing
1033	//  - A new message to sync gets added to the database, and a NATS message gets sent which no
1034	//    one hears
1035	//  - We start listening for NATS messages. There are none.
1036	//  - We eventually time out.
1037	let subscriber = state.nats_connection.sync_data_subscriber().await?;
1038
1039	let mut sync_data = fetch_sync_data(
1040		&mut transaction,
1041		&claims,
1042		initial,
1043		&mut sync_token,
1044		filter_definition.as_ref(),
1045	)
1046	.await?;
1047
1048	// NOTE: This default/fallback timeout is defined in the Matrix
1049	// specifications.
1050	let timeout = request.timeout.unwrap_or(Duration::ZERO);
1051
1052	// Try fetching data in a loop if we did not find any on the first try and
1053	// we have a timeout
1054	if sync_data.is_none() && timeout != Duration::ZERO {
1055		sync_data = wait_for_sync_data(
1056			subscriber,
1057			&mut transaction,
1058			&claims,
1059			initial,
1060			&mut sync_token,
1061			filter_definition.as_ref(),
1062			timeout,
1063		)
1064		.await?;
1065	}
1066
1067	// No matter if we found data or not, convert it to a response
1068	let mut sync_response = match sync_data {
1069		Some(sync_data) => {
1070			sync_data
1071				.build_sync_response(
1072					&mut transaction,
1073					&claims.user_id,
1074					&claims.device_id,
1075					sync_token,
1076					filter_definition.as_ref(),
1077				)
1078				.await?
1079		}
1080		None => SyncResponse::new(sync_token, claims.user_id.clone()),
1081	};
1082
1083	// Add the data that will always be in the sync response, no matter if it is
1084	// syncing anything new
1085	sync_response.device_one_time_keys_count =
1086		transaction.fetch_otk_counts(&claims.user_id, &claims.device_id).await?;
1087
1088	sync_response.device_unused_fallback_key_types = transaction
1089		.fetch_unused_fallback_keys_algorithms(&claims.user_id, &claims.device_id)
1090		.await?;
1091
1092	transaction.commit().await?;
1093
1094	// FIXME(#680): Remove before release, very GDPR unfriendly
1095	tracing::info!(?sync_response, "Made sync response");
1096
1097	Ok(sync_response)
1098}