Skip to main content

tuwunel_service/rooms/event_handler/
handle_incoming_pdu.rs

1use futures::{FutureExt, TryFutureExt, TryStreamExt, future::try_join5};
2use ruma::{
3	CanonicalJsonObject, CanonicalJsonValue, EventId, OwnedEventId, RoomId, ServerName, UserId,
4	events::{
5		AnyStrippedStateEvent, StateEventType,
6		room::member::{MembershipState, RoomMemberEventContent},
7	},
8};
9use tuwunel_core::{
10	Err, Result, async_noinline, debug,
11	debug::INFO_SPAN_LEVEL,
12	debug_warn, err, implement,
13	matrix::{Event, PduCount, PduEvent, pdu::MAX_PREV_EVENTS, room_version::from_create_event},
14	smallvec::SmallVec,
15	trace,
16	utils::{
17		BoolExt,
18		future::ReadyEqExt,
19		stream::{IterStream, TryBroadbandExt, TryReadyExt},
20	},
21	warn,
22};
23
24use super::{
25	backoff::{Context, Disposition, Suppression, UPGRADE_RETRY},
26	fetch_prev::{Pdus, PrevFetch},
27	handle_prev_pdu::PrevUpgrade,
28	prev_walk::PrevWalk,
29	room_version_of,
30};
31use crate::rooms::{state_cache::MembershipUpdate, timeline::RawPduId};
32
33type PrevSplit = SmallVec<[OwnedEventId; MAX_PREV_EVENTS]>;
34
35type Handled = Option<(RawPduId, bool)>;
36
37/// When receiving an event one needs to:
38/// 0. Check the server is in the room
39/// 1. Skip the PDU if we already know about it
40/// 1.1. Remove unsigned field
41/// 2. Check signatures, otherwise drop
42/// 3. Check content hash, redact if doesn't match
43/// 4. Fetch any missing auth events doing all checks listed here starting at 1.
44///    These are not timeline events
45/// 5. Reject "due to auth events" if can't get all the auth events or some of
46///    the auth events are also rejected "due to auth events"
47/// 6. Reject "due to auth events" if the event doesn't pass auth based on the
48///    auth events
49/// 7. Persist this event as an outlier
50/// 8. If not timeline event: stop
51/// 9. Fetch any missing prev events doing all checks listed here starting at 1.
52///    These are timeline events
53/// 10. Fetch missing state and auth chain events by calling `/state_ids` at
54///     backwards extremities doing all the checks in this list starting at
55///     1. These are not timeline events
56/// 11. Check the auth of the event passes based on the state of the event
57/// 12. Ensure that the state is derived from the previous current state (i.e.
58///     we calculated by doing state res where one of the inputs was a
59///     previously trusted set of state, don't just trust a set of state we got
60///     from a remote)
61/// 13. Use state resolution to find new room state
62/// 14. Check if the event passes auth based on the "current state" of the room,
63///     if not soft fail it
64#[implement(super::Service)]
65// cross-crate codegen firewall
66#[async_noinline]
67#[tracing::instrument(
68	name = "pdu",
69	level = INFO_SPAN_LEVEL,
70	skip_all,
71	fields(%room_id, %event_id),
72	ret(level = "debug"),
73)]
74pub async fn handle_incoming_pdu<'a>(
75	&'a self,
76	origin: &'a ServerName,
77	room_id: &'a RoomId,
78	event_id: &'a EventId,
79	pdu: CanonicalJsonObject,
80	is_timeline_event: bool,
81) -> Result<Handled> {
82	// 1. Skip the PDU if we already have it as a timeline event
83	if let Ok(pdu_id) = self.services.timeline.get_pdu_id(event_id).await {
84		debug!(?pdu_id, "Exists.");
85		return Ok(Some((pdu_id, false)));
86	}
87
88	// 1.1 Check the server is in the room
89	let meta_exists = self.services.metadata.exists(room_id).map(Ok);
90
91	// 1.2 Check if the room is disabled
92	let is_disabled = self
93		.services
94		.metadata
95		.is_disabled(room_id)
96		.map(Ok);
97
98	// 1.3.1 Check room ACL on origin field/server
99	let origin_acl_check = self.acl_check(origin, room_id);
100
101	// 1.3.2 Check room ACL on sender's server name
102	let sender: &UserId = pdu
103		.get("sender")
104		.try_into()
105		.map_err(|e| err!(Request(InvalidParam("PDU does not have a valid sender key: {e}"))))?;
106
107	let sender_acl_check = sender
108		.server_name()
109		.ne(origin)
110		.then_async(|| self.acl_check(sender.server_name(), room_id));
111
112	// Fetch create event; absent when we are not resident in the room.
113	let create_event = self
114		.services
115		.state_accessor
116		.room_state_get(room_id, &StateEventType::RoomCreate, "")
117		.map(|result| Ok(result.ok()));
118
119	let (meta_exists, is_disabled, (), (), create_event) = try_join5(
120		meta_exists,
121		is_disabled,
122		origin_acl_check,
123		sender_acl_check.map(|o| o.unwrap_or(Ok(()))),
124		create_event,
125	)
126	.await?;
127
128	// When not resident, the only event we can act on is a leave rescinding an
129	// out-of-band invite we hold for a local user.
130	if !meta_exists {
131		return if self
132			.handle_rescinded_invite(room_id, &pdu)
133			.await?
134		{
135			Ok(None)
136		} else {
137			Err!(Request(NotFound("Room is unknown to this server")))
138		};
139	}
140
141	if is_disabled {
142		return Err!(Request(Forbidden("Federation of this room is disabled by this server.")));
143	}
144
145	let create_event =
146		create_event.ok_or_else(|| err!(Request(NotFound("Room is unknown to this server"))))?;
147
148	let room_version = from_create_event(&create_event)?;
149	let recursion_level = 0;
150
151	let (incoming_pdu, pdu) = self
152		.handle_outlier_pdu(origin, room_id, event_id, pdu, &room_version, recursion_level, false)
153		.await?;
154
155	// 8. if not timeline event: stop
156	if !is_timeline_event {
157		debug!(
158			kind = ?incoming_pdu.event_type(),
159			"Not a timeline event.",
160		);
161
162		return Ok(None);
163	}
164
165	// Skip old events
166	let first_ts_in_room = self
167		.services
168		.timeline
169		.first_pdu_in_room(room_id)
170		.await?
171		.origin_server_ts();
172
173	if incoming_pdu.origin_server_ts() < first_ts_in_room {
174		debug!(
175			origin_server_ts = ?incoming_pdu.origin_server_ts(),
176			?first_ts_in_room,
177			"Skipping old event."
178		);
179		return Ok(None);
180	}
181
182	let gapped = self
183		.services
184		.timeline
185		.non_outlier_pdus_exist(incoming_pdu.prev_events())
186		.await
187		.is_false();
188
189	self.prev_walk.enter(gapped);
190
191	let create_event_id = create_event.event_id();
192	let upgrade = PrevUpgrade {
193		origin,
194		room_id,
195		event_id,
196		room_version: &room_version,
197		recursion_level,
198		first_ts_in_room,
199		create_event_id,
200	};
201
202	// Start before the first await so a dropped future still settles the gapped count.
203	let pass = gapped.then(|| PrevWalk::start(self, &upgrade));
204
205	let standing = gapped
206		.then_async(|| self.is_suppressed(Context::Incoming, event_id, UPGRADE_RETRY))
207		.await
208		.unwrap_or(Suppression::Absent);
209
210	if standing.is_deny() {
211		if let Some(pass) = pass {
212			pass.hold();
213		}
214
215		debug_warn!(%origin, %room_id, %event_id, "Backing off from a gapped incoming event.");
216		return Ok(None);
217	}
218
219	// 9. Fetch any missing prev events doing all checks listed here starting at 1.
220	//    These are timeline events
221	let fetch = self
222		.fetch_prev(upgrade, incoming_pdu.prev_events())
223		.await;
224
225	let stopping = self.services.server.is_stopping();
226	let walk = pass.and_then(|pass| pass.fetched(fetch.as_ref(), stopping));
227	let fetch = fetch?;
228
229	let walking = fetch.sorted.is_empty().is_false();
230	let attempt = walking.then(|| self.record_attempt(Context::Incoming, event_id));
231	let PrevFetch { sorted, pdus, .. } = fetch;
232
233	let (handled, upgraded) = self
234		.handle_prev_events(upgrade, sorted, pdus)
235		.boxed() // size firewall
236		.and_then(|upgraded| {
237			self.upgrade_outlier_to_timeline_pdu(upgrade, incoming_pdu, pdu)
238				.boxed() // size firewall
239				.map(move |handled| Ok((handled, upgraded)))
240		})
241		.unwrap_or_else(|error| (Err(error), 0))
242		.await;
243
244	let appended = handled.as_ref().map(Option::is_some);
245
246	if let Some(walk) = walk {
247		walk.settle(appended, upgraded, self.services.server.is_stopping());
248	}
249
250	self.record_completion(Context::Incoming, event_id, standing, attempt, appended)
251		.await;
252
253	handled
254}
255
256/// Apply a federated leave that rescinds an out-of-band invite for a local
257/// user.
258///
259/// We are not resident in the room, so the kick cannot be processed as a normal
260/// timeline event for lack of room state; but it must still clear the invite so
261/// the invited user's `/sync` reflects the rescission. Mirrors Synapse's
262/// out-of-band membership handling: only a kick from the original inviter is
263/// honored, since without the room state we cannot judge any other sender's
264/// authority. Returns `true` when a rescission was applied.
265#[implement(super::Service)]
266#[tracing::instrument(skip_all, level = "debug", fields(%room_id))]
267async fn handle_rescinded_invite(
268	&self,
269	room_id: &RoomId,
270	pdu: &CanonicalJsonObject,
271) -> Result<bool> {
272	if pdu
273		.get("type")
274		.and_then(CanonicalJsonValue::as_str)
275		!= Some("m.room.member")
276	{
277		return Ok(false);
278	}
279
280	let Some(target) = pdu
281		.get("state_key")
282		.and_then(CanonicalJsonValue::as_str)
283		.and_then(|state_key| UserId::parse(state_key).ok())
284	else {
285		return Ok(false);
286	};
287
288	let Some(sender) = pdu
289		.get("sender")
290		.and_then(CanonicalJsonValue::as_str)
291		.and_then(|sender| UserId::parse(sender).ok())
292	else {
293		return Ok(false);
294	};
295
296	if sender == target || !self.services.globals.user_is_local(&target) {
297		return Ok(false);
298	}
299
300	let Some(content) = pdu
301		.get("content")
302		.cloned()
303		.map(Into::into)
304		.and_then(|content| serde_json::from_value::<RoomMemberEventContent>(content).ok())
305	else {
306		return Ok(false);
307	};
308
309	if content.membership != MembershipState::Leave {
310		return Ok(false);
311	}
312
313	if self
314		.services
315		.state_cache
316		.user_membership(&target, room_id)
317		.ne(&Some(MembershipState::Invite))
318		.await
319	{
320		return Ok(false);
321	}
322
323	// Recover the inviter and the room version from the stored stripped state.
324	let invite_state = self
325		.services
326		.state_cache
327		.invite_state(&target, room_id)
328		.await?;
329
330	let inviter = invite_state
331		.iter()
332		.find_map(|event| match event.deserialize() {
333			| Ok(AnyStrippedStateEvent::RoomMember(member)) if member.state_key == target =>
334				Some(member.sender),
335			| _ => None,
336		});
337
338	// Honor the rescission only from the original inviter.
339	if inviter.as_ref() != Some(&sender) {
340		return Ok(false);
341	}
342
343	let Some(room_version_id) = room_version_of(&invite_state) else {
344		return Ok(false);
345	};
346
347	// Verify the kick is signed by the sender's server before acting on it.
348	self.services
349		.server_keys
350		.verify_event(pdu, Some(&room_version_id))
351		.await
352		.map_err(|e| {
353			err!(Request(InvalidParam("Invite rescission signature is invalid: {e}")))
354		})?;
355
356	let count = self.services.globals.next_count();
357	self.services
358		.state_cache
359		.update_membership(MembershipUpdate {
360			room_id,
361			user_id: &target,
362			membership_event: RoomMemberEventContent::new(MembershipState::Leave),
363			sender: &sender,
364			last_state: None,
365			invite_via: None,
366			update_joined_count: false,
367			count: PduCount::Normal(*count),
368		})
369		.await?;
370
371	debug!(%room_id, %target, %sender, "Applied a federated invite rescission.");
372
373	Ok(true)
374}
375
376/// Upgrade an incoming PDU's previous events, walking interior events after
377/// their parents so each derives state locally instead of refetching it.
378///
379/// Extremities upgrade concurrently up to `prev_events_concurrency`; interior
380/// events upgrade one at a time. Returns how many previous events were upgraded.
381#[implement(super::Service)]
382async fn handle_prev_events(
383	&self,
384	upgrade: PrevUpgrade<'_>,
385	sorted_prev_events: Vec<OwnedEventId>,
386	mut pdus: Pdus, // HashMap::remove takes &mut self
387) -> Result<usize> {
388	trace!(
389		events = sorted_prev_events.len(),
390		event_ids = ?sorted_prev_events,
391		"Handling previous events"
392	);
393
394	let (interior, extremities): (PrevSplit, PrevSplit) = sorted_prev_events
395		.into_iter()
396		.partition(|prev_id| {
397			pdus.get(prev_id).is_some_and(|(pdu, _)| {
398				pdu.prev_events()
399					.any(|prev| pdus.contains_key(prev))
400			})
401		});
402
403	let concurrency = usize::from(
404		self.services
405			.server
406			.config
407			.prev_events_concurrency,
408	);
409
410	let upgraded = extremities
411		.into_iter()
412		.try_stream()
413		.map_ok(|prev_id| (pdus.remove(&prev_id), prev_id))
414		.broadn_and_then(concurrency, async |(info, prev_id)| {
415			self.upgrade_prev_event(upgrade, info, &prev_id)
416				.await
417		})
418		.ready_try_fold(0, tally_upgraded)
419		.boxed() // size firewall
420		.await?;
421
422	// Walk interior events forward so each parent commits before its children.
423	interior
424		.into_iter()
425		.try_stream()
426		.map_ok(|prev_id| (pdus.remove(&prev_id), prev_id))
427		.and_then(async |(info, prev_id)| {
428			self.upgrade_prev_event(upgrade, info, &prev_id)
429				.await
430		})
431		.ready_try_fold(upgraded, tally_upgraded)
432		.boxed() // size firewall
433		.await
434}
435
436/// Upgrade one previous event, folding a failure into a non-fatal skip so a
437/// single bad prev does not abort the batch.
438///
439/// A failure records a transient backoff for the prev. A shutdown propagates
440/// through the leading running check; an interruption surfacing from inside
441/// the upgrade is not a verdict on the prev and records nothing.
442#[implement(super::Service)]
443async fn upgrade_prev_event(
444	&self,
445	upgrade: PrevUpgrade<'_>,
446	info: Option<(PduEvent, CanonicalJsonObject)>,
447	prev_id: &EventId,
448) -> Result<Handled> {
449	self.services.server.check_running()?;
450
451	let PrevUpgrade { room_id, event_id, .. } = upgrade;
452
453	match self.handle_prev_pdu(upgrade, info, prev_id).await {
454		| Err(error) if error.is_interrupted() || self.services.server.is_stopping() => {
455			debug!(?prev_id, ?event_id, ?room_id, %error, "Prev event processing interrupted.");
456
457			Ok(None)
458		},
459		| Err(error) => {
460			self.record_outcome(Context::Upgrade, prev_id, Disposition::Transient);
461			warn!(?prev_id, ?event_id, ?room_id, %error, "Prev event processing failed.");
462
463			Ok(None)
464		},
465		| Ok(None) => {
466			debug_warn!(?prev_id, "Prev event not processed.");
467
468			Ok(None)
469		},
470		| Ok(handled) => {
471			self.record_success(Context::Upgrade, prev_id)
472				.await;
473
474			debug!(?prev_id, ?handled, "Prev event processed.");
475
476			Ok(handled)
477		},
478	}
479}
480
481fn tally_upgraded(upgraded: usize, handled: Handled) -> Result<usize> {
482	Ok(upgraded.saturating_add(usize::from(handled.is_some())))
483}