1#![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#[derive(Debug)]
76enum Room {
77 Join(sync_events::v3::JoinedRoom),
79 Leave(sync_events::v3::LeftRoom),
81 Invite {
83 inviter: Option<OwnedUserId>,
85 room: sync_events::v3::InvitedRoom,
87 },
88 Knock(sync_events::v3::KnockedRoom),
90}
91
92#[derive(Debug)]
94struct SyncedRoom {
95 room: Room,
97 min_stream_id: StreamId,
100 state_event_ids: HashSet<OwnedEventId>,
103 user: OwnedUserId,
105}
106
107impl SyncedRoom {
108 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 #[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 else {
154 timeline.limited = true;
155 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 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 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 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#[derive(Debug)]
385pub(crate) struct SyncResponse {
386 sync_token: SyncToken,
388 user: OwnedUserId,
390 rooms: HashMap<OwnedRoomId, SyncedRoom>,
392 account_data_events: Vec<Raw<AnyGlobalAccountDataEvent>>,
394 to_device_messages: sync_events::v3::ToDevice,
396 changed_user_keys: Vec<OwnedUserId>,
399 left_user_keys: Vec<OwnedUserId>,
402 device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
405 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 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 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 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 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 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 &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 #[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 #[error("Filter not found")]
554 #[matrix_api_error(status_code = StatusCode::NOT_FOUND, error_kind = ErrorKind::NotFound)]
555 UnknownFilter,
556}
557
558#[derive(Debug)]
560pub struct SyncData {
561 pub global_account_data_events: Vec<SyncGlobalAccountDataEvent>,
563 pub room_account_data_events: Vec<SyncRoomAccountDataEvent>,
565 pub room_events: Vec<SyncEvent>,
567 pub todevice_events: Vec<SyncToDeviceEvent>,
569 pub changed_user_keys: Vec<ChangedUserKeys>,
572 pub left_user_keys: Vec<OwnedUserId>,
575 pub read_receipts: SyncRoomReceipts,
578 pub federated_invites: Vec<(StreamId, FederatedInvite)>,
580 pub federated_knocks: Vec<(StreamId, FederatedInvite)>,
582}
583
584impl SyncData {
585 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 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 Room::Invite { .. } | Room::Knock(_) => {
731 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 .filter(|event| {
748 event.event_type() != &TimelineEventType::RoomMember
749 || event.state_key() != Some(user_id.as_str())
750 })
751 .collect();
752 state.push(invite_event);
755 Ok(state)
756 }
757 Room::Join(_) | Room::Leave(_) => {
758 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 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 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
823async 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 let (changed_user_keys, users_that_left_rooms) = match initial {
863 true => {
866 sync_token.user_keys_stream_position =
867 transaction.fetch_current_user_keys_stream_id().await?;
868 Default::default()
869 }
870 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
923async 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 Ok(sync_data) => Ok(sync_data?),
956 Err(_timeout) => {
958 fetch_sync_data(transaction, session, initial, sync_token, filter_definition).await
959 }
960 }
961}
962
963async 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
990pub(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 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 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 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 let timeout = request.timeout.unwrap_or(Duration::ZERO);
1051
1052 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 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 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 tracing::info!(?sync_response, "Made sync response");
1096
1097 Ok(sync_response)
1098}