1use axum::extract::State;
2use futures::{
3 FutureExt, Stream, StreamExt, TryFutureExt, TryStreamExt,
4 future::{OptionFuture, join, join3, try_join},
5};
6use ruma::{
7 DeviceId, EventId, OwnedEventId, RoomId, UInt, UserId,
8 api::client::{context::get_context, filter::RoomEventFilter},
9 events::{AnyStateEvent, StateEventType},
10 serde::Raw,
11};
12use tuwunel_core::{
13 Err, Event, Result, at, debug_warn, err,
14 matrix::pdu::{PduEvent, RawPduId},
15 ref_at,
16 utils::{
17 BoolExt, IterStream,
18 future::TryExtExt,
19 math::usize_from_ruma_bounded,
20 stream::{BroadbandExt, ReadyExt, TryIgnore, WidebandExt},
21 },
22};
23use tuwunel_service::{
24 Services,
25 rooms::{
26 lazy_loading,
27 lazy_loading::{Options, Witness},
28 short::{ShortRoomId, ShortStateKey},
29 timeline::PdusIterItem,
30 },
31};
32
33use crate::{
34 Ruma,
35 client::{
36 is_ignored_pdu,
37 message::{
38 add_membership_unsigned, event_filter, event_filters, ignored_filter,
39 lazy_loading_witness, related_by_filter, with_membership,
40 },
41 },
42};
43
44const LIMIT_MAX: usize = 100;
45const LIMIT_DEFAULT: usize = 10;
46
47pub(crate) async fn get_context_route(
54 State(services): State<crate::State>,
55 body: Ruma<get_context::v3::Request>,
56) -> Result<get_context::v3::Response> {
57 event_context(&services, ContextArgs {
58 room_id: &body.room_id,
59 event_id: &body.event_id,
60 sender_user: body.sender_user(),
61 sender_device: body.sender_device.as_deref(),
62 filter: &body.filter,
63 limit: Some(body.limit),
64 bypass_visibility: false,
65 })
66 .await
67}
68
69pub(crate) struct ContextArgs<'a> {
72 pub room_id: &'a RoomId,
73 pub event_id: &'a EventId,
74 pub sender_user: &'a UserId,
75 pub sender_device: Option<&'a DeviceId>,
76 pub filter: &'a RoomEventFilter,
77 pub limit: Option<UInt>,
78
79 pub bypass_visibility: bool,
82}
83
84pub(crate) async fn event_context(
88 services: &Services,
89 args: ContextArgs<'_>,
90) -> Result<get_context::v3::Response> {
91 let ContextArgs {
92 room_id,
93 event_id,
94 sender_user,
95 sender_device,
96 filter,
97 limit,
98 bypass_visibility,
99 } = args;
100
101 if !services.metadata.exists(room_id).await {
102 return Err!(Request(Forbidden("Room does not exist to this server")));
103 }
104
105 let limit = limit
106 .map_or(LIMIT_DEFAULT, |limit| usize_from_ruma_bounded(limit, LIMIT_DEFAULT, LIMIT_MAX));
107
108 let (base_id, base_pdu) =
109 resolve_base_event(services, room_id, event_id, sender_user, bypass_visibility).await?;
110
111 let base_count = base_id.pdu_count();
112
113 let encrypted = services
114 .state_accessor
115 .is_encrypted_room(room_id)
116 .await;
117
118 let shortroomid = services.short.get_shortroomid(room_id).await?;
119
120 let base_event = async {
121 let item = if bypass_visibility {
122 (base_count, base_pdu)
123 } else {
124 ignored_filter(services, (base_count, base_pdu), sender_user).await?
125 };
126
127 Some(add_membership_unsigned(services, item, sender_user, encrypted).await)
128 };
129
130 let half = TimelineHalf {
131 services,
132 filter,
133 shortroomid,
134 sender_user,
135 encrypted,
136 bypass_visibility,
137 };
138
139 let events_before = collect_timeline_half(
140 half,
141 services
142 .timeline
143 .pdus_rev(Some(sender_user), room_id, Some(base_count)),
144 limit / 2,
145 );
146
147 let events_after = collect_timeline_half(
148 half,
149 services
150 .timeline
151 .pdus(Some(sender_user), room_id, Some(base_count)),
152 limit.div_ceil(2),
153 );
154
155 let (base_event, events_before, events_after): (_, Vec<_>, Vec<_>) =
156 join3(base_event, events_before, events_after)
157 .boxed()
158 .await;
159
160 let lazy_loading_context = lazy_loading::Context {
161 user_id: sender_user,
162 device_id: sender_device,
163 room_id,
164 token: Some(base_count.into_unsigned()),
165 options: Some(&filter.lazy_load_options),
166 mode: lazy_loading::Mode::Update,
167 };
168
169 let lazy_loading_witnessed = filter
170 .lazy_load_options
171 .is_enabled()
172 .then_async(|| {
173 let witnessed = base_event
174 .iter()
175 .chain(events_before.iter())
176 .chain(events_after.iter());
177
178 lazy_loading_witness(services, &lazy_loading_context, witnessed)
179 });
180
181 let state_at = events_after
182 .last()
183 .map(ref_at!(1))
184 .map_or_else(|| event_id, |pdu| pdu.event_id.as_ref());
185
186 let (lazy_loading_witnessed, state_ids) =
187 join(lazy_loading_witnessed, load_state_ids(services, room_id, state_at)).await;
188
189 let state = build_state_response(
190 services,
191 state_ids?,
192 lazy_loading_witnessed.unwrap_or_default(),
193 filter,
194 sender_user,
195 encrypted,
196 )
197 .await;
198
199 let event = OptionFuture::from(base_event.map(at!(1)).map(|pdu| {
200 services
201 .pdu_metadata
202 .bundle_aggregations(sender_user, pdu)
203 }))
204 .await
205 .map(Event::into_format);
206
207 Ok(get_context::v3::Response {
208 event,
209
210 start: events_before
211 .last()
212 .map(at!(0))
213 .or(Some(base_count))
214 .as_ref()
215 .map(ToString::to_string),
216
217 end: events_after
220 .last()
221 .map(at!(0))
222 .or_else(|| Some(base_count.saturating_add(1)))
223 .as_ref()
224 .map(ToString::to_string),
225
226 events_before: events_before
227 .into_iter()
228 .map(at!(1))
229 .map(Event::into_format)
230 .collect(),
231
232 events_after: events_after
233 .into_iter()
234 .map(at!(1))
235 .map(Event::into_format)
236 .collect(),
237
238 state,
239 })
240}
241
242async fn resolve_base_event(
243 services: &Services,
244 room_id: &RoomId,
245 event_id: &EventId,
246 sender_user: &UserId,
247 bypass_visibility: bool,
248) -> Result<(RawPduId, PduEvent)> {
249 let lookup = || {
250 let base_id = services
251 .timeline
252 .get_pdu_id(event_id)
253 .map_err(|_| err!(Request(NotFound("Event not found."))));
254
255 let base_pdu = services
256 .timeline
257 .get_pdu(event_id)
258 .map_err(|_| err!(Request(NotFound("Base event not found."))));
259
260 try_join(base_id, base_pdu)
261 };
262
263 let resolve_remote = services
264 .config
265 .fetch_unreceived_contexts_over_federation
266 && services.config.allow_federation;
267
268 let (base_id, base_pdu) = match lookup().await {
269 | Ok(found) => found,
270 | Err(e) if !resolve_remote => return Err(e),
271 | Err(_) => {
272 services
273 .timeline
274 .fetch_remote_event(room_id, event_id)
275 .await
276 .ok();
277
278 lookup().await?
279 },
280 };
281
282 if base_pdu.room_id != *room_id || base_pdu.event_id != *event_id {
283 return Err!(Request(NotFound("Base event not found.")));
284 }
285
286 if !bypass_visibility
287 && !services
288 .state_accessor
289 .user_can_see_event(sender_user, &base_pdu)
290 .await
291 {
292 debug_warn!(
293 req_evt = ?event_id, ?base_id, ?room_id, %sender_user,
294 "Event requested but the requester is not allowed to see it."
295 );
296
297 return Err!(Request(NotFound("Event not found.")));
298 }
299
300 if !bypass_visibility && is_ignored_pdu(services, &base_pdu, sender_user).await {
301 return Err!(HttpJson(NOT_FOUND, {
302 "errcode": "M_SENDER_IGNORED",
303 "error": "You have ignored the user that sent this event",
304 "sender": base_pdu.sender().as_str(),
305 }));
306 }
307
308 Ok((base_id, base_pdu))
309}
310
311#[derive(Clone, Copy)]
314struct TimelineHalf<'a> {
315 services: &'a Services,
316 filter: &'a RoomEventFilter,
317 shortroomid: ShortRoomId,
318 sender_user: &'a UserId,
319 encrypted: bool,
320 bypass_visibility: bool,
321}
322
323async fn collect_timeline_half<'a, S>(
324 half: TimelineHalf<'a>,
325 pdus: S,
326 take: usize,
327) -> Vec<PdusIterItem>
328where
329 S: Stream<Item = Result<PdusIterItem>> + Send + 'a,
330{
331 let TimelineHalf {
332 services,
333 filter,
334 shortroomid,
335 sender_user,
336 encrypted,
337 bypass_visibility,
338 } = half;
339
340 pdus.ignore_err()
341 .ready_filter_map(|item| event_filter(item, filter))
342 .wide_filter_map(|item| related_by_filter(services, shortroomid, filter, item))
343 .wide_filter_map(|item| event_filters(services, sender_user, item, bypass_visibility))
344 .take(take)
345 .wide_then(|item| add_membership_unsigned(services, item, sender_user, encrypted))
346 .wide_then(async |(count, pdu)| {
347 let pdu = services
348 .pdu_metadata
349 .bundle_aggregations(sender_user, pdu)
350 .await;
351
352 (count, pdu)
353 })
354 .collect()
355 .await
356}
357
358async fn load_state_ids(
359 services: &Services,
360 room_id: &RoomId,
361 state_at: &EventId,
362) -> Result<Vec<(ShortStateKey, OwnedEventId)>> {
363 services
364 .state
365 .pdu_shortstatehash(state_at)
366 .or_else(|_| services.state.get_room_shortstatehash(room_id))
367 .map_ok(|shortstatehash| {
368 services
369 .state_accessor
370 .state_full_ids(shortstatehash)
371 .map(Ok)
372 })
373 .map_err(|e| err!(Database("State not found: {e}")))
374 .try_flatten_stream()
375 .try_collect()
376 .boxed()
377 .await
378}
379
380async fn build_state_response(
381 services: &Services,
382 state_ids: Vec<(ShortStateKey, OwnedEventId)>,
383 lazy_loading_witnessed: Witness,
384 filter: &RoomEventFilter,
385 sender_user: &UserId,
386 encrypted: bool,
387) -> Vec<Raw<AnyStateEvent>> {
388 let shortstatekeys = state_ids.iter().map(at!(0)).stream();
389 let shorteventids = state_ids.iter().map(ref_at!(1)).stream();
390
391 services
392 .short
393 .multi_get_statekey_from_short(shortstatekeys)
394 .zip(shorteventids)
395 .ready_filter_map(|item| Some((item.0.ok()?, item.1)))
396 .ready_filter_map(|((event_type, state_key), event_id)| {
397 if filter.lazy_load_options.is_enabled()
398 && event_type == StateEventType::RoomMember
399 && state_key
400 .as_str()
401 .try_into()
402 .is_ok_and(|user_id: &UserId| !lazy_loading_witnessed.contains(user_id))
403 {
404 return None;
405 }
406
407 Some(event_id)
408 })
409 .broad_filter_map(|event_id: &OwnedEventId| {
410 services.timeline.get_pdu(event_id.as_ref()).ok()
411 })
412 .broad_then(|pdu| with_membership(services, pdu, sender_user, encrypted))
413 .map(Event::into_format)
414 .collect()
415 .await
416}