tuwunel_api_client/delayed_events/
mod.rs1mod 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 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#[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 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
187fn 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
234async 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}