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, Result, at, debug_warn, err, ref_at,
14 utils::{
15 BoolExt, IterStream,
16 future::TryExtExt,
17 math::usize_from_ruma_bounded,
18 stream::{BroadbandExt, ReadyExt, TryIgnore, WidebandExt},
19 },
20};
21use tuwunel_matrix::{
22 Event,
23 pdu::{PduEvent, RawPduId},
24};
25use tuwunel_service::{
26 Services,
27 rooms::{
28 lazy_loading,
29 lazy_loading::{Options, Witness},
30 short::{ShortRoomId, ShortStateHash, ShortStateKey},
31 timeline::PdusIterItem,
32 },
33};
34
35use crate::{
36 Ruma, 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
43const LIMIT_MAX: usize = 100;
44const LIMIT_DEFAULT: usize = 10;
45
46pub(crate) async fn get_context_route(
53 State(services): State<crate::State>,
54 body: Ruma<get_context::v3::Request>,
55) -> Result<get_context::v3::Response> {
56 event_context(&services, ContextArgs {
57 room_id: &body.room_id,
58 event_id: &body.event_id,
59 sender_user: body.sender_user(),
60 sender_device: body.sender_device.as_deref(),
61 filter: &body.filter,
62 limit: Some(body.limit),
63 bypass_visibility: false,
64 })
65 .await
66}
67
68pub struct ContextArgs<'a> {
71 pub room_id: &'a RoomId,
73
74 pub event_id: &'a EventId,
76
77 pub sender_user: &'a UserId,
79
80 pub sender_device: Option<&'a DeviceId>,
82
83 pub filter: &'a RoomEventFilter,
85
86 pub limit: Option<UInt>,
88
89 pub bypass_visibility: bool,
92}
93
94pub async fn event_context(
100 services: &Services,
101 args: ContextArgs<'_>,
102) -> Result<get_context::v3::Response> {
103 let ContextArgs {
104 room_id,
105 event_id,
106 sender_user,
107 sender_device,
108 filter,
109 limit,
110 bypass_visibility,
111 } = args;
112
113 if !services.metadata.exists(room_id).await {
114 return Err!(Request(Forbidden("Room does not exist to this server")));
115 }
116
117 let limit = limit
118 .map_or(LIMIT_DEFAULT, |limit| usize_from_ruma_bounded(limit, LIMIT_DEFAULT, LIMIT_MAX));
119
120 let (base_id, base_pdu) =
121 resolve_base_event(services, room_id, event_id, sender_user, bypass_visibility).await?;
122
123 let base_count = base_id.pdu_count();
124
125 let encrypted = services
126 .state_accessor
127 .is_encrypted_room(room_id)
128 .await;
129
130 let shortroomid = services.short.get_shortroomid(room_id).await?;
131
132 let base_event = async {
133 let item = if bypass_visibility {
134 (base_count, base_pdu)
135 } else {
136 ignored_filter(services, (base_count, base_pdu), sender_user).await?
137 };
138
139 Some(add_membership_unsigned(services, item, sender_user, encrypted).await)
140 };
141
142 let half = TimelineHalf {
143 services,
144 filter,
145 shortroomid,
146 sender_user,
147 encrypted,
148 bypass_visibility,
149 };
150
151 let events_before = collect_timeline_half(
152 half,
153 services
154 .timeline
155 .pdus_rev(Some(sender_user), room_id, Some(base_count)),
156 limit / 2,
157 );
158
159 let events_after = collect_timeline_half(
160 half,
161 services
162 .timeline
163 .pdus(Some(sender_user), room_id, Some(base_count)),
164 limit.div_ceil(2),
165 );
166
167 let (base_event, events_before, events_after): (_, Vec<_>, Vec<_>) =
168 join3(base_event, events_before, events_after)
169 .boxed() .await;
171
172 let lazy_loading_context = lazy_loading::Context {
173 user_id: sender_user,
174 device_id: sender_device,
175 room_id,
176 token: Some(base_count.into_unsigned()),
177 options: Some(&filter.lazy_load_options),
178 mode: lazy_loading::Mode::Update,
179 };
180
181 let lazy_loading_witnessed = filter
182 .lazy_load_options
183 .is_enabled()
184 .then_async(|| {
185 let witnessed = base_event
186 .iter()
187 .chain(events_before.iter())
188 .chain(events_after.iter());
189
190 lazy_loading_witness(services, &lazy_loading_context, witnessed)
191 });
192
193 let state_at = events_after
194 .last()
195 .map(ref_at!(1))
196 .map_or(event_id, Event::event_id);
197
198 let state_ids = load_state_ids(services, room_id, state_at, sender_user, bypass_visibility);
199
200 let (lazy_loading_witnessed, state_ids) = join(lazy_loading_witnessed, state_ids).await;
201
202 let state = build_state_response(
203 services,
204 state_ids?,
205 lazy_loading_witnessed.unwrap_or_default(),
206 filter,
207 sender_user,
208 encrypted,
209 )
210 .await;
211
212 let event = OptionFuture::from(base_event.map(at!(1)).map(|pdu| {
213 services
214 .pdu_metadata
215 .bundle_aggregations(sender_user, pdu)
216 }))
217 .await
218 .map(Event::into_format);
219
220 Ok(get_context::v3::Response {
221 event,
222
223 start: events_before
224 .last()
225 .map(at!(0))
226 .or(Some(base_count))
227 .as_ref()
228 .map(ToString::to_string),
229
230 end: events_after
233 .last()
234 .map(at!(0))
235 .or_else(|| Some(base_count.saturating_add(1)))
236 .as_ref()
237 .map(ToString::to_string),
238
239 events_before: events_before
240 .into_iter()
241 .map(at!(1))
242 .map(Event::into_format)
243 .collect(),
244
245 events_after: events_after
246 .into_iter()
247 .map(at!(1))
248 .map(Event::into_format)
249 .collect(),
250
251 state,
252 })
253}
254
255async fn resolve_base_event(
256 services: &Services,
257 room_id: &RoomId,
258 event_id: &EventId,
259 sender_user: &UserId,
260 bypass_visibility: bool,
261) -> Result<(RawPduId, PduEvent)> {
262 let lookup = || {
263 let base_id = services
264 .timeline
265 .get_pdu_id(event_id)
266 .map_err(|_| err!(Request(NotFound("Event not found."))));
267
268 let base_pdu = services
269 .timeline
270 .get_pdu(event_id)
271 .map_err(|_| err!(Request(NotFound("Base event not found."))));
272
273 try_join(base_id, base_pdu)
274 };
275
276 let resolve_remote = services
277 .config
278 .federation
279 .history
280 .fetch_unreceived_contexts
281 && services.config.federation.enable;
282
283 let (base_id, base_pdu) = match lookup().await {
284 | Ok(found) => found,
285 | Err(e) if !resolve_remote => return Err(e),
286 | Err(_) => {
287 services
288 .timeline
289 .fetch_remote_event(room_id, event_id)
290 .await
291 .ok();
292
293 lookup().await?
294 },
295 };
296
297 if base_pdu.room_id != *room_id || base_pdu.event_id != *event_id {
298 return Err!(Request(NotFound("Base event not found.")));
299 }
300
301 if !bypass_visibility
302 && !services
303 .state_accessor
304 .user_can_see_event(sender_user, &base_pdu)
305 .await
306 {
307 debug_warn!(
308 req_evt = ?event_id, ?base_id, ?room_id, %sender_user,
309 "Event requested but the requester is not allowed to see it."
310 );
311
312 return Err!(Request(NotFound("Event not found.")));
313 }
314
315 if !bypass_visibility && is_ignored_pdu(services, &base_pdu, sender_user).await {
316 return Err!(HttpJson(NOT_FOUND, {
317 "errcode": "M_SENDER_IGNORED",
318 "error": "You have ignored the user that sent this event",
319 "sender": base_pdu.sender().as_str(),
320 }));
321 }
322
323 Ok((base_id, base_pdu.without_private_unless_sender(Some(sender_user))))
324}
325
326#[derive(Clone, Copy)]
329struct TimelineHalf<'a> {
330 services: &'a Services,
331 filter: &'a RoomEventFilter,
332 shortroomid: ShortRoomId,
333 sender_user: &'a UserId,
334 encrypted: bool,
335 bypass_visibility: bool,
336}
337
338async fn collect_timeline_half<'a, S>(
339 half: TimelineHalf<'a>,
340 pdus: S,
341 take: usize,
342) -> Vec<PdusIterItem>
343where
344 S: Stream<Item = Result<PdusIterItem>> + Send + 'a,
345{
346 let TimelineHalf {
347 services,
348 filter,
349 shortroomid,
350 sender_user,
351 encrypted,
352 bypass_visibility,
353 } = half;
354
355 pdus.ignore_err()
356 .ready_filter_map(|item| event_filter(item, filter))
357 .wide_filter_map(|item| related_by_filter(services, shortroomid, filter, item))
358 .wide_filter_map(|item| event_filters(services, sender_user, item, bypass_visibility))
359 .take(take)
360 .wide_then(|item| add_membership_unsigned(services, item, sender_user, encrypted))
361 .wide_then(async |(count, pdu)| {
362 let pdu = services
363 .pdu_metadata
364 .bundle_aggregations(sender_user, pdu)
365 .await;
366
367 (count, pdu)
368 })
369 .collect()
370 .await
371}
372
373async fn load_state_ids(
379 services: &Services,
380 room_id: &RoomId,
381 state_at: &EventId,
382 sender_user: &UserId,
383 bypass_visibility: bool,
384) -> Result<Vec<(ShortStateKey, OwnedEventId)>> {
385 let state_full_ids = |shortstatehash| {
386 services
387 .state_accessor
388 .state_full_ids(shortstatehash)
389 };
390
391 services
392 .state
393 .pdu_shortstatehash(state_at)
394 .map_ok(Some)
395 .or_else(|_| fallback_shortstatehash(services, room_id, sender_user, bypass_visibility))
396 .map_ok(|shortstatehash| {
397 shortstatehash
398 .into_iter()
399 .stream()
400 .flat_map(state_full_ids)
401 .map(Ok)
402 })
403 .map_err(|e| err!(Database("State not found: {e}")))
404 .try_flatten_stream()
405 .try_collect()
406 .boxed() .await
408}
409
410async fn fallback_shortstatehash(
416 services: &Services,
417 room_id: &RoomId,
418 sender_user: &UserId,
419 bypass_visibility: bool,
420) -> Result<Option<ShortStateHash>> {
421 let admitted = bypass_visibility
422 || services
423 .state_accessor
424 .user_can_see_state_events(sender_user, room_id)
425 .await;
426
427 match admitted
428 .then_async(|| {
429 services
430 .state_accessor
431 .user_visible_shortstatehash(sender_user, room_id, None)
432 })
433 .await
434 .transpose()?
435 .flatten()
436 {
437 | None if bypass_visibility =>
438 services
439 .state
440 .get_room_shortstatehash(room_id)
441 .map_ok(Some)
442 .await,
443 | shortstatehash => Ok(shortstatehash),
444 }
445}
446
447async fn build_state_response(
448 services: &Services,
449 state_ids: Vec<(ShortStateKey, OwnedEventId)>,
450 lazy_loading_witnessed: Witness,
451 filter: &RoomEventFilter,
452 sender_user: &UserId,
453 encrypted: bool,
454) -> Vec<Raw<AnyStateEvent>> {
455 let shortstatekeys = state_ids.iter().map(at!(0)).stream();
456 let shorteventids = state_ids.iter().map(ref_at!(1)).stream();
457
458 services
459 .short
460 .multi_get_statekey_from_short(shortstatekeys)
461 .zip(shorteventids)
462 .ready_filter_map(|item| Some((item.0.ok()?, item.1)))
463 .ready_filter_map(|((event_type, state_key), event_id)| {
464 if filter.lazy_load_options.is_enabled()
465 && event_type == StateEventType::RoomMember
466 && state_key
467 .as_str()
468 .try_into()
469 .is_ok_and(|user_id: &UserId| !lazy_loading_witnessed.contains(user_id))
470 {
471 return None;
472 }
473
474 Some(event_id)
475 })
476 .broad_filter_map(|event_id: &OwnedEventId| {
477 services.timeline.get_pdu(event_id.as_ref()).ok()
478 })
479 .map(|pdu| pdu.without_private_unless_sender(Some(sender_user)))
480 .broad_then(|pdu| with_membership(services, pdu, sender_user, encrypted))
481 .map(Event::into_format)
482 .collect()
483 .await
484}