Skip to main content

tuwunel_service/rooms/timeline/
mod.rs

1mod append;
2mod backfill;
3mod build;
4mod create;
5mod pdus;
6mod purge;
7mod redact;
8
9use std::{fmt::Write, sync::Arc};
10
11use async_trait::async_trait;
12use futures::{
13	TryFutureExt, TryStreamExt,
14	future::{
15		Either::{Left, Right},
16		select_ok,
17	},
18	pin_mut,
19};
20use ruma::{
21	CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId, RoomId,
22	UserId, api::Direction, events::room::encrypted::Relation,
23};
24use serde::Deserialize;
25pub use tuwunel_core::matrix::pdu::{PduId, RawPduId};
26use tuwunel_core::{
27	Err, Result, at, err, implement,
28	matrix::{
29		ShortEventId,
30		pdu::{PduCount, PduEvent},
31	},
32	utils::{
33		MutexMap, MutexMapGuard,
34		result::{LogErr, NotFound},
35		stream::TryReadyExt,
36	},
37	warn,
38};
39use tuwunel_database::{Database, Deserialized, Json, Map};
40
41pub use self::pdus::{PdusIterItem, bias_count};
42use crate::rooms::short::{ShortRoomId, ShortStateHash};
43
44pub struct Service {
45	services: Arc<crate::services::OnceServices>,
46	db: Data,
47	/// Serializes timeline insertion as the leaf per-room operation.
48	///
49	/// Acquire it after any federation or state mutex held for the same room.
50	/// Never acquire either outer mutex while holding this guard.
51	pub mutex_insert: RoomMutexMap,
52}
53
54struct Data {
55	eventid_outlierpdu: Arc<Map>,
56	eventid_pduid: Arc<Map>,
57	pduid_pdu: Arc<Map>,
58	roomid_tscount_pducount: Arc<Map>,
59	db: Arc<Database>,
60}
61
62// Update Relationships
63#[derive(Deserialize)]
64struct ExtractRelatesTo {
65	#[serde(rename = "m.relates_to")]
66	relates_to: Relation,
67}
68
69#[derive(Clone, Debug, Deserialize)]
70struct ExtractEventId {
71	event_id: OwnedEventId,
72}
73#[derive(Clone, Debug, Deserialize)]
74struct ExtractRelatesToEventId {
75	#[serde(rename = "m.relates_to")]
76	relates_to: ExtractEventId,
77}
78
79#[derive(Deserialize)]
80struct ExtractBody {
81	body: Option<String>,
82}
83
84type RoomMutexMap = MutexMap<OwnedRoomId, ()>;
85pub type RoomMutexGuard = MutexMapGuard<OwnedRoomId, ()>;
86
87#[async_trait]
88impl crate::Service for Service {
89	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
90		Ok(Arc::new(Self {
91			services: args.services.clone(),
92			db: Data {
93				eventid_outlierpdu: args.db["eventid_outlierpdu"].clone(),
94				eventid_pduid: args.db["eventid_pduid"].clone(),
95				pduid_pdu: args.db["pduid_pdu"].clone(),
96				roomid_tscount_pducount: args.db["roomid_tscount_pducount"].clone(),
97				db: args.db.clone(),
98			},
99			mutex_insert: RoomMutexMap::new(),
100		}))
101	}
102
103	async fn memory_usage(&self, out: &mut (dyn Write + Send)) -> Result {
104		let mutex_insert = self.mutex_insert.len();
105		writeln!(out, "- insert_mutex: {mutex_insert}")?;
106
107		Ok(())
108	}
109
110	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
111}
112
113/// Removes a pdu and creates a new one with the same id.
114#[implement(Service)]
115#[tracing::instrument(skip(self), level = "debug")]
116pub async fn replace_pdu(&self, pdu_id: &RawPduId, pdu_json: &CanonicalJsonObject) -> Result {
117	if self.db.pduid_pdu.get(pdu_id).await.is_not_found() {
118		return Err!(Request(NotFound("PDU does not exist.")));
119	}
120
121	self.db.pduid_pdu.raw_put(pdu_id, Json(pdu_json));
122
123	Ok(())
124}
125
126#[implement(Service)]
127#[tracing::instrument(skip(self, pdu), level = "debug")]
128pub fn add_pdu_outlier(&self, event_id: &EventId, pdu: &CanonicalJsonObject) {
129	self.db
130		.eventid_outlierpdu
131		.raw_put(event_id, Json(pdu));
132}
133
134#[implement(Service)]
135#[tracing::instrument(skip(self), level = "debug")]
136pub async fn first_pdu_in_room(&self, room_id: &RoomId) -> Result<PduEvent> {
137	self.first_item_in_room(room_id).await.map(at!(1))
138}
139
140#[implement(Service)]
141#[tracing::instrument(skip(self), level = "debug")]
142#[inline]
143pub async fn latest_pdu_in_room(&self, room_id: &RoomId) -> Result<PduEvent> {
144	self.latest_item_in_room(None, room_id).await
145}
146
147#[implement(Service)]
148#[tracing::instrument(skip(self), level = "debug")]
149pub async fn first_item_in_room(&self, room_id: &RoomId) -> Result<(PduCount, PduEvent)> {
150	let pdus = self.pdus(None, room_id, None);
151
152	pin_mut!(pdus);
153	pdus.try_next()
154		.await?
155		.ok_or_else(|| err!(Request(NotFound("No PDU found in room"))))
156}
157
158#[implement(Service)]
159#[tracing::instrument(skip(self), level = "debug")]
160pub async fn latest_item_in_room(
161	&self,
162	sender_user: Option<&UserId>,
163	room_id: &RoomId,
164) -> Result<PduEvent> {
165	let pdus_rev = self.pdus_rev(sender_user, room_id, None);
166
167	pin_mut!(pdus_rev);
168	pdus_rev
169		.try_next()
170		.await?
171		.map(at!(1))
172		.ok_or_else(|| err!(Request(NotFound("No PDU's found in room"))))
173}
174
175/// Returns the shortstatehash of the room at the event directly preceding the
176/// exclusive `before` param. `before` does not have to be a valid count
177/// or in the room.
178#[implement(Service)]
179#[tracing::instrument(skip(self), level = "debug")]
180pub async fn prev_shortstatehash(
181	&self,
182	room_id: &RoomId,
183	before: PduCount,
184) -> Result<ShortStateHash> {
185	let shortroomid: ShortRoomId = self
186		.services
187		.short
188		.get_shortroomid(room_id)
189		.await
190		.map_err(|e| err!(Request(NotFound("Room {room_id:?} not found: {e:?}"))))?;
191
192	let before = PduId { shortroomid, count: before };
193
194	let prev = PduId {
195		shortroomid,
196		count: self.prev_timeline_count(&before).await?,
197	};
198
199	let shorteventid = self.get_shorteventid_from_pdu_id(&prev).await?;
200
201	self.services
202		.state
203		.get_shortstatehash(shorteventid)
204		.await
205}
206
207/// Returns the shortstatehash of the room at the event directly following the
208/// exclusive `after` param. `after` does not have to be a valid count or
209/// in the room.
210#[implement(Service)]
211#[tracing::instrument(skip(self), level = "debug")]
212pub async fn next_shortstatehash(
213	&self,
214	room_id: &RoomId,
215	after: PduCount,
216) -> Result<ShortStateHash> {
217	let shortroomid: ShortRoomId = self
218		.services
219		.short
220		.get_shortroomid(room_id)
221		.await
222		.map_err(|e| err!(Request(NotFound("Room {room_id:?} not found: {e:?}"))))?;
223
224	let after = PduId { shortroomid, count: after };
225
226	let next = PduId {
227		shortroomid,
228		count: self.next_timeline_count(&after).await?,
229	};
230
231	let shorteventid = self.get_shorteventid_from_pdu_id(&next).await?;
232
233	self.services
234		.state
235		.get_shortstatehash(shorteventid)
236		.await
237}
238
239/// Returns the shortstatehash of the room at the event
240#[implement(Service)]
241#[tracing::instrument(skip(self), level = "debug")]
242pub async fn get_shortstatehash(
243	&self,
244	room_id: &RoomId,
245	count: PduCount,
246) -> Result<ShortStateHash> {
247	let shortroomid: ShortRoomId = self
248		.services
249		.short
250		.get_shortroomid(room_id)
251		.await
252		.map_err(|e| err!(Request(NotFound("Room {room_id:?} not found: {e:?}"))))?;
253
254	let pdu_id = PduId { shortroomid, count };
255
256	let shorteventid = self.get_shorteventid_from_pdu_id(&pdu_id).await?;
257
258	self.services
259		.state
260		.get_shortstatehash(shorteventid)
261		.await
262}
263
264/// Returns the shorteventid in the room preceding the exclusive `before` param.
265/// `before` does not have to be a valid shorteventid or in the room.
266#[implement(Service)]
267#[tracing::instrument(skip(self), level = "debug")]
268pub async fn prev_timeline_count(&self, before: &PduId) -> Result<PduCount> {
269	let before = Self::pdu_count_to_id(before.shortroomid, before.count, Direction::Backward);
270
271	let pdu_ids = self
272		.db
273		.pduid_pdu
274		.rev_keys_raw_from(&before)
275		.ready_try_take_while(|pdu_id: &RawPduId| Ok(pdu_id.is_room_eq(before)))
276		.ready_and_then(|pdu_id: RawPduId| Ok(pdu_id.pdu_count()));
277
278	pin_mut!(pdu_ids);
279	pdu_ids
280		.try_next()
281		.await
282		.log_err()?
283		.ok_or_else(|| err!(Request(NotFound("No earlier PDU's found in room"))))
284}
285
286/// Returns the next shorteventid in the room after the exclusive `after` param.
287/// `after` does not have to be a valid shorteventid or in the room.
288#[implement(Service)]
289#[tracing::instrument(skip(self), level = "debug")]
290pub async fn next_timeline_count(&self, after: &PduId) -> Result<PduCount> {
291	let after = Self::pdu_count_to_id(after.shortroomid, after.count, Direction::Forward);
292
293	let pdu_ids = self
294		.db
295		.pduid_pdu
296		.keys_raw_from(&after)
297		.ready_try_take_while(|pdu_id: &RawPduId| Ok(pdu_id.is_room_eq(after)))
298		.ready_and_then(|pdu_id: RawPduId| Ok(pdu_id.pdu_count()));
299
300	pin_mut!(pdu_ids);
301	pdu_ids
302		.try_next()
303		.await
304		.log_err()?
305		.ok_or(err!(Request(NotFound("No more PDU's found in room"))))
306}
307
308#[implement(Service)]
309#[tracing::instrument(skip(self), level = "debug")]
310pub async fn last_timeline_count(
311	&self,
312	sender_user: Option<&UserId>,
313	room_id: &RoomId,
314	upper_bound: Option<PduCount>,
315) -> Result<PduCount> {
316	let upper_bound = upper_bound.unwrap_or_else(PduCount::max);
317	let pdus_rev = self.pdus_rev(sender_user, room_id, None);
318
319	pin_mut!(pdus_rev);
320	let last_count = pdus_rev
321		.ready_try_skip_while(|&(pducount, _)| Ok(pducount > upper_bound))
322		.try_next()
323		.await?
324		.map(at!(0))
325		.filter(|&count| matches!(count, PduCount::Normal(_)))
326		.unwrap_or_else(PduCount::max);
327
328	Ok(last_count)
329}
330
331#[implement(Service)]
332pub async fn get_event_id_near_ts(
333	&self,
334	room_id: &RoomId,
335	ts: MilliSecondsSinceUnixEpoch,
336	dir: Direction,
337) -> Result<(MilliSecondsSinceUnixEpoch, OwnedEventId)> {
338	self.get_pdu_id_near_ts(room_id, ts, dir)
339		.and_then(async |(ts, pdu_id)| {
340			self.get_event_id_from_pdu_id(&pdu_id)
341				.map_ok(|event_id| (ts, event_id))
342				.await
343		})
344		.await
345}
346
347#[implement(Service)]
348pub async fn get_pdu_id_near_ts(
349	&self,
350	room_id: &RoomId,
351	ts: MilliSecondsSinceUnixEpoch,
352	dir: Direction,
353) -> Result<(MilliSecondsSinceUnixEpoch, PduId)> {
354	let pdu_ids = self.pdu_ids_near_ts(room_id, ts, dir);
355
356	pin_mut!(pdu_ids);
357	pdu_ids
358		.try_next()
359		.await?
360		.ok_or_else(|| err!(Request(NotFound("No event found near this timestamp."))))
361}
362
363#[implement(Service)]
364pub async fn get_pdu_near_ts(
365	&self,
366	_user_id: Option<&UserId>,
367	room_id: &RoomId,
368	ts: MilliSecondsSinceUnixEpoch,
369	dir: Direction,
370) -> Result<PdusIterItem> {
371	let pdus = self
372		.pdu_ids_near_ts(room_id, ts, dir)
373		.map_ok(|(ts, pdu_id)| (ts, pdu_id.into()))
374		.and_then(async |(_, pdu_id): (_, RawPduId)| {
375			self.get_pdu_from_id(&pdu_id)
376				.map_ok(|pdu| (pdu_id.pdu_count(), pdu))
377				.await
378		});
379
380	pin_mut!(pdus);
381	pdus.try_next()
382		.await?
383		.ok_or_else(|| err!(Request(NotFound("No event found near this timestamp."))))
384}
385
386#[implement(Service)]
387async fn count_to_id(
388	&self,
389	room_id: &RoomId,
390	count: PduCount,
391	dir: Direction,
392) -> Result<RawPduId> {
393	let shortroomid: ShortRoomId = self
394		.services
395		.short
396		.get_shortroomid(room_id)
397		.await
398		.map_err(|e| err!(Request(NotFound("Room {room_id:?} not found: {e:?}"))))?;
399
400	Ok(Self::pdu_count_to_id(shortroomid, count, dir))
401}
402
403#[implement(Service)]
404fn pdu_count_to_id(shortroomid: ShortRoomId, count: PduCount, dir: Direction) -> RawPduId {
405	// +1 so we don't send the base event
406	let pdu_id = PduId {
407		shortroomid,
408		count: count.saturating_inc(dir),
409	};
410
411	pdu_id.into()
412}
413
414/// Returns the pdu from shorteventid
415/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
416#[implement(Service)]
417pub async fn get_pdu_from_shorteventid(&self, shorteventid: ShortEventId) -> Result<PduEvent> {
418	let event_id: OwnedEventId = self
419		.services
420		.short
421		.get_eventid_from_short(shorteventid)
422		.await?;
423
424	self.get_pdu(&event_id).await
425}
426
427/// Returns the pdu.
428/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
429#[implement(Service)]
430pub async fn get_pdu(&self, event_id: &EventId) -> Result<PduEvent> { self.get(event_id).await }
431
432/// Returns the pdu.
433/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
434#[implement(Service)]
435pub async fn get_outlier_pdu(&self, event_id: &EventId) -> Result<PduEvent> {
436	self.get_outlier(event_id).await
437}
438
439/// Returns the pdu.
440/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
441#[implement(Service)]
442pub async fn get_non_outlier_pdu(&self, event_id: &EventId) -> Result<PduEvent> {
443	self.get_non_outlier(event_id).await
444}
445
446/// Returns the pdu.
447/// This does __NOT__ check the outliers `Tree`.
448#[implement(Service)]
449pub async fn get_pdu_from_id(&self, pdu_id: &RawPduId) -> Result<PduEvent> {
450	self.get_from_id(pdu_id).await
451}
452
453/// Returns the json of a pdu.
454/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
455#[implement(Service)]
456pub async fn get_pdu_json(&self, event_id: &EventId) -> Result<CanonicalJsonObject> {
457	self.get(event_id).await
458}
459
460/// Returns the json of a pdu.
461/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
462#[implement(Service)]
463pub async fn get_outlier_pdu_json(&self, event_id: &EventId) -> Result<CanonicalJsonObject> {
464	self.get_outlier(event_id).await
465}
466
467/// Returns the json of a pdu.
468/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
469#[implement(Service)]
470pub async fn get_non_outlier_pdu_json(&self, event_id: &EventId) -> Result<CanonicalJsonObject> {
471	self.get_non_outlier(event_id).await
472}
473
474/// Returns the pdu as a `BTreeMap<String, CanonicalJsonValue>`.
475/// This does __NOT__ check the outliers `Tree`.
476#[implement(Service)]
477pub async fn get_pdu_json_from_id(&self, pdu_id: &RawPduId) -> Result<CanonicalJsonObject> {
478	self.get_from_id(pdu_id).await
479}
480
481/// Returns the pdu into T.
482/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
483#[implement(Service)]
484#[inline]
485pub async fn get<T>(&self, event_id: &EventId) -> Result<T>
486where
487	T: for<'de> Deserialize<'de>,
488{
489	let accepted = self.get_non_outlier(event_id);
490	let outlier = self.get_outlier(event_id);
491
492	pin_mut!(accepted, outlier);
493	select_ok([Left(accepted), Right(outlier)])
494		.await
495		.map(at!(0))
496}
497
498/// Returns the pdu into T.
499/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
500#[implement(Service)]
501#[inline]
502pub async fn get_outlier<T>(&self, event_id: &EventId) -> Result<T>
503where
504	T: for<'de> Deserialize<'de>,
505{
506	self.db
507		.eventid_outlierpdu
508		.get(event_id)
509		.await
510		.deserialized()
511}
512
513/// Returns the pdu into T.
514/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
515#[implement(Service)]
516#[inline]
517pub async fn get_non_outlier<T>(&self, event_id: &EventId) -> Result<T>
518where
519	T: for<'de> Deserialize<'de>,
520{
521	let pdu_id = self.get_pdu_id(event_id).await?;
522
523	self.get_from_id(&pdu_id).await
524}
525
526/// Returns the pdu into T.
527/// This does __NOT__ check the outliers `Tree`.
528#[implement(Service)]
529#[inline]
530pub async fn get_from_id<T>(&self, pdu_id: &RawPduId) -> Result<T>
531where
532	T: for<'de> Deserialize<'de>,
533{
534	self.db.pduid_pdu.get(pdu_id).await.deserialized()
535}
536
537/// Checks if pdu exists
538/// Checks the `eventid_outlierpdu` Tree if not found in the timeline.
539#[implement(Service)]
540pub async fn pdu_exists<'a>(&'a self, event_id: &'a EventId) -> bool {
541	let non_outlier = self.non_outlier_pdu_exists(event_id);
542	let outlier = self.outlier_pdu_exists(event_id);
543
544	pin_mut!(non_outlier, outlier);
545	select_ok([Left(non_outlier), Right(outlier)])
546		.await
547		.map(at!(0))
548		.is_ok()
549}
550
551/// Resolves once `event_id` lands in the timeline (its `eventid_pduid` row is
552/// written), waking a task waiting for the event to arrive via concurrent
553/// ingest. Registration is eager: the watcher is in place when this returns,
554/// before the future is awaited.
555#[implement(Service)]
556pub fn watch_event<'a>(&'a self, event_id: &EventId) -> impl Future<Output = ()> + Send + 'a {
557	self.db
558		.eventid_pduid
559		.watch_raw_prefix_once(event_id)
560}
561
562/// Like get_non_outlier_pdu(), but without the expense of fetching and
563/// parsing the PduEvent
564#[implement(Service)]
565pub async fn non_outlier_pdu_exists(&self, event_id: &EventId) -> Result {
566	let pduid = self.get_pdu_id(event_id).await?;
567
568	self.db.pduid_pdu.exists(&pduid).await
569}
570
571/// Like get_non_outlier_pdu(), but without the expense of fetching and
572/// parsing the PduEvent
573#[implement(Service)]
574#[inline]
575pub async fn outlier_pdu_exists(&self, event_id: &EventId) -> Result {
576	self.db.eventid_outlierpdu.exists(event_id).await
577}
578
579/// Returns the `count` of this pdu's id.
580#[implement(Service)]
581pub async fn get_pdu_count(&self, event_id: &EventId) -> Result<PduCount> {
582	self.get_pdu_id(event_id)
583		.await
584		.map(RawPduId::pdu_count)
585}
586
587/// Returns the `shorteventid` from the `pdu_id`
588#[implement(Service)]
589pub async fn get_shorteventid_from_pdu_id(&self, pdu_id: &PduId) -> Result<ShortEventId> {
590	let event_id = self.get_event_id_from_pdu_id(pdu_id).await?;
591
592	self.services
593		.short
594		.get_shorteventid(&event_id)
595		.await
596}
597
598/// Returns the `event_id` from the `pdu_id`
599#[implement(Service)]
600pub async fn get_event_id_from_pdu_id(&self, pdu_id: &PduId) -> Result<OwnedEventId> {
601	let pdu_id: RawPduId = (*pdu_id).into();
602
603	self.get_pdu_from_id(&pdu_id)
604		.map_ok(|pdu| pdu.event_id)
605		.await
606}
607
608/// Returns the `pdu_id` from the `shorteventid`
609#[implement(Service)]
610pub async fn get_pdu_id_from_shorteventid(&self, shorteventid: ShortEventId) -> Result<RawPduId> {
611	let event_id: OwnedEventId = self
612		.services
613		.short
614		.get_eventid_from_short(shorteventid)
615		.await?;
616
617	self.get_pdu_id(&event_id).await
618}
619
620/// Returns the pdu's id.
621#[implement(Service)]
622pub async fn get_pdu_id(&self, event_id: &EventId) -> Result<RawPduId> {
623	self.db
624		.eventid_pduid
625		.get(event_id)
626		.await
627		.map(|handle| RawPduId::from(&*handle))
628}