Skip to main content

tuwunel_api/client/
unstable.rs

1use axum::extract::State;
2use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD as b64};
3use futures::StreamExt;
4use ruma::{OwnedRoomId, UInt, api::client::membership::mutual_rooms};
5use tuwunel_core::{Err, Result, err};
6
7use crate::{ClientIp, Ruma};
8
9/// Maximum number of rooms returned in a single `mutual_rooms` page.
10const PAGE_SIZE: usize = 1000;
11
12/// # `GET /_matrix/client/v1/mutual_rooms`
13/// # `GET /_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms`
14///
15/// Gets all the rooms the sender shares with the specified user.
16///
17/// An implementation of [MSC2666](https://github.com/matrix-org/matrix-spec-proposals/pull/2666)
18#[tracing::instrument(skip_all, fields(%client), name = "mutual_rooms")]
19pub(crate) async fn get_mutual_rooms_route(
20	State(services): State<crate::State>,
21	ClientIp(client): ClientIp,
22	body: Ruma<mutual_rooms::v1::Request>,
23) -> Result<mutual_rooms::v1::Response> {
24	let sender_user = body.sender_user();
25
26	if sender_user == body.user_id {
27		return Err!(Request(InvalidParam("You cannot request rooms in common with yourself.")));
28	}
29
30	if body.user_id.validate_historical().is_err() {
31		return Err!(Request(InvalidParam("The user_id is not a compliant user identifier.")));
32	}
33
34	let all: Vec<OwnedRoomId> = services
35		.state_cache
36		.get_shared_rooms(sender_user, &body.user_id)
37		.map(ToOwned::to_owned)
38		.collect()
39		.await;
40
41	let count = UInt::try_from(all.len()).unwrap_or(UInt::MAX);
42
43	let start = match body.from.as_deref() {
44		| None => 0,
45		| Some(token) => {
46			let cursor = decode_cursor(token)
47				.ok_or_else(|| err!(Request(InvalidParam("Invalid `from` token."))))?;
48
49			all.partition_point(|room_id| room_id.as_str() <= cursor.as_str())
50		},
51	};
52
53	let end = start.saturating_add(PAGE_SIZE).min(all.len());
54	let next_batch = (end < all.len()).then(|| b64.encode(all[end.saturating_sub(1)].as_str()));
55
56	let joined = if start == 0 && end == all.len() {
57		all
58	} else {
59		all[start..end].to_vec()
60	};
61
62	Ok(mutual_rooms::v1::Response { joined, count, next_batch })
63}
64
65/// Decodes a base64url pagination cursor to its room id.
66fn decode_cursor(token: &str) -> Option<OwnedRoomId> {
67	let bytes = b64.decode(token).ok()?;
68	let room_id = str::from_utf8(&bytes).ok()?;
69
70	OwnedRoomId::parse(room_id).ok()
71}