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