feat: Add support for MSC4388

This commit is contained in:
Ginger authored and Ellis Git committed 2026-11-13 18:01:10 +00:00
1 parent 77e8a6b41b
commit 0c266c28f6
10 files changed
+289 -11

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ pub mod oidc;
pub mod presence;
pub mod pusher;
pub mod registration_tokens;
pub mod rendezvous;
pub mod rooms;
pub mod sending;
pub mod server_keys;
+158
View File
@@ -0,0 +1,158 @@
use std::{
sync::Arc,
time::{Duration, SystemTime},
};
use conduwuit::{Err, Result, SyncMappedMutexGuard, SyncMutex, SyncMutexGuard, at, err, utils};
use lru_cache::LruCache;
use ruma::{
OwnedTransactionId,
api::client::rendezvous::{
create_rendezvous_session, delete_rendezvous_session, get_rendezvous_session,
update_rendezvous_session,
},
};
pub struct Service {
sessions: SyncMutex<LruCache<String, Session>>,
}
struct Session {
data: String,
created_at: SystemTime,
last_sequence_token: String,
transaction_sequences: LruCache<OwnedTransactionId, String>,
}
impl Session {
const SAVED_TRANSACTION_SEQUENCES: usize = 256;
fn new(data: String, sequence_token: String) -> Self {
Self {
data,
created_at: SystemTime::now(),
last_sequence_token: sequence_token,
transaction_sequences: LruCache::new(Self::SAVED_TRANSACTION_SEQUENCES),
}
}
}
impl crate::Service for Service {
fn build(_: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
sessions: SyncMutex::new(LruCache::new(Self::MAX_SESSIONS)),
}))
}
fn name(&self) -> &str { crate::service::make_name(std::module_path!()) }
}
impl Service {
const MAX_DATA_SIZE_BYTES: usize = 4096;
const MAX_SESSIONS: usize = 256;
const SESSION_MAX_AGE: Duration = Duration::from_secs(300);
const TOKEN_LENGTH: usize = 32;
fn create_token() -> String { utils::random_string(Self::TOKEN_LENGTH) }
fn lock_session(&self, id: &str) -> Result<SyncMappedMutexGuard<'_, Session>> {
SyncMutexGuard::try_map_or_err(self.sessions.lock(), |sessions| {
sessions
.get_mut(id)
.filter(|session| {
SystemTime::now()
.duration_since(session.created_at)
.is_ok_and(|duration| duration < Self::SESSION_MAX_AGE)
})
.ok_or_else(|| err!(Request(NotFound("Unknown session"))))
})
.map_err(at!(1))
}
pub fn create_session(
&self,
data: String,
) -> Result<create_rendezvous_session::msc4388::Response> {
if data.len() > Self::MAX_DATA_SIZE_BYTES {
return Err!(Request(TooLarge("Data too large")));
}
let id = Self::create_token();
let sequence_token = Self::create_token();
self.sessions
.lock()
.insert(id.clone(), Session::new(data, sequence_token.clone()));
Ok(create_rendezvous_session::msc4388::Response::new(
id,
sequence_token,
Self::SESSION_MAX_AGE,
))
}
pub fn update_session(
&self,
id: &str,
transaction: OwnedTransactionId,
sequence_token: String,
data: String,
) -> Result<update_rendezvous_session::msc4388::Response> {
let mut session = self.lock_session(id)?;
if data.len() > Self::MAX_DATA_SIZE_BYTES {
return Err!(Request(TooLarge("Data too large")));
}
let mut session = self.lock_session(id)?;
if let Some(sequence_token) = session.transaction_sequences.get_mut(&transaction) {
return Ok(update_rendezvous_session::msc4388::Response::new(
sequence_token.to_owned(),
));
}
if session.last_sequence_token != sequence_token {
return Err!(Request(ConcurrentWrite("Sequence token mismatch")));
}
let new_sequence_token = Self::create_token();
session.data = data;
session.last_sequence_token.clone_from(&new_sequence_token);
session
.transaction_sequences
.insert(transaction, sequence_token);
Ok(update_rendezvous_session::msc4388::Response::new(new_sequence_token))
}
pub fn get_session(&self, id: &str) -> Result<get_rendezvous_session::unstable::Response> {
let session = self.lock_session(id)?;
let session_age = SystemTime::now()
.duration_since(session.created_at)
.unwrap_or(Duration::MAX);
let session_time_remaining = Self::SESSION_MAX_AGE.saturating_sub(session_age);
Ok(get_rendezvous_session::msc4388::Response::new(
session.last_sequence_token.clone(),
session.data.clone(),
session_time_remaining,
))
}
pub fn delete_session(
&self,
id: &str,
) -> Result<delete_rendezvous_session::msc4388::Response> {
let mut sessions = self.sessions.lock();
if sessions.remove(id).is_some() {
Ok(delete_rendezvous_session::msc4388::Response::new())
} else {
Err!(Request(NotFound("Unknown session")))
}
}
}
+4 -2
View File
@@ -11,8 +11,8 @@ use crate::{
account_data, admin, announcements, antispam, appservice, client, config, emergency,
federation, firstrun, globals, key_backups, mailer,
manager::Manager,
media, moderation, oauth, oidc, presence, pusher, registration_tokens, rooms, sending,
server_keys,
media, moderation, oauth, oidc, presence, pusher, registration_tokens, rendezvous, rooms,
sending, server_keys,
service::{self, Args, Map, Service},
sync, threepid, transactions, uiaa, users,
};
@@ -33,6 +33,7 @@ pub struct Services {
pub presence: Arc<presence::Service>,
pub pusher: Arc<pusher::Service>,
pub registration_tokens: Arc<registration_tokens::Service>,
pub rendezvous: Arc<rendezvous::Service>,
pub rooms: rooms::Service,
pub federation: Arc<federation::Service>,
pub firstrun: Arc<firstrun::Service>,
@@ -89,6 +90,7 @@ impl Services {
presence: build!(presence::Service),
pusher: build!(pusher::Service),
registration_tokens: build!(registration_tokens::Service),
rendezvous: build!(rendezvous::Service),
rooms: rooms::Service {
alias: build!(rooms::alias::Service),
auth_chain: build!(rooms::auth_chain::Service),