Skip to main content

tuwunel_api_client/delayed_events/
mod.rs

1//! Delayed events (MSC4140): scheduling, management and lookup.
2//!
3//! Scheduling applies the policy an ordinary send would at the time of the
4//! request, then checks the event against room auth without storing it. The
5//! service sends it once due through the ordinary append path.
6
7mod delay_query;
8mod get_delayed_event;
9mod get_delayed_events;
10mod send_delayed_event;
11mod update_delayed_event;
12
13#[cfg(test)]
14mod tests;
15
16use std::time::Duration;
17
18use axum::extract::MatchedPath;
19use futures::future::OptionFuture;
20use ruma::{
21	DeviceId, MilliSecondsSinceUnixEpoch, RoomId, TransactionId, UserId,
22	events::{
23		AnyMessageLikeEventContent, AnyStateEventContent, MessageLikeEventType, StateEventType,
24		TimelineEventType,
25	},
26	serde::Raw,
27};
28use serde_json::{from_str, value::RawValue as RawJsonValue};
29use tuwunel_core::{
30	Err, Result, err, implement,
31	utils::time::{millis, now_millis},
32};
33use tuwunel_matrix::pdu::PduBuilder;
34use tuwunel_service::{
35	Services,
36	delayed_events::{DelayId, Event, Limits},
37	rooms::timeline::RoomMutexGuard,
38	transaction_ids::RoomTxn,
39};
40
41pub(crate) use self::{
42	delay_query::{send_message_event_or_delayed_route, send_state_event_or_delayed_route},
43	get_delayed_event::get_delayed_event_route,
44	get_delayed_events::get_delayed_events_route,
45	send_delayed_event::send_delayed_event_route,
46	update_delayed_event::{update_delayed_event_by_body_route, update_delayed_event_route},
47};
48use crate::{
49	send::{check_send_content, check_send_policy},
50	state::{allowed_to_send_state_event, check_member_content_size},
51};
52
53struct Schedule<'a> {
54	sender: &'a UserId,
55	device: Option<&'a DeviceId>,
56	room_id: &'a RoomId,
57	kind: Kind<'a>,
58	delay: Duration,
59	txn_id: Option<&'a TransactionId>,
60	timestamp: Option<MilliSecondsSinceUnixEpoch>,
61
62	/// Selects the stable error codes over the unstable ones.
63	stable: bool,
64}
65
66#[derive(Clone, Copy)]
67enum Kind<'a> {
68	Message {
69		event_type: &'a MessageLikeEventType,
70		content: &'a Raw<AnyMessageLikeEventContent>,
71	},
72	State {
73		event_type: &'a StateEventType,
74		state_key: &'a str,
75		content: &'a Raw<AnyStateEventContent>,
76	},
77}
78
79/// Schedules an event and returns its `delay_id`.
80///
81/// A retried transaction returns the `delay_id` it first produced before the
82/// sender policy runs, so a policy change since cannot fail it. The user's lock
83/// is held from the capacity check through the insert, and the room's lock
84/// from after that check as an ordinary send holds it, so room auth sees the
85/// state the event would be sent against now.
86#[tracing::instrument(level = "debug", skip_all)]
87async fn schedule(
88	services: &Services,
89	Schedule {
90		sender,
91		device,
92		room_id,
93		kind,
94		delay,
95		txn_id,
96		timestamp,
97		stable,
98	}: Schedule<'_>,
99) -> Result<DelayId> {
100	let limits = services
101		.delayed_events
102		.limits()
103		.ok_or_else(|| err!(Request(Forbidden("Delayed events are disabled on this server."))))?;
104
105	let delay_ms = check_delay(delay, limits, stable)?;
106	let event_type = kind.event_type();
107	let txn = txn_id.map(|txn_id| RoomTxn {
108		user_id: sender,
109		device_id: device,
110		txn_id,
111		room_id,
112	});
113
114	let txn_type = event_type.to_cow_str();
115	let existing = || {
116		let lookup = txn.map(|txn| {
117			services
118				.delayed_events
119				.existing_txn(txn, &txn_type)
120		});
121
122		OptionFuture::from(lookup)
123	};
124
125	if let Some(delay_id) = existing().await.transpose()?.flatten() {
126		return Ok(delay_id);
127	}
128
129	let redacts = match kind {
130		| Kind::Message { event_type, content } =>
131			check_send_policy(services, sender, event_type, content).await?,
132		| Kind::State { event_type, state_key, content } => {
133			allowed_to_send_state_event(
134				services, sender, room_id, event_type, state_key, content,
135			)
136			.await?;
137
138			None
139		},
140	};
141
142	let _user_lock = services.delayed_events.lock_user(sender).await;
143
144	// A concurrent retry may have scheduled it while the policy ran.
145	if let Some(delay_id) = existing().await.transpose()?.flatten() {
146		return Ok(delay_id);
147	}
148
149	if let Kind::State { event_type, state_key, content } = kind
150		&& *event_type == StateEventType::RoomMember
151	{
152		check_member_content_size(services, sender, room_id, state_key, content).await?;
153	}
154
155	services
156		.delayed_events
157		.check_capacity(sender, limits.max_scheduled)
158		.await?;
159
160	let state_lock = services.state.mutex.lock(room_id).await;
161
162	if let Kind::Message { event_type, content } = kind {
163		check_send_content(services, sender, room_id, event_type, content).await?;
164	}
165
166	let content = from_str(kind.json().get())
167		.map_err(|e| err!(Request(BadJson("Invalid JSON body: {e}"))))?;
168
169	let event = Event {
170		user_id: sender.to_owned(),
171		room_id: room_id.to_owned(),
172		event_type,
173		state_key: kind.state_key().map(Into::into),
174		content,
175		redacts,
176		timestamp,
177		delay_ms,
178		due_ms: now_millis().saturating_add(delay_ms),
179		finalised: None,
180	};
181
182	check_room_auth(services, &event, &state_lock).await?;
183
184	Ok(services.delayed_events.schedule(&event, txn))
185}
186
187/// Returns the delay in milliseconds once it is positive and within limits.
188///
189/// An over-limit delay is refused with the stable or the unstable error code,
190/// as the request's path calls for.
191fn check_delay(delay: Duration, limits: Limits, stable: bool) -> Result<u64> {
192	let delay_ms = millis(delay).unwrap_or(u64::MAX);
193
194	if delay_ms == 0 {
195		return Err!(Request(InvalidParam(
196			"The delay must be a positive number of milliseconds."
197		)));
198	}
199
200	if delay_ms <= limits.max_delay_ms {
201		return Ok(delay_ms);
202	}
203
204	match stable {
205		| true => Err!(Request(DelayTooLarge("The delay exceeds the maximum allowed."))),
206		| false => Err!(Request(UnstableDelayTooLarge("The delay exceeds the maximum allowed."))),
207	}
208}
209
210#[implement(Kind, generics = "<'a>", params = "<'a>")]
211fn event_type(self) -> TimelineEventType {
212	match self {
213		| Self::Message { event_type, .. } => event_type.clone().into(),
214		| Self::State { event_type, .. } => event_type.clone().into(),
215	}
216}
217
218#[implement(Kind, generics = "<'a>", params = "<'a>")]
219fn state_key(self) -> Option<&'a str> {
220	match self {
221		| Self::Message { .. } => None,
222		| Self::State { state_key, .. } => Some(state_key),
223	}
224}
225
226#[implement(Kind, generics = "<'a>", params = "<'a>")]
227fn json(self) -> &'a RawJsonValue {
228	match self {
229		| Self::Message { content, .. } => content.json(),
230		| Self::State { content, .. } => content.json(),
231	}
232}
233
234/// Builds and signs the event against the room's current state without
235/// storing it, refusing one room auth would reject now.
236///
237/// A change to the sender's own membership skips the check when the server
238/// configuration says so.
239async fn check_room_auth(
240	services: &Services,
241	event: &Event,
242	state_lock: &RoomMutexGuard,
243) -> Result {
244	let own_membership = event.event_type == TimelineEventType::RoomMember
245		&& event.state_key.as_deref() == Some(event.user_id.as_str());
246
247	if own_membership
248		&& !services
249			.config
250			.rooms
251			.delayed_events
252			.check_own_membership
253	{
254		return Ok(());
255	}
256
257	let pdu = PduBuilder {
258		event_type: event.event_type.clone(),
259		content: event.content.clone(),
260		unsigned: None,
261		state_key: event.state_key.clone(),
262		redacts: event.redacts.clone(),
263		timestamp: event.timestamp,
264	};
265
266	services
267		.timeline
268		.create_hash_and_sign_event(pdu, &event.user_id, &event.room_id, state_lock)
269		.await
270		.map(|_| ())
271}
272
273fn is_stable(path: &MatchedPath) -> bool {
274	!path
275		.as_str()
276		.starts_with("/_matrix/client/unstable/")
277}