From 8eb5843f6126abc16b5c1af50a794c88f1c9521e Mon Sep 17 00:00:00 2001 From: Nyaaori <+@nyaaori.cat> Date: Mon, 1 Nov 2021 01:58:26 +0000 Subject: [PATCH 1/4] Refactor room version support, add default room version config --- src/client_server/account.rs | 4 +-- src/client_server/capabilities.rs | 28 +++++++++++++-------- src/client_server/membership.rs | 14 ++++------- src/client_server/room.rs | 11 +++------ src/database.rs | 10 +++++++- src/database/globals.rs | 41 ++++++++++++++++++++++++++++++- src/database/rooms.rs | 17 +++++++------ src/server_server.rs | 13 +++++----- 8 files changed, 95 insertions(+), 43 deletions(-) diff --git a/src/client_server/account.rs b/src/client_server/account.rs index 4b3ad0d..fb223d5 100644 --- a/src/client_server/account.rs +++ b/src/client_server/account.rs @@ -34,7 +34,7 @@ use ruma::{ EventType, }, identifiers::RoomName, - push, RoomAliasId, RoomId, RoomVersionId, UserId, + push, RoomAliasId, RoomId, UserId, }; use serde_json::value::to_raw_value; use tracing::info; @@ -282,7 +282,7 @@ pub async fn register_route( let mut content = RoomCreateEventContent::new(conduit_user.clone()); content.federate = true; content.predecessor = None; - content.room_version = RoomVersionId::Version6; + content.room_version = db.globals.default_room_version(); // 1. The room create event db.rooms.build_and_append_pdu( diff --git a/src/client_server/capabilities.rs b/src/client_server/capabilities.rs index f86b23b..36296b5 100644 --- a/src/client_server/capabilities.rs +++ b/src/client_server/capabilities.rs @@ -1,9 +1,6 @@ -use crate::{ConduitResult, Ruma}; -use ruma::{ - api::client::r0::capabilities::{ - get_capabilities, Capabilities, RoomVersionStability, RoomVersionsCapability, - }, - RoomVersionId, +use crate::{database::DatabaseGuard, ConduitResult, Ruma}; +use ruma::api::client::r0::capabilities::{ + get_capabilities, Capabilities, RoomVersionStability, RoomVersionsCapability, }; use std::collections::BTreeMap; @@ -17,17 +14,28 @@ use rocket::get; feature = "conduit_bin", get("/_matrix/client/r0/capabilities", data = "<_body>") )] -#[tracing::instrument(skip(_body))] +#[tracing::instrument(skip(_body, db))] pub async fn get_capabilities_route( _body: Ruma, + db: DatabaseGuard, ) -> ConduitResult { let mut available = BTreeMap::new(); - available.insert(RoomVersionId::Version5, RoomVersionStability::Stable); - available.insert(RoomVersionId::Version6, RoomVersionStability::Stable); + if db.globals.allow_unstable_room_versions() { + for room_version in &db.globals.unstable_room_versions { + available.insert(room_version.clone(), RoomVersionStability::Stable); + } + } else { + for room_version in &db.globals.unstable_room_versions { + available.insert(room_version.clone(), RoomVersionStability::Unstable); + } + } + for room_version in &db.globals.stable_room_versions { + available.insert(room_version.clone(), RoomVersionStability::Stable); + } let mut capabilities = Capabilities::new(); capabilities.room_versions = RoomVersionsCapability { - default: RoomVersionId::Version6, + default: db.globals.default_room_version(), available, }; diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs index ec685ec..38a7e9f 100644 --- a/src/client_server/membership.rs +++ b/src/client_server/membership.rs @@ -551,7 +551,7 @@ async fn join_room_by_id_helper( federation::membership::create_join_event_template::v1::Request { room_id, user_id: sender_user, - ver: &[RoomVersionId::Version5, RoomVersionId::Version6], + ver: &db.globals.supported_room_versions(), }, ) .await; @@ -566,12 +566,7 @@ async fn join_room_by_id_helper( let (make_join_response, remote_server) = make_join_response_and_server?; let room_version = match make_join_response.room_version { - Some(room_version) - if room_version == RoomVersionId::Version5 - || room_version == RoomVersionId::Version6 => - { - room_version - } + Some(room_version) if db.rooms.is_supported_version(&db, &room_version) => room_version, _ => return Err(Error::BadServerResponse("Room version is not supported")), }; @@ -890,9 +885,10 @@ pub(crate) async fn invite_helper<'a>( None }; - // If there was no create event yet, assume we are creating a version 6 room right now + // If there was no create event yet, assume we are creating a room with the default + // version right now let room_version_id = create_event_content - .map_or(RoomVersionId::Version6, |create_event| { + .map_or(db.globals.default_room_version(), |create_event| { create_event.room_version }); let room_version = diff --git a/src/client_server/room.rs b/src/client_server/room.rs index 47c7ee6..a63b341 100644 --- a/src/client_server/room.rs +++ b/src/client_server/room.rs @@ -23,7 +23,7 @@ use ruma::{ EventType, }, serde::{CanonicalJsonObject, JsonObject}, - RoomAliasId, RoomId, RoomVersionId, + RoomAliasId, RoomId, }; use serde_json::{json, value::to_raw_value}; use std::{ @@ -109,7 +109,7 @@ pub async fn create_room_route( let room_version = match body.room_version.clone() { Some(room_version) => { - if room_version == RoomVersionId::Version5 || room_version == RoomVersionId::Version6 { + if db.rooms.is_supported_version(&db, &room_version) { room_version } else { return Err(Error::BadRequest( @@ -118,7 +118,7 @@ pub async fn create_room_route( )); } } - None => RoomVersionId::Version6, + None => db.globals.default_room_version(), }; let content = match &body.creation_content { @@ -505,10 +505,7 @@ pub async fn upgrade_room_route( ) -> ConduitResult { let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - if !matches!( - body.new_version, - RoomVersionId::Version5 | RoomVersionId::Version6 - ) { + if !db.rooms.is_supported_version(&db, &body.new_version) { return Err(Error::BadRequest( ErrorKind::UnsupportedRoomVersion, "This server does not support that room version.", diff --git a/src/database.rs b/src/database.rs index 080e24b..c533a45 100644 --- a/src/database.rs +++ b/src/database.rs @@ -24,7 +24,7 @@ use rocket::{ request::{FromRequest, Request}, Shutdown, State, }; -use ruma::{DeviceId, EventId, RoomId, ServerName, UserId}; +use ruma::{DeviceId, EventId, RoomId, RoomVersionId, ServerName, UserId}; use serde::{de::IgnoredAny, Deserialize}; use std::{ collections::{BTreeMap, HashMap, HashSet}, @@ -63,8 +63,12 @@ pub struct Config { allow_federation: bool, #[serde(default = "true_fn")] allow_room_creation: bool, + #[serde(default = "true_fn")] + allow_unstable_room_versions: bool, #[serde(default = "false_fn")] pub allow_jaeger: bool, + #[serde(default = "default_room_version")] + default_room_version: RoomVersionId, #[serde(default = "false_fn")] pub tracing_flame: bool, #[serde(default)] @@ -137,6 +141,10 @@ fn default_max_concurrent_requests() -> u16 { 100 } +fn default_room_version() -> RoomVersionId { + RoomVersionId::Version6 +} + fn default_log() -> String { "info,state_res=warn,rocket=off,_=off,sled=off".to_owned() } diff --git a/src/database/globals.rs b/src/database/globals.rs index 05ecb56..c7aa4aa 100644 --- a/src/database/globals.rs +++ b/src/database/globals.rs @@ -4,7 +4,8 @@ use ruma::{ client::r0::sync::sync_events, federation::discovery::{ServerSigningKeys, VerifyKey}, }, - DeviceId, EventId, MilliSecondsSinceUnixEpoch, RoomId, ServerName, ServerSigningKeyId, UserId, + DeviceId, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, ServerName, + ServerSigningKeyId, UserId, }; use std::{ collections::{BTreeMap, HashMap}, @@ -39,6 +40,8 @@ pub struct Globals { keypair: Arc, dns_resolver: TokioAsyncResolver, jwt_decoding_key: Option>, + pub stable_room_versions: Vec, + pub unstable_room_versions: Vec, pub(super) server_signingkeys: Arc, pub bad_event_ratelimiter: Arc>>, pub bad_signature_ratelimiter: Arc, RateLimitState>>>, @@ -132,6 +135,11 @@ impl Globals { .as_ref() .map(|secret| jsonwebtoken::DecodingKey::from_secret(secret.as_bytes()).into_static()); + // Supported and stable room versions + let stable_room_versions = vec![RoomVersionId::Version6]; + // Experimental, partially supported room versions + let unstable_room_versions = vec![RoomVersionId::Version5]; + let s = Self { globals, config, @@ -143,6 +151,8 @@ impl Globals { tls_name_override, server_signingkeys, jwt_decoding_key, + stable_room_versions, + unstable_room_versions, bad_event_ratelimiter: Arc::new(RwLock::new(HashMap::new())), bad_signature_ratelimiter: Arc::new(RwLock::new(HashMap::new())), servername_ratelimiter: Arc::new(RwLock::new(HashMap::new())), @@ -214,6 +224,22 @@ impl Globals { self.config.allow_room_creation } + pub fn allow_unstable_room_versions(&self) -> bool { + self.config.allow_unstable_room_versions + } + + pub fn default_room_version(&self) -> RoomVersionId { + if self + .supported_room_versions() + .contains(&self.config.default_room_version.clone()) + { + self.config.default_room_version.clone() + } else { + error!("Room version in config isn't supported, falling back to Version 6"); + RoomVersionId::Version6 + } + } + pub fn trusted_servers(&self) -> &[Box] { &self.config.trusted_servers } @@ -246,6 +272,19 @@ impl Globals { &self.config.turn_secret } + pub fn supported_room_versions(&self) -> Vec { + let mut room_versions: Vec = vec![]; + self.stable_room_versions + .iter() + .for_each(|room_version| room_versions.push(room_version.clone())); + if self.allow_unstable_room_versions() { + self.unstable_room_versions + .iter() + .for_each(|room_version| room_versions.push(room_version.clone())); + }; + room_versions + } + /// TODO: the key valid until timestamp is only honored in room version > 4 /// Remove the outdated keys and insert the new ones. /// diff --git a/src/database/rooms.rs b/src/database/rooms.rs index c5b795b..338b089 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -129,6 +129,12 @@ pub struct Rooms { } impl Rooms { + /// Returns true if a given room version is supported + #[tracing::instrument(skip(self, db))] + pub fn is_supported_version(&self, db: &Database, room_version: &RoomVersionId) -> bool { + db.globals.supported_room_versions().contains(room_version) + } + /// Builds a StateMap by iterating over all keys that start /// with state_hash, this gives the full state for the given state_hash. #[tracing::instrument(skip(self))] @@ -1983,9 +1989,10 @@ impl Rooms { None }; - // If there was no create event yet, assume we are creating a version 6 room right now + // If there was no create event yet, assume we are creating a room with the default + // version right now let room_version_id = create_event_content - .map_or(RoomVersionId::Version6, |create_event| { + .map_or(db.globals.default_room_version(), |create_event| { create_event.room_version }); let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); @@ -2778,11 +2785,7 @@ impl Rooms { let (make_leave_response, remote_server) = make_leave_response_and_server?; let room_version_id = match make_leave_response.room_version { - Some(version) - if version == RoomVersionId::Version5 || version == RoomVersionId::Version6 => - { - version - } + Some(version) if self.is_supported_version(&db, &version) => version, _ => return Err(Error::BadServerResponse("Room version is not supported")), }; diff --git a/src/server_server.rs b/src/server_server.rs index 482edf0..7709f1e 100644 --- a/src/server_server.rs +++ b/src/server_server.rs @@ -2670,10 +2670,12 @@ pub fn create_join_event_template_route( None }; - // If there was no create event yet, assume we are creating a version 6 room right now - let room_version_id = create_event_content.map_or(RoomVersionId::Version6, |create_event| { - create_event.room_version - }); + // If there was no create event yet, assume we are creating a room with the default version + // right now + let room_version_id = create_event_content + .map_or(db.globals.default_room_version(), |create_event| { + create_event.room_version + }); let room_version = RoomVersion::new(&room_version_id).expect("room version is supported"); if !body.ver.contains(&room_version_id) { @@ -2937,8 +2939,7 @@ pub async fn create_invite_route( return Err(Error::bad_config("Federation is disabled.")); } - if body.room_version != RoomVersionId::Version5 && body.room_version != RoomVersionId::Version6 - { + if !db.rooms.is_supported_version(&db, &body.room_version) { return Err(Error::BadRequest( ErrorKind::IncompatibleRoomVersion { room_version: body.room_version.clone(), From 526b5b4d7ff7fda4e3b05470685bad78ce2385b1 Mon Sep 17 00:00:00 2001 From: Nyaaori <+@nyaaori.cat> Date: Mon, 1 Nov 2021 02:22:32 +0000 Subject: [PATCH 2/4] Enable room version 4 --- src/database/globals.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/globals.rs b/src/database/globals.rs index c7aa4aa..0c6476d 100644 --- a/src/database/globals.rs +++ b/src/database/globals.rs @@ -138,7 +138,7 @@ impl Globals { // Supported and stable room versions let stable_room_versions = vec![RoomVersionId::Version6]; // Experimental, partially supported room versions - let unstable_room_versions = vec![RoomVersionId::Version5]; + let unstable_room_versions = vec![RoomVersionId::Version4, RoomVersionId::Version5]; let s = Self { globals, From 25065a58e8267c3aeef72e25c58d5202015c8976 Mon Sep 17 00:00:00 2001 From: Nyaaori <+@nyaaori.cat> Date: Mon, 1 Nov 2021 08:57:27 +0000 Subject: [PATCH 3/4] Support room version 3 --- src/client_server/membership.rs | 3 ++- src/database/globals.rs | 6 +++++- src/database/rooms.rs | 23 +++++++++++++++++++++++ src/pdu.rs | 16 ++++++++++++---- src/server_server.rs | 6 +++--- 5 files changed, 45 insertions(+), 9 deletions(-) diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs index 38a7e9f..9147242 100644 --- a/src/client_server/membership.rs +++ b/src/client_server/membership.rs @@ -1035,7 +1035,8 @@ pub(crate) async fn invite_helper<'a>( let pub_key_map = RwLock::new(BTreeMap::new()); // We do not add the event_id field to the pdu here because of signature and hashes checks - let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(&response.event) { + let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(&response.event, &db) + { Ok(t) => t, Err(_) => { // Event could not be converted to canonical json diff --git a/src/database/globals.rs b/src/database/globals.rs index 0c6476d..30bf937 100644 --- a/src/database/globals.rs +++ b/src/database/globals.rs @@ -138,7 +138,11 @@ impl Globals { // Supported and stable room versions let stable_room_versions = vec![RoomVersionId::Version6]; // Experimental, partially supported room versions - let unstable_room_versions = vec![RoomVersionId::Version4, RoomVersionId::Version5]; + let unstable_room_versions = vec![ + RoomVersionId::Version3, + RoomVersionId::Version4, + RoomVersionId::Version5, + ]; let s = Self { globals, diff --git a/src/database/rooms.rs b/src/database/rooms.rs index 338b089..1b956eb 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -3451,4 +3451,27 @@ impl Rooms { Ok(()) } + + /// Returns the room's version. + #[tracing::instrument(skip(self))] + pub fn get_room_version(&self, room_id: &RoomId) -> RoomVersionId { + let create_event = self + .room_state_get(room_id, &EventType::RoomCreate, "") + .unwrap(); + + let create_event_content: Option = create_event + .as_ref() + .map(|create_event| { + serde_json::from_str(create_event.content.get()).map_err(|e| { + warn!("Invalid create event: {}", e); + Error::bad_database("Invalid create event in db.") + }) + }) + .transpose() + .unwrap(); + + create_event_content + .map(|create_event| create_event.room_version) + .expect("Invalid room version") + } } diff --git a/src/pdu.rs b/src/pdu.rs index 0f99f43..bcd52ed 100644 --- a/src/pdu.rs +++ b/src/pdu.rs @@ -1,4 +1,4 @@ -use crate::Error; +use crate::{Database, Error}; use ruma::{ events::{ room::member::RoomMemberEventContent, AnyEphemeralRoomEvent, AnyInitialStateEvent, @@ -6,7 +6,7 @@ use ruma::{ EventType, StateEvent, }, serde::{CanonicalJsonObject, CanonicalJsonValue, Raw}, - state_res, EventId, MilliSecondsSinceUnixEpoch, RoomId, RoomVersionId, UInt, UserId, + state_res, EventId, MilliSecondsSinceUnixEpoch, RoomId, UInt, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::{ @@ -331,16 +331,24 @@ impl Ord for PduEvent { /// Returns a tuple of the new `EventId` and the PDU as a `BTreeMap`. pub(crate) fn gen_event_id_canonical_json( pdu: &RawJsonValue, + db: &Database, ) -> crate::Result<(EventId, CanonicalJsonObject)> { - let value = serde_json::from_str(pdu.get()).map_err(|e| { + let value: CanonicalJsonObject = serde_json::from_str(pdu.get()).map_err(|e| { warn!("Error parsing incoming event {:?}: {:?}", pdu, e); Error::BadServerResponse("Invalid PDU in server response") })?; + let room_id = value + .get("room_id") + .and_then(|id| RoomId::try_from(id.as_str()?).ok()) + .expect("Invalid room id in event"); + + let room_version_id = db.rooms.get_room_version(&room_id); + let event_id = EventId::try_from(&*format!( "${}", // Anything higher than version3 behaves the same - ruma::signatures::reference_hash(&value, &RoomVersionId::Version6) + ruma::signatures::reference_hash(&value, &room_version_id) .expect("ruma can calculate reference hashes") )) .expect("ruma's reference hashes are valid event ids"); diff --git a/src/server_server.rs b/src/server_server.rs index 7709f1e..5357a90 100644 --- a/src/server_server.rs +++ b/src/server_server.rs @@ -725,7 +725,7 @@ pub async fn send_transaction_message_route( for pdu in &body.pdus { // We do not add the event_id field to the pdu here because of signature and hashes checks - let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu) { + let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu, &db) { Ok(t) => t, Err(_) => { // Event could not be converted to canonical json @@ -1911,7 +1911,7 @@ pub(crate) fn fetch_and_handle_outliers<'a>( Ok(res) => { warn!("Got {} over federation", id); let (calculated_event_id, value) = - match crate::pdu::gen_event_id_canonical_json(&res.pdu) { + match crate::pdu::gen_event_id_canonical_json(&res.pdu, &db) { Ok(t) => t, Err(_) => { back_off((**id).clone()); @@ -2815,7 +2815,7 @@ async fn create_join_event( // let mut auth_cache = EventMap::new(); // We do not add the event_id field to the pdu here because of signature and hashes checks - let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu) { + let (event_id, value) = match crate::pdu::gen_event_id_canonical_json(pdu, &db) { Ok(t) => t, Err(_) => { // Event could not be converted to canonical json From 04fcac20b3b2b859b19db17fe61c695014369244 Mon Sep 17 00:00:00 2001 From: Nyaaori <+@nyaaori.cat> Date: Fri, 5 Nov 2021 20:47:11 +0000 Subject: [PATCH 4/4] Cleanup rooms.rs, globals.rs, and pdu.rs --- src/database/globals.rs | 28 ++++++++++++---------------- src/database/rooms.rs | 15 ++++++--------- src/pdu.rs | 2 +- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/database/globals.rs b/src/database/globals.rs index 30bf937..de5b250 100644 --- a/src/database/globals.rs +++ b/src/database/globals.rs @@ -144,7 +144,7 @@ impl Globals { RoomVersionId::Version5, ]; - let s = Self { + let mut s = Self { globals, config, keypair: Arc::new(keypair), @@ -169,6 +169,14 @@ impl Globals { fs::create_dir_all(s.get_media_folder())?; + if !s + .supported_room_versions() + .contains(&s.config.default_room_version) + { + error!("Room version in config isn't supported, falling back to Version 6"); + s.config.default_room_version = RoomVersionId::Version6; + }; + Ok(s) } @@ -233,15 +241,7 @@ impl Globals { } pub fn default_room_version(&self) -> RoomVersionId { - if self - .supported_room_versions() - .contains(&self.config.default_room_version.clone()) - { - self.config.default_room_version.clone() - } else { - error!("Room version in config isn't supported, falling back to Version 6"); - RoomVersionId::Version6 - } + self.config.default_room_version.clone() } pub fn trusted_servers(&self) -> &[Box] { @@ -278,13 +278,9 @@ impl Globals { pub fn supported_room_versions(&self) -> Vec { let mut room_versions: Vec = vec![]; - self.stable_room_versions - .iter() - .for_each(|room_version| room_versions.push(room_version.clone())); + room_versions.extend(self.stable_room_versions.clone()); if self.allow_unstable_room_versions() { - self.unstable_room_versions - .iter() - .for_each(|room_version| room_versions.push(room_version.clone())); + room_versions.extend(self.unstable_room_versions.clone()); }; room_versions } diff --git a/src/database/rooms.rs b/src/database/rooms.rs index 1b956eb..a0455db 100644 --- a/src/database/rooms.rs +++ b/src/database/rooms.rs @@ -3454,10 +3454,8 @@ impl Rooms { /// Returns the room's version. #[tracing::instrument(skip(self))] - pub fn get_room_version(&self, room_id: &RoomId) -> RoomVersionId { - let create_event = self - .room_state_get(room_id, &EventType::RoomCreate, "") - .unwrap(); + pub fn get_room_version(&self, room_id: &RoomId) -> Result { + let create_event = self.room_state_get(room_id, &EventType::RoomCreate, "")?; let create_event_content: Option = create_event .as_ref() @@ -3467,11 +3465,10 @@ impl Rooms { Error::bad_database("Invalid create event in db.") }) }) - .transpose() - .unwrap(); - - create_event_content + .transpose()?; + let room_version = create_event_content .map(|create_event| create_event.room_version) - .expect("Invalid room version") + .ok_or_else(|| Error::BadDatabase("Invalid room version"))?; + Ok(room_version) } } diff --git a/src/pdu.rs b/src/pdu.rs index bcd52ed..6fe322a 100644 --- a/src/pdu.rs +++ b/src/pdu.rs @@ -348,7 +348,7 @@ pub(crate) fn gen_event_id_canonical_json( let event_id = EventId::try_from(&*format!( "${}", // Anything higher than version3 behaves the same - ruma::signatures::reference_hash(&value, &room_version_id) + ruma::signatures::reference_hash(&value, &room_version_id?) .expect("ruma can calculate reference hashes") )) .expect("ruma's reference hashes are valid event ids");