1use std::collections::HashMap;
2
3use futures::{FutureExt, TryStreamExt, future::try_join5};
4use ruma::{
5 CanonicalJsonObject, CanonicalJsonValue, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId,
6 RoomId, RoomVersionId, ServerName, UserId,
7 events::{
8 AnyStrippedStateEvent, StateEventType,
9 room::member::{MembershipState, RoomMemberEventContent},
10 },
11};
12use tuwunel_core::{
13 Err, Result, async_noinline, debug,
14 debug::INFO_SPAN_LEVEL,
15 debug_warn, err, implement,
16 matrix::{Event, PduCount, PduEvent, pdu::MAX_PREV_EVENTS, room_version::from_create_event},
17 smallvec::SmallVec,
18 trace,
19 utils::{
20 BoolExt,
21 stream::{IterStream, TryWidebandExt},
22 },
23 warn,
24};
25
26use super::backoff::{Context, Disposition};
27use crate::rooms::{state_cache::MembershipUpdate, timeline::RawPduId};
28
29type PrevResultsHandled = SmallVec<[PrevHandled; MAX_PREV_EVENTS]>;
30type PrevHandled = (OwnedEventId, Handled);
31type PrevSplit = SmallVec<[OwnedEventId; MAX_PREV_EVENTS]>;
32
33type Handled = Option<(RawPduId, bool)>;
34
35#[implement(super::Service)]
63#[async_noinline]
64#[tracing::instrument(
65 name = "pdu",
66 level = INFO_SPAN_LEVEL,
67 skip_all,
68 fields(%room_id, %event_id),
69 ret(level = "debug"),
70)]
71pub async fn handle_incoming_pdu<'a>(
72 &'a self,
73 origin: &'a ServerName,
74 room_id: &'a RoomId,
75 event_id: &'a EventId,
76 pdu: CanonicalJsonObject,
77 is_timeline_event: bool,
78) -> Result<Handled> {
79 if let Ok(pdu_id) = self.services.timeline.get_pdu_id(event_id).await {
81 debug!(?pdu_id, "Exists.");
82 return Ok(Some((pdu_id, false)));
83 }
84
85 let meta_exists = self.services.metadata.exists(room_id).map(Ok);
87
88 let is_disabled = self
90 .services
91 .metadata
92 .is_disabled(room_id)
93 .map(Ok);
94
95 let origin_acl_check = self.acl_check(origin, room_id);
97
98 let sender: &UserId = pdu
100 .get("sender")
101 .try_into()
102 .map_err(|e| err!(Request(InvalidParam("PDU does not have a valid sender key: {e}"))))?;
103
104 let sender_acl_check = sender
105 .server_name()
106 .ne(origin)
107 .then_async(|| self.acl_check(sender.server_name(), room_id));
108
109 let create_event = self
111 .services
112 .state_accessor
113 .room_state_get(room_id, &StateEventType::RoomCreate, "")
114 .map(|result| Ok(result.ok()));
115
116 let (meta_exists, is_disabled, (), (), create_event) = try_join5(
117 meta_exists,
118 is_disabled,
119 origin_acl_check,
120 sender_acl_check.map(|o| o.unwrap_or(Ok(()))),
121 create_event,
122 )
123 .await?;
124
125 if !meta_exists {
128 return if self
129 .handle_rescinded_invite(room_id, &pdu)
130 .await?
131 {
132 Ok(None)
133 } else {
134 Err!(Request(NotFound("Room is unknown to this server")))
135 };
136 }
137
138 if is_disabled {
139 return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
140 }
141
142 let create_event =
143 create_event.ok_or_else(|| err!(Request(NotFound("Room is unknown to this server"))))?;
144
145 let room_version = from_create_event(&create_event)?;
146 let recursion_level = 0;
147
148 let (incoming_pdu, pdu) = self
149 .handle_outlier_pdu(origin, room_id, event_id, pdu, &room_version, recursion_level, false)
150 .await?;
151
152 if !is_timeline_event {
154 debug!(
155 kind = ?incoming_pdu.event_type(),
156 "Not a timeline event.",
157 );
158 return Ok(None);
159 }
160
161 let first_ts_in_room = self
163 .services
164 .timeline
165 .first_pdu_in_room(room_id)
166 .await?
167 .origin_server_ts();
168
169 if incoming_pdu.origin_server_ts() < first_ts_in_room {
170 debug!(
171 origin_server_ts = ?incoming_pdu.origin_server_ts(),
172 ?first_ts_in_room,
173 "Skipping old event."
174 );
175 return Ok(None);
176 }
177
178 let (sorted_prev_events, eventid_info) = self
181 .fetch_prev(
182 origin,
183 room_id,
184 event_id,
185 incoming_pdu.prev_events(),
186 &room_version,
187 recursion_level,
188 first_ts_in_room,
189 )
190 .await?;
191
192 self.handle_prev_events(
193 origin,
194 room_id,
195 event_id,
196 sorted_prev_events,
197 eventid_info,
198 &room_version,
199 recursion_level,
200 first_ts_in_room,
201 create_event.event_id(),
202 )
203 .boxed()
204 .await?;
205
206 self.upgrade_outlier_to_timeline_pdu(
208 origin,
209 room_id,
210 incoming_pdu,
211 pdu,
212 &room_version,
213 recursion_level,
214 create_event.event_id(),
215 )
216 .boxed()
217 .await
218}
219
220#[implement(super::Service)]
230#[tracing::instrument(skip_all, level = "debug", fields(%room_id))]
231async fn handle_rescinded_invite(
232 &self,
233 room_id: &RoomId,
234 pdu: &CanonicalJsonObject,
235) -> Result<bool> {
236 if pdu
237 .get("type")
238 .and_then(CanonicalJsonValue::as_str)
239 != Some("m.room.member")
240 {
241 return Ok(false);
242 }
243
244 let Some(target) = pdu
245 .get("state_key")
246 .and_then(CanonicalJsonValue::as_str)
247 .and_then(|state_key| UserId::parse(state_key).ok())
248 else {
249 return Ok(false);
250 };
251
252 let Some(sender) = pdu
253 .get("sender")
254 .and_then(CanonicalJsonValue::as_str)
255 .and_then(|sender| UserId::parse(sender).ok())
256 else {
257 return Ok(false);
258 };
259
260 if sender == target || !self.services.globals.user_is_local(&target) {
261 return Ok(false);
262 }
263
264 let Some(content) = pdu
265 .get("content")
266 .cloned()
267 .map(Into::into)
268 .and_then(|content| serde_json::from_value::<RoomMemberEventContent>(content).ok())
269 else {
270 return Ok(false);
271 };
272
273 if content.membership != MembershipState::Leave {
274 return Ok(false);
275 }
276
277 if self
278 .services
279 .state_cache
280 .user_membership(&target, room_id)
281 .await != Some(MembershipState::Invite)
282 {
283 return Ok(false);
284 }
285
286 let invite_state = self
288 .services
289 .state_cache
290 .invite_state(&target, room_id)
291 .await?;
292
293 let inviter = invite_state
294 .iter()
295 .find_map(|event| match event.deserialize() {
296 | Ok(AnyStrippedStateEvent::RoomMember(member)) if member.state_key == target =>
297 Some(member.sender),
298 | _ => None,
299 });
300
301 if inviter.as_ref() != Some(&sender) {
303 return Ok(false);
304 }
305
306 let Some(room_version_id) = super::room_version_of(&invite_state) else {
307 return Ok(false);
308 };
309
310 self.services
312 .server_keys
313 .verify_event(pdu, Some(&room_version_id))
314 .await
315 .map_err(|e| {
316 err!(Request(InvalidParam("Invite rescission signature is invalid: {e}")))
317 })?;
318
319 let count = self.services.globals.next_count();
320 self.services
321 .state_cache
322 .update_membership(MembershipUpdate {
323 room_id,
324 user_id: &target,
325 membership_event: RoomMemberEventContent::new(MembershipState::Leave),
326 sender: &sender,
327 last_state: None,
328 invite_via: None,
329 update_joined_count: false,
330 count: PduCount::Normal(*count),
331 })
332 .await?;
333
334 debug!(%room_id, %target, %sender, "Applied a federated invite rescission.");
335
336 Ok(true)
337}
338
339#[implement(super::Service)]
342#[expect(clippy::too_many_arguments)]
343async fn handle_prev_events(
344 &self,
345 origin: &ServerName,
346 room_id: &RoomId,
347 event_id: &EventId,
348 sorted_prev_events: Vec<OwnedEventId>,
349 mut eventid_info: HashMap<OwnedEventId, (PduEvent, CanonicalJsonObject)>,
350 room_version: &RoomVersionId,
351 recursion_level: usize,
352 first_ts_in_room: MilliSecondsSinceUnixEpoch,
353 create_event_id: &EventId,
354) -> Result<()> {
355 trace!(
356 events = sorted_prev_events.len(),
357 event_ids = ?sorted_prev_events,
358 "Handling previous events"
359 );
360
361 let (interior, extremities): (PrevSplit, PrevSplit) = sorted_prev_events
362 .into_iter()
363 .partition(|prev_id| {
364 eventid_info.get(prev_id).is_some_and(|(pdu, _)| {
365 pdu.prev_events()
366 .any(|prev| eventid_info.contains_key(prev))
367 })
368 });
369
370 extremities
371 .into_iter()
372 .try_stream()
373 .map_ok(|prev_id| (eventid_info.remove(&prev_id), prev_id))
374 .widen_and_then(MAX_PREV_EVENTS, async |(info, prev_id)| {
375 self.upgrade_prev_event(
376 origin,
377 room_id,
378 event_id,
379 info,
380 room_version,
381 recursion_level,
382 first_ts_in_room,
383 prev_id,
384 create_event_id,
385 )
386 .await
387 })
388 .try_collect::<PrevResultsHandled>()
389 .boxed()
390 .await?;
391
392 interior
394 .into_iter()
395 .try_stream()
396 .map_ok(|prev_id| (eventid_info.remove(&prev_id), prev_id))
397 .try_for_each(async |(info, prev_id)| {
398 self.upgrade_prev_event(
399 origin,
400 room_id,
401 event_id,
402 info,
403 room_version,
404 recursion_level,
405 first_ts_in_room,
406 prev_id,
407 create_event_id,
408 )
409 .await?;
410
411 Ok(())
412 })
413 .boxed()
414 .await
415}
416
417#[implement(super::Service)]
420#[expect(clippy::too_many_arguments)]
421async fn upgrade_prev_event(
422 &self,
423 origin: &ServerName,
424 room_id: &RoomId,
425 event_id: &EventId,
426 info: Option<(PduEvent, CanonicalJsonObject)>,
427 room_version: &RoomVersionId,
428 recursion_level: usize,
429 first_ts_in_room: MilliSecondsSinceUnixEpoch,
430 prev_id: OwnedEventId,
431 create_event_id: &EventId,
432) -> Result<PrevHandled> {
433 self.services.server.check_running()?;
434 match self
435 .handle_prev_pdu(
436 origin,
437 room_id,
438 event_id,
439 info,
440 room_version,
441 recursion_level,
442 first_ts_in_room,
443 &prev_id,
444 create_event_id,
445 )
446 .await
447 {
448 | Ok(handled) => {
449 if handled.is_some() {
450 self.record_success(Context::Upgrade, &prev_id)
451 .await;
452 debug!(?prev_id, ?handled, "Prev event processed.");
453 } else {
454 debug_warn!(?prev_id, "Prev event not processed.");
455 }
456
457 Ok((prev_id, handled))
458 },
459 | Err(e) => {
460 self.record_outcome(Context::Upgrade, &prev_id, Disposition::Transient);
461 warn!(?prev_id, ?event_id, ?room_id, "Prev event processing failed: {e}");
462
463 Ok((prev_id, None))
464 },
465 }
466}