Skip to main content

tuwunel_service/rooms/state_res/resolve/
mainline_sort.rs

1use std::collections::HashMap;
2
3use futures::{
4	FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt, pin_mut, stream::try_unfold,
5};
6use ruma::{EventId, OwnedEventId, events::TimelineEventType};
7use tuwunel_core::{
8	Error, Result, at,
9	matrix::{Event, event_id::RandomState},
10	trace,
11	utils::stream::{BroadbandExt, IterStream, TryReadyExt},
12};
13
14/// Mainline position of each power-levels event, oldest first.
15type Positions<'a> = HashMap<&'a EventId, usize, RandomState>;
16
17/// Perform mainline ordering of the given events.
18///
19/// Definition in the spec:
20/// Given mainline positions calculated from P, the mainline ordering based on P
21/// of a set of events is the ordering, from smallest to largest, using the
22/// following comparison relation on events: for events x and y, x < y if
23///
24/// 1. the mainline position of x is greater than the mainline position of y
25///    (i.e. the auth chain of x is based on an earlier event in the mainline
26///    than y); or
27/// 2. the mainline positions of the events are the same, but x’s
28///    origin_server_ts is less than y’s origin_server_ts; or
29/// 3. the mainline positions of the events are the same and the events have the
30///    same origin_server_ts, but x’s event_id is less than y’s event_id.
31///
32/// ## Arguments
33///
34/// * `events` - The list of event IDs to sort.
35/// * `power_level` - The power level event in the current state.
36/// * `fetch_event` - Function to fetch an event in the room given its event ID.
37///
38/// ## Returns
39///
40/// Returns the sorted list of event IDs, or an `Err(_)` if one the event in the
41/// room has an unexpected format.
42#[tracing::instrument(
43	level = "debug",
44	skip_all,
45	fields(
46		power_levels = power_level_event_id
47			.as_deref()
48			.map(EventId::as_str)
49			.unwrap_or_default(),
50	)
51)]
52pub(super) async fn mainline_sort<'a, RemainingEvents, Fetch, Fut, Pdu>(
53	power_level_event_id: Option<OwnedEventId>,
54	events: RemainingEvents,
55	fetch: &Fetch,
56) -> Result<Vec<OwnedEventId>>
57where
58	RemainingEvents: Stream<Item = &'a EventId> + Send,
59	Fetch: Fn(OwnedEventId) -> Fut + Sync,
60	Fut: Future<Output = Result<Pdu>> + Send,
61	Pdu: Event,
62{
63	// Populate the mainline of the power level.
64	let mainline: Vec<_> = try_unfold(power_level_event_id, async |power_level_event_id| {
65		let Some(power_level_event_id) = power_level_event_id else {
66			return Ok::<_, Error>(None);
67		};
68
69		let power_level_event = fetch(power_level_event_id).await?;
70		let this_event_id = power_level_event.event_id().to_owned();
71		let next_event_id = get_power_levels_auth_event(&power_level_event, fetch)
72			.map_ok(|event| {
73				event
74					.as_ref()
75					.map(Event::event_id)
76					.map(ToOwned::to_owned)
77			})
78			.await?;
79
80		trace!(?this_event_id, ?next_event_id, "mainline descent",);
81
82		Ok(Some((this_event_id, next_event_id)))
83	})
84	.try_collect()
85	.await?;
86
87	let positions: Positions<'_> = mainline
88		.iter()
89		.rev()
90		.map(AsRef::as_ref)
91		.enumerate()
92		.map(|(position, event_id)| (event_id, position))
93		.collect();
94
95	events
96		.map(ToOwned::to_owned)
97		.broad_filter_map(async |event_id| {
98			let event = fetch(event_id.clone()).await.ok()?;
99			let origin_server_ts = event.origin_server_ts();
100			let position = mainline_position(Some(event), &positions, fetch)
101				.await
102				.ok()?;
103
104			Some((event_id, (position, origin_server_ts)))
105		})
106		.inspect(|(event_id, (position, origin_server_ts))| {
107			trace!(position, ?origin_server_ts, ?event_id, "mainline position");
108		})
109		.collect()
110		.map(|mut vec: Vec<_>| {
111			vec.sort_by(|a, b| {
112				let (a_pos, a_ots) = &a.1;
113				let (b_pos, b_ots) = &b.1;
114				a_pos
115					.cmp(b_pos)
116					.then(a_ots.cmp(b_ots))
117					.then(a.cmp(b))
118			});
119
120			vec.into_iter().map(at!(0)).collect()
121		})
122		.map(Ok)
123		.await
124}
125
126/// Get the mainline position of the given event from the given mainline map.
127///
128/// ## Arguments
129///
130/// * `event` - The event to compute the mainline position of.
131/// * `positions` - The mainline positions of the m.room.power_levels events.
132/// * `fetch` - Function to fetch an event in the room given its event ID.
133///
134/// ## Returns
135///
136/// Returns the mainline position of the event, or an `Err(_)` if one of the
137/// events in the auth chain of the event was not found.
138#[tracing::instrument(
139	name = "position",
140	level = "trace",
141	ret(level = "trace"),
142	skip_all,
143	fields(
144		mainline = positions.len(),
145		event = ?current_event.as_ref().map(Event::event_id).map(ToOwned::to_owned),
146	)
147)]
148async fn mainline_position<Fetch, Fut, Pdu>(
149	mut current_event: Option<Pdu>,
150	positions: &Positions<'_>,
151	fetch: &Fetch,
152) -> Result<usize>
153where
154	Fetch: Fn(OwnedEventId) -> Fut + Sync,
155	Fut: Future<Output = Result<Pdu>> + Send,
156	Pdu: Event,
157{
158	while let Some(event) = current_event {
159		trace!(
160			event_id = ?event.event_id(),
161			"mainline position search",
162		);
163
164		// Real positions are 1..N (i + 1) so that 0 is free to mark
165		// "no power-levels in the auth chain". Without that, no-PL events
166		// would tie with events rooted at the oldest mainline PL.
167		if let Some(position) = positions.get(event.event_id()) {
168			return Ok(position.saturating_add(1));
169		}
170
171		// Look for the power levels event in the auth events.
172		current_event = get_power_levels_auth_event(&event, fetch).await?;
173	}
174
175	// No power-levels ancestor in the auth chain; sort before all
176	// chain-rooted events.
177	Ok(0)
178}
179
180#[expect(clippy::redundant_closure)]
181#[tracing::instrument(level = "trace", skip_all)]
182async fn get_power_levels_auth_event<Fetch, Fut, Pdu>(
183	event: &Pdu,
184	fetch: &Fetch,
185) -> Result<Option<Pdu>>
186where
187	Fetch: Fn(OwnedEventId) -> Fut + Sync,
188	Fut: Future<Output = Result<Pdu>> + Send,
189	Pdu: Event,
190{
191	let power_level_event = event
192		.auth_events()
193		.try_stream()
194		.map_ok(ToOwned::to_owned)
195		.and_then(|auth_event_id| fetch(auth_event_id))
196		.ready_try_skip_while(|auth_event| {
197			Ok(!auth_event.is_type_and_state_key(&TimelineEventType::RoomPowerLevels, ""))
198		});
199
200	pin_mut!(power_level_event);
201	power_level_event.try_next().await
202}