Skip to main content

tuwunel_service/rooms/short/
mod.rs

1use std::{borrow::Borrow, sync::Arc};
2
3use futures::{FutureExt, Stream, StreamExt, pin_mut};
4use ruma::{EventId, OwnedEventId, OwnedRoomId, RoomId, events::StateEventType};
5use serde::Deserialize;
6pub use tuwunel_core::matrix::{ShortEventId, ShortId, ShortRoomId, ShortStateKey};
7use tuwunel_core::{
8	Err, Result, err, implement,
9	matrix::StateKey,
10	utils,
11	utils::{
12		IterStream, MutexMap,
13		hash::sha256::Digest,
14		stream::{ReadyExt, WidebandExt},
15	},
16};
17use tuwunel_database::{Deserialized, Get, Map, Qry, Txn};
18
19pub struct Service {
20	db: Data,
21	creating: Creating,
22	services: Arc<crate::services::OnceServices>,
23}
24
25struct Data {
26	eventid_shorteventid: Arc<Map>,
27	shorteventid_eventid: Arc<Map>,
28	statekey_shortstatekey: Arc<Map>,
29	shortstatekey_statekey: Arc<Map>,
30	roomid_shortroomid: Arc<Map>,
31	statehash_shortstatehash: Arc<Map>,
32}
33
34/// Serializes concurrent allocations so one identity maps to one short id.
35///
36/// A guard is held across both the re-read that detects a competing
37/// allocation and the writes that publish this one. Entries exist only while
38/// a caller holds or awaits one, so an uncontended allocation leaves nothing
39/// behind.
40#[derive(Default)]
41struct Creating {
42	shorteventid: MutexMap<OwnedEventId, ()>,
43	shortstatekey: MutexMap<(StateEventType, StateKey), ()>,
44	shortstatehash: MutexMap<Digest, ()>,
45	shortroomid: MutexMap<OwnedRoomId, ()>,
46}
47
48pub type ShortStateHash = ShortId;
49
50impl crate::Service for Service {
51	fn build(args: &crate::Args<'_>) -> Result<Arc<Self>> {
52		Ok(Arc::new(Self {
53			db: Data {
54				eventid_shorteventid: args.db["eventid_shorteventid"].clone(),
55				shorteventid_eventid: args.db["shorteventid_eventid"].clone(),
56				statekey_shortstatekey: args.db["statekey_shortstatekey"].clone(),
57				shortstatekey_statekey: args.db["shortstatekey_statekey"].clone(),
58				roomid_shortroomid: args.db["roomid_shortroomid"].clone(),
59				statehash_shortstatehash: args.db["statehash_shortstatehash"].clone(),
60			},
61			creating: Creating::default(),
62			services: args.services.clone(),
63		}))
64	}
65
66	fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
67}
68
69#[implement(Service)]
70pub async fn get_or_create_shorteventid(&self, event_id: &EventId) -> ShortEventId {
71	if let Ok(shorteventid) = self.get_shorteventid(event_id).await {
72		return shorteventid;
73	}
74
75	self.create_shorteventid(event_id).await
76}
77
78/// Resolves each event id to its short id, allocating any that are absent.
79///
80/// Allocation runs ahead of consumer demand, so a caller that stops early
81/// still allocates for the events already buffered. Today's callers drain the
82/// stream in full.
83#[implement(Service)]
84pub fn multi_get_or_create_shorteventid<'a, I>(
85	&'a self,
86	event_ids: I,
87) -> impl Stream<Item = ShortEventId> + Send + '_
88where
89	I: Iterator<Item = &'a EventId> + Clone + Send + 'a,
90{
91	event_ids
92		.clone()
93		.stream()
94		.get(&self.db.eventid_shorteventid)
95		.zip(event_ids.into_iter().stream())
96		.wide_then(async |(result, event_id)| match result {
97			| Ok(ref short) => utils::u64_from_u8(short),
98			| Err(_) => self.create_shorteventid(event_id).await,
99		})
100}
101
102#[implement(Service)]
103async fn create_shorteventid(&self, event_id: &EventId) -> ShortEventId {
104	let _lock = self.creating.shorteventid.lock(event_id).await;
105
106	if let Ok(shorteventid) = self.get_shorteventid(event_id).await {
107		return shorteventid;
108	}
109
110	let short = self.services.globals.next_count();
111	let mut txn = self.services.db.txn();
112
113	txn.insert_raw(&self.db.shorteventid_eventid, (*short).to_be_bytes(), event_id);
114	txn.insert_raw(&self.db.eventid_shorteventid, event_id, (*short).to_be_bytes());
115	txn.execute();
116
117	*short
118}
119
120#[implement(Service)]
121pub async fn get_shorteventid(&self, event_id: &EventId) -> Result<ShortEventId> {
122	self.db
123		.eventid_shorteventid
124		.get(event_id)
125		.await
126		.deserialized()
127}
128
129#[implement(Service)]
130pub async fn get_or_create_shortstatekey(
131	&self,
132	event_type: &StateEventType,
133	state_key: &str,
134) -> ShortStateKey {
135	if let Ok(shortstatekey) = self
136		.get_shortstatekey(event_type, state_key)
137		.await
138	{
139		return shortstatekey;
140	}
141
142	self.create_shortstatekey(event_type, state_key)
143		.await
144}
145
146#[implement(Service)]
147async fn create_shortstatekey(
148	&self,
149	event_type: &StateEventType,
150	state_key: &str,
151) -> ShortStateKey {
152	let owned_key = (event_type.clone(), StateKey::from_str(state_key));
153	let _lock = self.creating.shortstatekey.lock(&owned_key).await;
154
155	if let Ok(shortstatekey) = self
156		.get_shortstatekey(event_type, state_key)
157		.await
158	{
159		return shortstatekey;
160	}
161
162	let key = (event_type, state_key);
163	let shortstatekey = self.services.globals.next_count();
164	let mut txn = self.services.db.txn();
165
166	txn.put(&self.db.shortstatekey_statekey, *shortstatekey, key);
167	txn.put(&self.db.statekey_shortstatekey, key, *shortstatekey);
168	txn.execute();
169
170	*shortstatekey
171}
172
173#[implement(Service)]
174pub async fn get_shortstatekey(
175	&self,
176	event_type: &StateEventType,
177	state_key: &str,
178) -> Result<ShortStateKey> {
179	let key = (event_type, state_key);
180	self.db
181		.statekey_shortstatekey
182		.qry(&key)
183		.await
184		.deserialized()
185}
186
187#[implement(Service)]
188pub async fn get_eventid_from_short<Id>(&self, shorteventid: ShortEventId) -> Result<Id>
189where
190	Id: for<'de> Deserialize<'de> + Send + Sized + ToOwned,
191	<Id as ToOwned>::Owned: Borrow<EventId>,
192{
193	const BUFSIZE: usize = size_of::<ShortEventId>();
194
195	self.db
196		.shorteventid_eventid
197		.aqry::<BUFSIZE, _>(&shorteventid)
198		.await
199		.deserialized()
200		.map_err(|e| err!(Database("Failed to find EventId from short {shorteventid:?}: {e:?}")))
201}
202
203#[implement(Service)]
204pub fn multi_get_eventid_from_short<'a, Id, S>(
205	&'a self,
206	shorteventid: S,
207) -> impl Stream<Item = Result<Id>> + Send + 'a
208where
209	S: Stream<Item = ShortEventId> + Send + 'a,
210	Id: for<'de> Deserialize<'de> + Send + Sized + ToOwned + 'a,
211	<Id as ToOwned>::Owned: Borrow<EventId>,
212{
213	shorteventid
214		.qry(&self.db.shorteventid_eventid)
215		.map(Deserialized::deserialized)
216}
217
218#[implement(Service)]
219pub async fn get_statekey_from_short(
220	&self,
221	shortstatekey: ShortStateKey,
222) -> Result<(StateEventType, StateKey)> {
223	const BUFSIZE: usize = size_of::<ShortStateKey>();
224
225	self.db
226		.shortstatekey_statekey
227		.aqry::<BUFSIZE, _>(&shortstatekey)
228		.await
229		.deserialized()
230		.map_err(|e| {
231			err!(Database(
232				"Failed to find (StateEventType, state_key) from short {shortstatekey:?}: {e:?}"
233			))
234		})
235}
236
237#[implement(Service)]
238pub fn multi_get_statekey_from_short<'a, S>(
239	&'a self,
240	shortstatekey: S,
241) -> impl Stream<Item = Result<(StateEventType, StateKey)>> + Send + 'a
242where
243	S: Stream<Item = ShortStateKey> + Send + 'a,
244{
245	shortstatekey
246		.qry(&self.db.shortstatekey_statekey)
247		.map(Deserialized::deserialized)
248}
249
250/// Returns (shortstatehash, already_existed)
251#[implement(Service)]
252pub async fn get_or_create_shortstatehash<F>(
253	&self,
254	state_hash: &Digest,
255	write_statediff: F,
256) -> Result<(ShortStateHash, bool)>
257where
258	F: FnOnce(&mut Txn, ShortStateHash) -> Result,
259{
260	if let Ok(shortstatehash) = self.get_shortstatehash(state_hash).await {
261		return Ok((shortstatehash, true));
262	}
263
264	self.create_shortstatehash(state_hash, write_statediff)
265		.await
266}
267
268#[implement(Service)]
269async fn create_shortstatehash<F>(
270	&self,
271	state_hash: &Digest,
272	write_statediff: F,
273) -> Result<(ShortStateHash, bool)>
274where
275	F: FnOnce(&mut Txn, ShortStateHash) -> Result,
276{
277	let _lock = self
278		.creating
279		.shortstatehash
280		.lock(state_hash)
281		.await;
282
283	if let Ok(shortstatehash) = self.get_shortstatehash(state_hash).await {
284		return Ok((shortstatehash, true));
285	}
286
287	let shortstatehash = self.services.globals.next_count();
288	let mut txn = self.services.db.txn();
289
290	txn.insert_raw(
291		&self.db.statehash_shortstatehash,
292		state_hash,
293		(*shortstatehash).to_be_bytes(),
294	);
295	write_statediff(&mut txn, *shortstatehash)?;
296	txn.execute();
297
298	Ok((*shortstatehash, false))
299}
300
301#[implement(Service)]
302pub async fn get_shortstatehash(&self, state_hash: &Digest) -> Result<ShortStateHash> {
303	self.db
304		.statehash_shortstatehash
305		.get(state_hash)
306		.await
307		.deserialized()
308}
309
310#[implement(Service)]
311pub async fn get_shortroomid(&self, room_id: &RoomId) -> Result<ShortRoomId> {
312	self.db
313		.roomid_shortroomid
314		.get(room_id)
315		.await
316		.deserialized()
317}
318
319#[implement(Service)]
320pub async fn get_roomid_from_short(&self, shortroomid_: ShortRoomId) -> Result<OwnedRoomId> {
321	let stream = self
322		.db
323		.roomid_shortroomid
324		.stream()
325		.ready_filter_map(Result::ok);
326
327	pin_mut!(stream);
328	stream
329		.ready_find(|&(_, shortroomid)| shortroomid == shortroomid_)
330		.map(|found| found.map(|(room_id, _): (&RoomId, ShortRoomId)| room_id.to_owned()))
331		.await
332		.ok_or_else(|| err!(Database("Failed to find RoomId from {shortroomid_:?}")))
333}
334
335#[implement(Service)]
336pub async fn get_or_create_shortroomid(&self, room_id: &RoomId) -> ShortRoomId {
337	if let Ok(shortroomid) = self.get_shortroomid(room_id).await {
338		return shortroomid;
339	}
340
341	self.create_shortroomid(room_id).await
342}
343
344#[implement(Service)]
345async fn create_shortroomid(&self, room_id: &RoomId) -> ShortRoomId {
346	const BUFSIZE: usize = size_of::<ShortRoomId>();
347
348	let _lock = self.creating.shortroomid.lock(room_id).await;
349
350	if let Ok(shortroomid) = self.get_shortroomid(room_id).await {
351		return shortroomid;
352	}
353
354	let short = self.services.globals.next_count();
355
356	debug_assert!(size_of_val(&*short) == BUFSIZE, "buffer requirement changed");
357
358	self.db
359		.roomid_shortroomid
360		.raw_aput::<BUFSIZE, _, _>(room_id, *short);
361
362	*short
363}
364
365#[implement(Service)]
366pub async fn delete_shortroomid(&self, room_id: &RoomId) -> Result {
367	if self
368		.db
369		.roomid_shortroomid
370		.exists(room_id)
371		.await
372		.is_ok()
373	{
374		self.db.roomid_shortroomid.remove(room_id);
375		Ok(())
376	} else {
377		Err!(Database("not found"))
378	}
379}